Java设计模式

Java日记之设计模式总结(结构型)

2019-11-30  本文已影响0人  居居居居居居x

前言

推荐看这篇文章之前先了解Java日记之设计模式初探

结构型设计模式总共有7种

1.外观模式
2.装饰者模式
3.适配器模式
4.享元模式
5.组合模式
6.桥接模式
7.代理模式

1.外观模式

代码举例:

//物品实体类
public class PointsGift {

    private String name;

    public PointsGift(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
//支付子系统
public class PointsPaymentService {

    public boolean pay(PointsGift pointsGift) {
        //扣减积分逻辑
        System.out.println("支付" + pointsGift.getName() + "积分成功");
        return true;
    }
}

//校验子系统
public class QualifyService {

    public boolean isAvailable(PointsGift pointsGift) {
        System.out.println("校验" + pointsGift.getName() + "积分资格通过,库存通过");
        return true;
    }
    
}

//物流子系统
public class ShippingService {
    
    //返回订单号
    public String shipGift(PointsGift pointsGift){
        //物流系统的对接逻辑
        System.out.println(pointsGift.getName()+"进入物流系统");
        String shippingOrderNo = "666";
        return shippingOrderNo;
    }
    
}
//外观类
public class GiftExchangeService {

    private QualifyService qualifyService = new QualifyService();
    private PointsPaymentService pointsPaymentService = new PointsPaymentService();
    private ShippingService shippingService = new ShippingService();

    public void giftExchange(PointsGift pointsGift) {
        //资格校验通过
        if (qualifyService.isAvailable(pointsGift)) {
            //支付通过
            if (pointsPaymentService.pay(pointsGift)) {
                //返回订单号
                String shippingOrderNo = shippingService.shipGift(pointsGift);
                System.out.println("物流系统下单成功,订单号是:" + shippingOrderNo);
            }
        } else {
            //不通过
            //TODO
        }
    }
}


//测试类
public class Test {

    public static void main(String[] args) {
        PointsGift pointsGift = new PointsGift("T桖");
        //正常来说可以通过spring注入
        GiftExchangeService giftExchangeService = new GiftExchangeService();
        giftExchangeService.giftExchange(pointsGift);
    }
}

我们举个例子,比如有一个积分商城,你需要去进行换购物品,这时候就需要经过积分校验-支付-进入物流这3个过程,也就是3个子系统,而外观类呢就是把这3个子系统结合在一起,我们应用层只需要和外观类交互就可以换购1个商品,而不不用去在意那3个子系统的哪个先调用和一些其中具体的逻辑,从UML图也可以看出外观模式的核心思想就是应用层不要跟子系统去相连,只要通过外观类联系就好了,当然外观类也可以改成抽象外观类,这个就需要通过具体的业务逻辑判断了。

外观模式运行结果
外观模式UML

2.装饰者模式

代码举例:

//煎饼抽象类
public abstract class ABatterCake {

    public abstract String getDesc();

    public abstract int cost();
}

//煎饼实体类
public class BatterCake extends ABatterCake{


    @Override
    public String getDesc() {
        return "煎饼";
    }

    @Override
    public int cost() {
        return 8;
    }
}

//装饰者模式抽象类
public abstract class AbstractDecorator extends ABatterCake {
    
    //持有抽象煎饼的引用
    private ABatterCake aBatterCake;

    protected abstract void odSomething();

    public AbstractDecorator(ABatterCake aBatterCake) {
        this.aBatterCake = aBatterCake;
    }

    @Override
    public String getDesc() {
        return aBatterCake.getDesc();
    }

    @Override
    public int cost() {
        return aBatterCake.cost();
    }
}

这里举例一个场景,单位楼下有一个卖煎饼的,然后在单位的茶水间吃早餐,有时候加1个蛋或者香肠。这时候我们就需要装饰者模式了,我们首先定义1个煎饼的抽象类,还有最需要的装饰者抽象类,,我们这里持有抽象煎饼类的引用,接下来,我们该怎么给这个煎饼添加鸡蛋和香肠呢。代码如下:

//加蛋的类
public class EggDecorator extends AbstractDecorator {

    @Override
    protected void odSomething() {

    }

    public EggDecorator(ABatterCake aBatterCake) {
        super(aBatterCake);
    }


    @Override
    public String getDesc() {
        return super.getDesc() + "加1个煎蛋 ";
    }

    @Override
    public int cost() {
        return super.cost() + 1;
    }
}

//加香肠的类
public class SausageDecorator extends AbstractDecorator {


    @Override
    protected void odSomething() {

    }

    public SausageDecorator(ABatterCake aBatterCake) {
        super(aBatterCake);
    }


    @Override
    public String getDesc() {
        return super.getDesc() + "加1根香肠 ";
    }

    @Override
    public int cost() {
        return super.cost() + 2;
    }
}

//测试类
public class Test {

    public static void main(String[] args) {
        ABatterCake aBatterCake;
        aBatterCake = new BatterCake();
        aBatterCake = new EggDecorator(aBatterCake);
        aBatterCake = new SausageDecorator(aBatterCake);
        aBatterCake = new EggDecorator(aBatterCake);
        System.out.println(aBatterCake.getDesc() + "销售价格:" + aBatterCake.cost());
    }
}

我们让香肠类和蛋糕类去继承装饰者类,因为装饰者类不是无参的构造函数,所以我们要实现构造方法。然后通过装饰者模式来返回新的对象。这样子我们需要加香肠还是煎蛋,只需要重新new一个,然后获得新的返回对象就行。我们从UML中也可以看到,煎蛋类和香肠类是同级的。

装饰者模式结果
装饰者模式UML类图

3.适配器模式

适配器和外观模式都是对现有的类进行封装,外观定义了新的接口,适配器则是复用一个原有的接口,适配器是使2个已有的接口协同工作,而外观则是在系统中提供一个更方便的访问入口,这两个最大的区别就是适配力度不同,外观模式是用来适配整个子系统,外观所针对的对象力度更大。

类适配器模式

代码举例:

//被适配者
public class Adaptee {

    public void adapteeRequest(){
        System.out.println("被适配者的方法");
    }
}

//转换接口
public interface Target {

    void request();
}

//适配器
public class Adapter extends Adaptee implements Target{
    @Override
    public void request() {
        //添加各种逻辑代码
        super.adapteeRequest();
    }
}

//实现类
public class ConcreteTarget implements Target{

    @Override
    public void request() {
        System.out.println("concreteTarget目标方法");
    }
}

//测试类
public class Test {

    public static void main(String[] args) {
        Target target = new ConcreteTarget();
        target.request();
        
        Target adapterTarget = new Adapter();
        adapterTarget.request();
    }
}

首先建立一个被适配者的类,然后建立一个实现类ConcreteTarget,接下来我们就需要一个适配器Adapter来进行适配器,这个Adapter要继承被适配器的类和Target接口。这样我们的接口就可以通过Adapter来实现了。也就是说Target的具体实现通过Adapter已经移交给了Adaptee,这就是类适配器,这里强调的是继承,通过继承来获取被适配者的一些方法,我们可以在request()添加各种逻辑代码。

类适配器模式结果
类适配器UML

对象适配器模式

//适配器类
public class Adapter implements Target{

    private Adaptee adaptee  = new Adaptee();

    @Override
    public void request() {
         ......
         adaptee.adapteeRequest();
    }
}

其它的代码不变,变就是适配器类,这里不通过继承,直接通过持有Adaptee的引用来进行组合。结果不变。


对象适配器模式UML

我们这里在举一个变压器的例子,来理解适配器设计模式。

//220V电压
public class AC220 {

    public int outPutAC220V() {
        int outPut = 220;
        System.out.println("输出交流电" + outPut + "V");
        return outPut;
    }
}


//5V直流电
public interface DC5 {

    int outputDC5V();
}


//适配器类
public class PowerAdapter implements DC5 {

    AC220 ac220 = new AC220();


    @Override
    public int outputDC5V() {
        int adapterInput = ac220.outPutAC220V();
        //变压器
        int adapterOutput = adapterInput / 44;
        System.out.println("使用PowerAdapter输入AC:" + adapterInput + "V" + "输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}


//测试类
public class Test {

    public static void main(String[] args) {
        DC5 dc5 = new PowerAdapter();
        dc5.outputDC5V();
    }
}
变压器运行结果
从上诉代码可以看出,220V就是一个被适配者的类,然后我们通过变压器(也就是适配器类)来适配输出AC,这样就可以进行转换了。

享元模式

代码举例:
我们现在有一个场景,一个公司要求各个部门的管理者做年底总结报告,如果报告已经生成过了,就没必要就去new一个了。这里也会结合工程模式。

//经理功能接口
public interface Employee {

    void report();
}

//经理
public class Manager implements Employee {

    //所在部门
    private String department;
    //报告内容
    private String reportContent;


    public Manager(String department) {
        this.department = department;
    }

    @Override
    public void report() {
        System.out.println(reportContent);
    }

    public String getReportContent() {
        return reportContent;
    }

    public void setReportContent(String reportContent) {
        this.reportContent = reportContent;
    }
}

//工厂模式
public class EmployeeFactory {

    //容器单例
    private static final Map<String, Employee> EMPLOYEE_MAP = new HashMap<>();

    public static Employee getManager(String department) {
        Manager manager = (Manager) EMPLOYEE_MAP.get(department);

        if (manager == null) {
            manager = new Manager(department);
            System.out.print("创建部门经理:" + department);
            String reportContent = department + "部门汇报:此次报告的主要内容是******";
            manager.setReportContent(reportContent);
            System.out.println(" 创建报告:" + reportContent);
            EMPLOYEE_MAP.put(department, manager);

        }

        return manager;
    }
}

//测试类
public class Test {

    private static final String[] departments = {"RD", "QA", "PM", "BD"};

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            //随机叫部门报告
            String department = departments[(int) (Math.random() * departments.length)];
            Manager manager = (Manager) EmployeeFactory.getManager(department);
            manager.report();
        }
    }
}
享元模式运行结果

从上述代码就可以看出EMPLOYEE_MAP就是享元模式的缓冲池,每次叫部门做报告的时候,如果没有做报告的就会new一个报告出来,如果之前已经报告过的,就直接从缓冲池里进行报告,不用再次创建报告。对于部门经理,也就是department,它是声明在外部的,并且manager的department这个状态依赖于外部传入的参数,所以可以认为它是外部状态,因为传入不同的department,获得的manager的department这个属性是不一样的,对于Manager这个类的实体,我们就可以认为department是外部状态,那内部状态就是比如说我直接在Manager直接声明一个String变量。

private String title = "部门经理";

这个时候,不论外部怎么传递department或者reportContent,Manager的title是不变的,它不会随着的外部状态而变化。title永远都是部门经理。

5.组合模式

代码举例:
老样子,举例一个业务场景,拿慕课网来说,这里有很多课程,也有课程目录,课程有名称和价格,例如Java课程,那这个课程就属于Java目录下,Java目录下有很多Java相关的课程,Python也一样。如果使课程的目录和课程继承同一个抽象类,例如目录组件,那么就可以把课程本身,还有由课程组合成的课程目录视为同一类对象进行操作,但是在操作上还会有一些差别,具体的差别在定制化处理。

//组合模式抽象类
public abstract class CatalogComponent {

    public void add(CatalogComponent catalogComponent){
        throw new UnsupportedOperationException("不支持添加操作");
    }

    public void remove(CatalogComponent catalogComponent){
        throw new UnsupportedOperationException("不支持删除操作");
    }

    public String getName(CatalogComponent catalogComponent){
        throw new UnsupportedOperationException("不支持获取名称操作");
    }

    public double getPrice(CatalogComponent catalogComponent){
        throw new UnsupportedOperationException("不支持获取价格操作");
    }

    public void print(){
        throw new UnsupportedOperationException("不支持打印操作");
    }


}

//课程类
public class Course extends CatalogComponent {

    private String name;
    private double price;


    public Course(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String getName(CatalogComponent catalogComponent) {
        return this.name;
    }

    @Override
    public double getPrice(CatalogComponent catalogComponent) {
        return this.price;
    }

    @Override
    public void print() {
        System.out.println("Course name:" + name + "price:" + price);
    }
}


//目录类
public class CourseCatalog extends CatalogComponent {

    private List<CatalogComponent> items = new ArrayList<>();
    private String name;

    public CourseCatalog(String name) {
        this.name = name;
    }

    @Override
    public void add(CatalogComponent catalogComponent) {
        items.add(catalogComponent);
    }

    @Override
    public void remove(CatalogComponent catalogComponent) {
        items.remove(catalogComponent);
    }

    @Override
    public void print() {
        System.out.println(this.name);
        for (CatalogComponent catalogComponent : items){
            System.out.print(" ");
            catalogComponent.print();
        }
    }

    public String getName() {
        return this.name;
    }
}


//测试类
public class Test {

    public static void main(String[] args) {
        CatalogComponent linuxCourse = new Course("Linux课程", 11);
        CatalogComponent windowsCourse = new Course("Windows课程", 10);

        CatalogComponent javaCourseCatalog = new CourseCatalog("Java课程目录");

        CatalogComponent mallCourse1 = new Course("Java电商课程1期", 55);
        CatalogComponent mallCourse2 = new Course("Java电商课程2期", 66);
        CatalogComponent designPattern = new Course("Java设计模式", 77);

        javaCourseCatalog.add(mallCourse1);
        javaCourseCatalog.add(mallCourse2);
        javaCourseCatalog.add(designPattern)    ;

        //主目录
        CatalogComponent imoocMainCourseCatalog = new CourseCatalog("慕课网课程主目录");
        imoocMainCourseCatalog.add(linuxCourse);
        imoocMainCourseCatalog.add(windowsCourse);
        imoocMainCourseCatalog.add(javaCourseCatalog);

        imoocMainCourseCatalog.print();
    }
}
组合模式运行结果
组合模式UML

从运行结果我们也可以看到,我们new了一个主目录然后分别添加1个二级目录和2个课程,以输出前面空格来代表层级,在Java目录下面又有3个Java课程,这里从UML也可以看出CatalogComponent和CatalogComponent形成了组合关系。但是这里就会体现组合设计模式的缺点了,yejiushi也就是如果说要动态的对类型进行限制,会使业务逻辑比较复杂,例如说现在先Java课程目录本身是二级目录,慕课网目录是一级目录,对于空格来说,二级目录是需要打印2个的,从而区分从属关系,对于这里就要进行动态的类型判断了。也就是说使用组合设计模式,我们就要对抽象组件的设计多花点心思,而且还有缺点目录和课程哪些符合业务逻辑的重写,哪些不符合。

接着,我们现在还要改代码,其实很简单,只要添加1个层级就好了

public class CourseCatalog extends CatalogComponent {

    private List<CatalogComponent> items = new ArrayList<>();
    private String name;
    //添加层级
    private Integer level;

    ......

    @Override
    public void print() {
        System.out.println(this.name);
        for (CatalogComponent catalogComponent : items){
            //进行输出
            if (this.level != null){
                for (int i = 0;i<this.level;i++){
                    System.out.print(" ");
                }
            }
            catalogComponent.print();
        }
    }

    .......
}


public class Test {

    public static void main(String[] args) {
        ......

        CatalogComponent javaCourseCatalog = new CourseCatalog("Java课程目录",2);

        ......

        //主目录
        CatalogComponent imoocMainCourseCatalog = new CourseCatalog("慕课网课程主目录",1);
        ......
    }
}
添加level后的输出结果

6.桥接模式

代码举例:
老样子举个例子,在中国有很多银行,农业(ABC)和工商银行(ICBC),然后我们就有自己的账号,关于存蓄账号分为定期账号和活期账号。定期可以存3个月或者1年,银行还会给利息,利息比活期账号利息高很多。这里面分2大块,银行和账号。·

//定义账号接口
public interface Account {

    Account openAccount();

    //查看账户类型
    void showAccount();
}


//定期账号
public class DepositAccount implements Account{

    @Override
    public Account openAccount() {
        System.out.println("打开定期账号");
        return new DepositAccount();
    }

    @Override
    public void showAccount() {
        System.out.println("这是一个定期账号");
    }
}

//活期账号
public class SavingAccount implements Account{

    @Override
    public Account openAccount() {
        System.out.println("打开活期账号");
        return new SavingAccount();
    }

    @Override
    public void showAccount() {
        System.out.println("这是一个活期账号");
    }
}
//银行抽象类
public abstract class Bank {

    protected Account account;

    public Bank(Account account){
        this.account = account;
    }

    abstract Account openAccount();
}


//农业银行
public class ABCBank extends Bank{


    public ABCBank(Account account) {
        super(account);
    }

    @Override
    Account openAccount() {
        System.out.println("打开中国农业银行账号");
        //这里要注意委托实现
        account.openAccount();
        return account;
    }
}

//工商银行
public class ICBCBank extends Bank {


    public ICBCBank(Account account) {
        super(account);
    }

    @Override
    Account openAccount() {
        System.out.println("打开中国工商银行账号");
        //这里要注意委托实现
        account.openAccount();
        return account;
    }
}
桥接模式UML

首先我们创建了Account,它是这个桥的实现接口,左侧是实现层,右侧是抽象层,桥接模式最重要的就是桥的左侧和右侧划分清楚,然后在Bank抽象类里持有Account接口的引用,这就是桥接模式的核心,通过组合的方式,底下的都是具体的类实现。而且可以一直扩展,这就是桥接模式的精华所在。

public class Test {

    public static void main(String[] args) {
        Bank icbcBank = new ICBCBank(new DepositAccount());
        Account icbcAccount = icbcBank.openAccount();
        icbcAccount.showAccount();

        Bank abcBank = new ICBCBank(new SavingAccount());
        Account abcAccount = abcBank.openAccount();
        abcAccount.showAccount();
    }
}
桥接模式运行结果

7.代理模式

代码举例:

动态代理

代理模式UML图,侵删

从UML图可以看出代理类与实现类都是继承了抽象类,这样的好处就是可以有一样的方法。

//接口
public interface Subject {

    void visit();
}

//真实类
public class RealSubject implements Subject{

    private String name = "juju";

    @Override
    public void visit() {
        System.out.println(name);
    }
}


//代理类
public class ProxySubject implements Subject{

    private Subject subject;


    public ProxySubject(Subject subject){
        this.subject =subject;
    }

    @Override
    public void visit() {
        subject.visit();
    }
}

//测试
public class Test {

    public static void main(String[] args) {
        ProxySubject proxySubject = new ProxySubject(new RealSubject());
        proxySubject.visit();
    }
}
静态代理运行结果
从上面代码可以看出,代理类接收一个Subject对象,任何实现该接口的真实类,都可以通过这个代理类进行代理,增加了通用性,但是也有一个缺点,就是每一个代理类都必须要实现真是类的接口,这样会造成后期的臃肿。

动态代理

接口和真实类还是和静态代理一样,然后我们在创建一个DynamicProxy来进行动态代理来继承InvocationHandler,并重写invoke()方法。然后进行测试。

public class DynamicProxy implements InvocationHandler {


    private Object object;

    public DynamicProxy(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object,args);
        return result;
    }
}


//测试类
public class Test {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        DynamicProxy dynamicProxy = new DynamicProxy(realSubject);
        ClassLoader classLoader = realSubject.getClass().getClassLoader();
        Subject subject = (Subject) Proxy.newProxyInstance(classLoader,new Class[]{Subject.class},dynamicProxy);
        subject.visit();
    }
}

这里创建动态对象通过Proxy.newProxyInstance()来进行创建,第一个参数为表示当前使用到的appClassloader,第二个为目标对象实现的一组接口,第三个为表示当前的InvocationHandler实现实例对象。这里的动态对象创建是通过Jdk来实现的,当然还有其他通过非Jdk来实现的方法。

参考

上一篇 下一篇

猜你喜欢

热点阅读