设计模式之适配器、模板方法、装饰者、组合

适配器模式

12V直流的计算器和交流100V之间需要适配器转换

现有程序无法直接使用,需要做适当的交换之后才能使用。填补“现有程序”和“所需的程序”之间的差异的设计模式就是Adapter适配器模式

Adapter模式也被称为Wrapper模式,包装器,

适配器模式两种方式:

  • 组合方式
  • 继承方式

实例

现有类

需求:将hello包装为(hello)或者*hello*

  • 消费端要使用的是Print接口,
  • 提供者现只有Banner类
  • 要转换一下Banner,转成Print给消费者
// 消费者要的接口
public interface Print {
    
    
    public abstract void printWeak();
    public abstract void printStrong();
}
//现有实现类
public class Banner {
    
    
    private String string;
    public Banner(String string) {
    
    
        this.string = string;
    }
    public void showWithParen() {
    
    
        System.out.println("(" + string + ")");
    }
    public void showWithAster() {
    
    
        System.out.println("*" + string + "*");
    }
}
①继承方式
//适配器 // 继承方式
public class PrintBanner extends Banner implements Print {
    
    
    public PrintBanner(String string) {
    
    
        super(string);
    }
    public void printWeak() {
    
    
        showWithParen();
    }
    public void printStrong() {
    
    
        showWithAster();
    }
}

// 使用方法
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Print p = new PrintBanner("Hello");
        p.printWeak();
        p.printStrong();
    }
}
②组合方式
// 适配器 // 属性方式(组合方式)
public class PrintBanner extends Print {
    
    
    private Banner banner;
    public PrintBanner(String string) {
    
    
        this.banner = new Banner(string);
    }
    public void printWeak() {
    
    
        banner.showWithParen();
    }
    public void printStrong() {
    
    
        banner.showWithAster();
    }
}
//使用
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Print p = new PrintBanner("Hello");
        p.printWeak();
        p.printStrong();
    }
}

模板方法设计模式

多态的应用:模板方法设计模式(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题:

  • 当功能内部一部分实现是确定的, 一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式

拿mybatis中的执行器举例:

img
  • Executor:他们都有顶级接口Executor
  • BaseExecutor是抽象类,实现了query(),但其内部调用doQuery()
  • 下面SimpleExecutor/ReuseExecutor/SimpleExecutor继承了BaseExecutor,并且实现了doQuery()

我们的调用逻辑是,外部调用时只调用query()方法,query()先进行一些准备工作,然后具体工作交给子类.doQuery()处理。

这在spring源码的getBean/doGetBean等都有体现

模板方法应用

模板方法设计模式是编程中经常用得到的模式。 各个框架、 类库中都有他的影子, 比如常见的有:
 数据库访问的封装
 Junit单元测试
 JavaWeb的Servlet中关于doGet/doPost方法调用
 Hibernate中模板程序
 Spring中JDBCTemlate、 HibernateTemplate等

  • mybatis中的执行器

  • java JUC包下面的AQS

同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):

  1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
  2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。

装饰者模式

定义

装饰模式是在

  • 不必改变原类和
  • 不使用继承的情况下 (但可以使用实现),

动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象

使用场景

  1. 需要在运行时动态的给一个对象增加额外的职责时候
  2. 需要给一个现有的类增加职责,但是又不想通过继承的方式来实现的时候(应该优先使用组合而非继承),或者通过继承的方式不现实的时候(可能由于排列组合产生类爆炸的问题)。

比如,数据库查询的时候,我们有了缓存,可以先从缓存中拿,缓存中没有再从数据库中查。那么我们封装的时候查询类内部持有者数据库的属性,

比如mybatis中执行器:

img
public class CachingExecutor implements Executor {
    
    
  private BaseExecutor delegate;// 分发 ,这个是术语BaseExecutor那个支的实现类

CachingExecutor只负责缓存的逻辑,缓存中没有再拿delegate去查询数据库

具体业务场景

在上一篇 桥接模式 中提到林蛋大公司接了星巴克的咖啡系统项目。林蛋大在王二狗的指导下使用桥接模式实现了星巴克要求的各种下单功能:大杯原味、大杯加糖、大杯加奶;中杯原味、中杯加糖、中杯加奶;小杯原味、小杯加糖、小杯加奶。刚舒服了没两天,项目经理就来找蛋大了:蛋大啊,现在用户的口味太刁,好多都要同时加奶,加糖,有的还要加蜂蜜,咱们目前这个实现不支持,你去改一下。对了,有些用户要求先加糖再加奶,而一些用户要求先加奶然后再加糖,顺序不能乱!蛋大:我了个去。。。。。。。正当林蛋大一筹莫展的时候,王二狗兴高采烈的走了过来,看样子和牛翠花的进展不错。蛋大:狗哥,我这个有个棘手的问题请教你一下。。。

王二狗开始分析需求:假设我们有一个原味咖啡的类 OriginalCoffee, 目前的需求就是要动态的给这个类的一些实例增加一些额外功能,此处就是动态的对某些咖啡制作过程增加新的流程,例如加奶,加糖,而有的咖啡却保持原味不变。

这种需求要是通过继承的方式就不太好实现,因为咖啡制作过程是动态变化的。例如有的需要原味咖啡,有的需要加奶咖啡,有的需要加糖咖啡,而有的需要先加奶再加糖咖啡,而有的需要先加糖再加奶的咖啡,。。。这是一个排列组合问题,如果使用类继承的方式实现,必须预先将所有可能组合都想清楚,然后生成相应的子类,随着咖啡口味的增多,以及添加顺序的改变,几乎是不可扩展和维护的。

经过需求分析,二狗锁定了装饰者模式来实现此需求。原味咖啡是本质,而加奶,加糖都是在装饰这个本质的东西,再怎么加东西咖啡还是咖啡。

下图是装饰者模式的UML图

img

首先我们有一个ICoffee接口,里面有一个制作咖啡的接口方法makeCoffee()。要进行装饰的类 OriginalCoffee 和装饰者基类CoffeeDecorator(一般为抽象类)实现了此接口。

CoffeeDecorator类里面持有一个ICoffee引用,我们第一步会把要装饰那个原始对象赋值给这个引用,那样在装饰者类中才可以调用到那个被装饰的对象的方法。MilkDecoratorSugarDecorator 都继承至CoffeeDecorator, 都是具体的装饰者类。

具体实现

第一步:先声明一个原始对象的接口

public interface ICoffee {
    
    
    void makeCoffee();
}

第二步:构建我们的原始对象,此处为原味咖啡对象,它实现了ICoffee接口。

public class OriginalCoffee implements ICoffee {
    
    
    @Override
    public void makeCoffee() {
    
    
        System.out.print("原味咖啡 ");
    }
}

第三步:构建装饰者抽象基类,它要实现与原始对象相同的接口ICoffee,其内部持有一个ICoffee类型的引用,用来接收被装饰的对象

public abstract class CoffeeDecorator implements ICoffee {
    
    
    private  ICoffee coffee;// 原味咖啡
    public CoffeeDecorator(ICoffee coffee){
    
    
        this.coffee=coffee;
    }

    @Override
    public void makeCoffee() {
    
    
        coffee.makeCoffee();
    }
}

第四步:构建各种装饰者类,他们都继承至装饰者基类 CoffeeDecorator。此处生成了两个,一个是加奶的装饰者,另一个是加糖的装饰者。

public class MilkDecorator extends CoffeeDecorator {
    
    
    public MilkDecorator(ICoffee coffee) {
    
    
        super(coffee);
    }
    @Override
    public void makeCoffee() {
    
    
        super.makeCoffee();
        addMilk();
    }
    private void addMilk(){
    
    
        System.out.print("加奶 ");
    }    
}
public class SugarDecorator extends CoffeeDecorator {
    
    
    public SugarDecorator(ICoffee coffee) {
    
    
        super(coffee);
    }
    @Override
    public void makeCoffee() {
    
    
        super.makeCoffee();
        addSugar();
    }
    private void addSugar(){
    
    
        System.out.print("加糖");
    } 
}

第五步:客户端使用

public static void main(String[] args) {
    
    
    //原味咖啡
    ICoffee coffee=new OriginalCoffee();
    coffee.makeCoffee();
    System.out.println("");

    //加奶的咖啡
    coffee=new MilkDecorator(coffee);
    coffee.makeCoffee();
    System.out.println("");

    //先加奶后加糖的咖啡
    coffee=new SugarDecorator(coffee);
    coffee.makeCoffee();
}

输出:

原味咖啡 
原味咖啡 加奶 
原味咖啡 加奶 加糖

可以从客户端调用代码看出,装饰者模式的精髓在于动态的给对象增减功能

当你你需要原味咖啡时那就生成原味咖啡的对象,而当你需要加奶咖啡时,仅仅需要将原味咖啡对象传递到加奶装饰者中去装饰一下就好了。如果你加了奶还想加糖,那就把加了奶的咖啡对象丢到加糖装饰者类中去装饰一下,一个先加奶后加糖的咖啡对象就出来了。

优缺点

优点

可以提供比继承更加灵活的方式去扩展对象的功能,通过排列组合,可以对某个类的一些对象做动态的功能扩展,而不需要装饰的对象却可以保持原样。

缺点

仍然是设计模式的通用缺点:类的个数会增加,会产生很多装饰者类,相应的就增加了复杂度。

装饰者模式与代理模式的区别

一般认为代理模式侧重于使用代理类增强被代理对象的访问,而装饰者模式侧重于使用装饰者类来对被装饰对象的功能进行增减。 除了上面的区别,个人实践中还发现,装饰者模式主要是提供一组装饰者类,然后形成一个装饰者栈,来动态的对某一个对象不断加强,而代理一般不会使用多级代理,详情请见秒懂Java代理与动态代理模式

组合模式

去超市购物,先把东西放到购物车里,可以拿入和拿出,最后再结账

public abstract class Component{
    
    
    private String name;
    public Component(String name){
    
    
        name=name;
    }
    protected abstract void display();//子类实现

}
public class Product extends Component{
    
    

    public Product(String name){
    
    
        super(name);
    }
    protected void display(){
    
    
        sout("name");
    }

}
public class Box extends Component{
    
    

    private List<Component> productList = new ArrayList<>();
    public Box(String name){
    
    
        super(name);
    }
    
    public void addProdcut(Component component){
    
    
        this.productList.add(component);
    }
    public void removeProdcut(Component component){
    
    
        this.productList.remove(component);
    }
    protected void display(){
    
    //结账
        for(Component component:productList){
    
    
            sout(component);
        }
    }

}

猜你喜欢

转载自blog.csdn.net/hancoder/article/details/111281056