设计模式(四)-正篇-原型模式、建造者模式、适配器模式

原型模式

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。

例子

有一只羊,现在需要我们再克隆属性完全相同的四只羊。

传统方法

使用四次new方法

//传统的方法
        Sheep sheep = new Sheep("tom", 1, "白色");
        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep5 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());

在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂,效率就会很低;总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活。

原型模式

Sheep类中继承cloneable,并实现clone()方法。

@Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }

浅拷贝与深拷贝

浅拷贝

原型模式中clone默认是浅拷贝,如果Sheep中有一个属性是对象,如下:

 此时使用clone()

 我们发现,这个对象属性并没有被复制一份,而是和之前的一样,如下图,hashCode一样

 且修改sheep2中friend的某个属性值,sheep中的也会同样改变

 那么也就是说,浅拷贝只是将该变量的内存地址(引用值)复制给一份新的对象,但实际上这两对象都指向同一个实例。所以修改值会互相影响。

深拷贝

有两种实现方法:

方法一(不推荐):

/**
     * 深拷贝 方式一,重写clone方法
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        deep = super.clone();

        //将对象属性也拷贝一份
        DeepProtoType deepProtoType = (DeepProtoType) deep;
        deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();

        return deepProtoType;
    }

方法二(推荐)

/**
     * 深拷贝 方式二 通过对象的序列化实现
     * @return
     */
    public Object deepClone() {
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            //当前这个对象以对象流的方式输出
            oos.writeObject(this);

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            DeepProtoType copy = (DeepProtoType) ois.readObject();
            return copy;
        } catch (IOException | ClassNotFoundException ioException) {
            ioException.printStackTrace();
            return null;
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

测试:

 说明确实是被复制了一份。深拷贝为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。

应用例子

Spring中原型Bean的创建中,如果xml配置的scope=“prototype”。那么在doGetBean方法中有一个if判断是否是原型模式,是就按原型模式创建,这样每次创建出来的Bean不是同一个,即使属性一样。

优点

1)创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时提高效率。

2)不用重新初始化对象,而是动态地获得对象运行的状态。

3)如果原始对象发生变化,比如增加属性,其他克隆对象也会发生相应的变化,不需要修改多余的代码。

4)实现深拷贝需要重写clone方法或者使用序列化和反序列化。

缺点

需要为每一个类配备一个克隆方法,对于已又有类进行改造时需要修改源代码,违反开闭原则。

建造者模式

定义

1)又叫生成器模式,是一种对象构建模式。可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以够造出不同表现的对象。

2)建造者是一步步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部实现细节。

四个角色

1)Product:产品角色,比如房子

2)Builder:抽象建造者,创建一个Product对象的各个部分指定的接口/抽象类

3)ConcreteBuilder:具体建造者,实现接口,构建和装配各个部件。

4)Director:指挥者,构建一个使用Builder接口的对象(聚合了Builder)。它主要用于创建一个复杂的对象。主要有两个作用,①隔离了客户与对象的生产过程。②负责控制产品对象的生产过程。

结构类图

 

例子

加入需要建一个房子,房子有buildBasic、buildWalls,roofed基本操作。房子也有高楼和普通房等类型。

传统方式

代码设计

定义抽象类AbstractHouse

public abstract class AbstractHouse {

    public abstract void buildBasic();

    public abstract void buildWalls();

    public abstract void roofed();

    public void build() {
        buildBasic();
        buildWalls();
        roofed();
    }
}

定义两个类ComonHouse和HighBuilding,均继承AbstractHouse

public class CommonHouse extends AbstractHouse{
    @Override
    public void buildBasic() {
        System.out.println("给普通房子打地基");
    }

    @Override
    public void buildWalls() {
        System.out.println("给普通房子砌墙");
    }

    @Override
    public void roofed() {
        System.out.println("给普通房子封墙");
    }
}
public class HighBuilding extends AbstractHouse{
    @Override
    public void buildBasic() {
        System.out.println("给高楼建筑打地基");
    }

    @Override
    public void buildWalls() {
        System.out.println("给高楼建筑砌墙");
    }

    @Override
    public void roofed() {
        System.out.println("给高楼建筑封墙");
    }
}

客户端调用测试

public class Client {
    public static void main(String[] args) {
        CommonHouse commonHouse = new CommonHouse();
        commonHouse.build();
    }
}

结构类图

 传统方式的问题:

没有涉及缓冲层,不易扩展和维护,把房子和创建房子的过程封装在一起,耦合性增强了。

建造者模式

代码

定义个House类,平平无奇(角色Product)

定义一个抽象类HouseBuilder(也可以用接口)(角色Builder)

public abstract class HouseBuilder {
    protected House house = new House();

    //定义各个流程的抽象方法
    public abstract void buildBasic();

    public abstract void buildWalls();

    public abstract void roofed();

    //建造房子
    public House buildHouse() {
        return house;
    }
}

定义两个类ComonHouse和HighBuilding,均继承HouseBuilder(ConcreteBuilder)

public class CommonHouse extends HouseBuilder{
    @Override
    public void buildBasic() {
        System.out.println("给普通房子打地基");
    }

    @Override
    public void buildWalls() {
        System.out.println("给普通房子砌墙");
    }

    @Override
    public void roofed() {
        System.out.println("给普通房子封墙");
    }
}
public class HighBuilding extends HouseBuilder{

    @Override
    public void buildBasic() {
        System.out.println("给高楼建筑打地基");
    }

    @Override
    public void buildWalls() {
        System.out.println("给高楼建筑砌墙");
    }

    @Override
    public void roofed() {
        System.out.println("给高楼建筑封墙");
    }
}

定义一个类HouseDirector(角色Director)

public class HouseDirector {

    HouseBuilder houseBuilder = null;

    public HouseDirector(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    public void setHouseBuilder(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    public House constructHouse() {
        houseBuilder.buildBasic();
        houseBuilder.buildWalls();
        houseBuilder.roofed();
        return houseBuilder.buildHouse();
    }
}

主要是由指挥者决定步骤,步骤的具体细节由不同建造者实现。把创建者(HouseBuilder)与创建产品的过程(constructHouse函数)分离来实现解耦。

结构类图

应用例子

jdk中的StringBuilder源码中涉及到建造者模式的思想,虽然不是完全一样,但是思想有相同的地方

Appendable接口

定义了多个append方法,充当抽象建造者的角色(Builder),定义了抽象方法

AbstractStringBuilder实现了Appendable接口方法,它其实已经是建造者了,完成了建造方法的实现,只是不能实例化。

 StringBuilder类,继承AbstractStringBuilder,即充当指挥者(Director),又充当具体建造者(ConcreteBuilder)。

注意事项

1)建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间差异性很大,则不适合用建造者模式

2)如果产品内部变化复杂,也要慎重考虑建造者模式,因为可能会导致需要定义很多具体建造者类来实现这种变化。

建造者模式vs抽象工厂模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合(比如披萨根据主要原料、地域风味),采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

适配器模式

定义

属于结构型模式,将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作,别名为包装器(Wrapper)。

主要有两类:类适配器、对象适配器、接口适配器模式。

比如一些插座只支持两孔,可以买多个多功能转换插头(也就是类比于适配器),这样可以供各种电器使用。

工作原理

1)将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容。

2)从用户的角度看不到被适配者,是解耦的

3)用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法。

4)用户收到反馈效果,感觉只是和目标接口交互。

 

类适配器模式

原理

Adapter类通过继承src类,实现dst类接口,完成src->dst的适配。

例子

220V交流电相当于src,充电器相当于适配器,而手机支持的5v直流电是dst。

结构类图

代码

定义被适配者(src) 

public class Voltage220V {
    public int output220V() {
        int src = 220;
        System.out.println("电压=" + src + "伏");
        return src;
    }
}

定义接口Voltage5V(dst)

public interface Voltage5V {
    public int output5V();
}

定义适配器(Adapter)

public class VoltageAdapter extends Voltage220V implements Voltage5V{

    @Override
    public int output5V() {
        int srcV = output220V();
        int dstV = srcV / 44;
        return dstV;
    }
}

注意事项

1)Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定的局限性。

2)src类的方法在Adapter中都会暴露出来,也增加使用的成本。

3)由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter灵活性增强了。

对象适配器模式

原理

基本思路与类适配器相同,只是修改Adapter类,不继承src类,而是持有src类的实例(聚合),以解决兼容性问题。(合成复用原则指出:在系统中尽量使用关联关系来替代继承关系,详情见设计原则

例子

结构类图

 

代码

其他一样,就是修改了Adapter

public class VoltageAdapter implements Voltage5V {

    private Voltage220V voltage220V;

    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public int output5V() {
        int dstV = 0;
        if (voltage220V != null) {
            int srcV = voltage220V.output220V();
            System.out.println("使用对象适配器");
            dstV = srcV / 44;
        }
        return dstV;
    }
}

注意事项

1)和类适配器思想相同,不过遵守合成复用原则,使用组合/聚合替代继承,解决了类适配器必须继承src从而导致dst只能是接口的局限性问题。

2)更灵活,耦合性更低。

接口适配器模式

原理

也称为缺省适配器模式

1)当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每一个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类得到某些方法来实现需求。(安卓开发中一些监听器相关源码函数经常使用到该模式,用什么监听函数就自己覆盖相应方法去实现)比如我们自己具体使用覆盖时如下格式:

new AmimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator amimator) {
        //具体实现
    }
}

2)适用于一个接口不想使用其所有方法的情况。

例子

结构类图

 

代码

定义一个接口,有好多方法,但不会使用其全部

public interface InterfaceExam {
    public void m1();

    public void m2();

    public void m3();

    public void m4();
}

定义一个抽象类AbsAdapter,实现接口,为该接口中每一个方法提供一个默认实现(空方法)

public abstract class AbsAdapter implements InterfaceExam{
    @Override
    public void m1() {

    }

    @Override
    public void m2() {

    }

    @Override
    public void m3() {

    }

    @Override
    public void m4() {

    }
}

使用

public class Client {
    public static void main(String[] args) {
        System.out.println();
        AbsAdapter absAdapter = new AbsAdapter() {
            //只需要覆盖我们需要使用的接口方法

            @Override
            public void m1() {
                System.out.println("使用了m1方法");
            }
        };
        absAdapter.m1();
    }
}

应用例子

SpringMvc中的HandlerAdapter,使用了适配器模式。

SpringMVC相关的源代码分析在这里讲感觉篇幅不够,可能以后会另开一篇,暂时省略,先挖个坑

TODO

注意事项

1)三种命名方式,是根据src是以怎样的形式给到Adapter(在Adapter)来命名的。

2)类适配器:以类给到,在Adapter里,就是将src当做父类继承。

     对象适配器:以对象给到,在Adapter里,将src作为一个对象持有(聚合)。

     接口适配器:以接口给到,在Adapter里,将src作为一个接口实现。

3)Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。

4)实际开发中,实现起来不拘泥于我们讲解的三种经典形式。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值