设计模式相关

设计模式的一些问题

Posted by ZBX on March 29, 2020

还待进一步完善

七大原则

  • 单一职责
  • 接口隔离
    • 最小接口
  • 依赖倒转
    • 抽象不应该依赖细节,细节应该依赖抽象,中心思想是面向接口编程
    • 三种实现方式:接口传递、构造方法传递、setter方式传递
  • 里氏替换
    • 所有引用基类的地方必须透明地使用其子类对象。
    • 在子类中尽量不要重写父类的方法
    • 继承让两个类的耦合性增强了,在适当情况下,可以通过聚合、组合、依赖来解决问题。
  • 开闭原则
    • 对扩展开发(对提供方),对修改关闭(对使用方)。软件需要改变时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  • 迪米特法则
    • 最少知道原则,一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管有多么复杂,都尽量将逻辑封装在类的内部,对外除了提供public方法,不对外泄露任何信息。
    • 只与直接的朋友通信。直接的朋友:出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部中的类不是直接的朋友。
  • 合成复用原则
    • 尽量使用合成/聚合的方式,而不是使用继承

UML类图

  • 依赖
    • 类中用到了对方,那么它们之间就存在依赖关系
  • 泛化
    • 继承关系,依赖关系的特例
  • 实现
    • A类实现B接口
  • 关联
    • 类与类之间的联系,具有单向和双向关系,具有多重性
    • 如单向一对一关系,双向一对一关系
  • 聚合
    • 整体和部分的关系,整体与部分可以分开。是关联关系的特例
  • 组合
    • 整体和部分的关系,整体与部分不可以分开。

      设计模式

      设计模式分为三种类型,共23种 1. 创建型模式:单例模式、抽象工厂、原型模式、建造者模式、工厂模式。 2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。 3. 行为模式:模板方法模式、命令模式、访问者模式、迭代模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局控制点.

public class InnerClassSingleton {
    /**
     * 基于类初始化
     */
    private static class InstanceHolder {
        public static InnerClassSingleton instance = new InnerClassSingleton();
    }
    public static InnerClassSingleton getInstance() {
        return InstanceHolder.instance;
    }
}
public class DoubleCheckSingleton {
    private static volatile DoubleCheckSingleton instance;
	
    private DoubleCheckSingleton() {}

    //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
    //同时保证了效率, 推荐使用
    public static DoubleCheckSingleton getInstance() {
        if(instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if(instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
            
        }
        return instance;
    }
}

 //使用枚举,可以实现单例, 推荐
public enum Singleton {
    INSTANCE; //属性
    public Singleton getInstance() {
        return Singleton.INSTANCE;
    }
}

工厂模式

针对每一种产品提供一个工厂类,通过不同的工厂实例来创建不同的产品实例

定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。

抽象工厂模式

应对产品族概念而生,与工厂模式相比,抽象工厂模式是为了应对产品族

提供一个接口,用于创建 相关的对象家族

原型模式

  • 用原型实例指定创建对象的种类,并通过拷贝这些原型,创建新的对象。
  • 允许一个对象创建另一个对象,无序知道创建的细节

  • 通过对象的序列化来实现对象的拷贝(推荐)。
class DeepCloneableTarget implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    private String cloneName;

    private String cloneClass;

    //构造器
    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    //因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class DeepProtoType implements Serializable, Cloneable{

    public String name; //String 属性
    public DeepCloneableTarget deepCloneableTarget;// 引用类型
    public DeepProtoType() {
        super();
    }


    //深拷贝 - 方式 1 使用clone 方法
    @Override
    protected Object clone() throws CloneNotSupportedException {

        Object deep = null;
        //这里完成对基本数据类型(属性)和String的克隆
        deep = super.clone();
        //对引用类型的属性,进行单独处理
        DeepProtoType deepProtoType = (DeepProtoType)deep;
        deepProtoType.deepCloneableTarget  = (DeepCloneableTarget)deepCloneableTarget.clone();

        // TODO Auto-generated method stub
        return deepProtoType;
    }

    //深拷贝 - 方式2 通过对象的序列化实现 (推荐)

    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); //当前这个对象以对象流的方式输出

//            oos = new ObjectOutputStream(new ByteArrayOutputStream());

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
//            ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            DeepProtoType copyObj = (DeepProtoType)ois.readObject();

            return copyObj;

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return null;
        } finally {
            //关闭流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e2) {
                // TODO: handle exception
                System.out.println(e2.getMessage());
            }
        }
    }
}

装饰者模式

动态的给一个对象添加一些额外的功能。

  • 动态地将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则

代理模式

封装被代理对象并限制外界对被代理对象的访问。

interface ITeacherDao {
    void teach(); // 授课的方法
    void sayHello(String name);
}
class TeacherDao implements ITeacherDao {

    @Override
    public void teach() {
        // TODO Auto-generated method stub
        System.out.println(" 老师授课中.... ");
    }
    @Override
    public void sayHello(String name) {
        // TODO Auto-generated method stub
        System.out.println("hello " + name);
    }
}
class ProxyFactory {
    //维护一个目标对象 , Object
    private Object target;
    //构造器 , 对target 进行初始化
    public ProxyFactory(Object target) {

        this.target = target;
    }

    //给目标对象 生成一个代理对象
    public Object getProxyInstance() {

        //说明
		/*
		 *  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

            //1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
            //2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
            //3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
		 */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    // TODO Auto-generated method stub
                    System.out.println("JDK代理开始~~");
                    //反射机制调用目标对象的方法
                    Object returnVal = method.invoke(target, args);
                    System.out.println("JDK代理提交");
                    return returnVal;
                });
    }
}
public class Client {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //创建目标对象
        ITeacherDao target = new TeacherDao();

        //给目标对象,创建代理对象, 可以转成 ITeacherDao
        ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();

        // proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
        System.out.println("proxyInstance=" + proxyInstance.getClass());

        //通过代理对象,调用目标对象的方法
        //proxyInstance.teach();

        proxyInstance.sayHello(" tom ");
    }
}

模板方法模式

定义一个操作的算法骨架, 并将一些步骤延迟到子类中。

该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。一般模板方法都加上final关键字,防止子类重写模板方法。

使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤 在实现时可能不同,通常考虑用模板方法模式来处理。

//抽象类,表示豆浆
abstract class SoyaMilk {

    //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
    final void make() {

        select();
        if(customerWantCondiments()) {
            addCondiments();
        }
        soak();
        beat();

    }

    //选材料
    void select() {
        System.out.println("第一步:选择好的新鲜黄豆  ");
    }

    //添加不同的配料, 抽象方法, 子类具体实现
    abstract void addCondiments();

    //浸泡
    void soak() {
        System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
    }

    void beat() {
        System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");
    }

    //钩子方法,决定是否需要添加配料
    boolean customerWantCondiments() {
        return true;
    }
}
class RedBeanSoyaMilk extends SoyaMilk {

    @Override
    void addCondiments() {
        // TODO Auto-generated method stub
        System.out.println(" 加入上好的红豆 ");
    }

}
class PureSoyaMilk extends SoyaMilk{

    @Override
    void addCondiments() {
        // TODO Auto-generated method stub
        //空实现
    }

    @Override
    boolean customerWantCondiments() {
        // TODO Auto-generated method stub
        return false;
    }

}
class PeanutSoyaMilk extends SoyaMilk {

    @Override
    void addCondiments() {
        // TODO Auto-generated method stub
        System.out.println(" 加入上好的花生 ");
    }

}
public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //制作红豆豆浆

        System.out.println("----制作红豆豆浆----");
        SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        System.out.println("----制作花生豆浆----");
        SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
        peanutSoyaMilk.make();

        System.out.println("----制作纯豆浆----");
        SoyaMilk pureSoyaMilk = new PureSoyaMilk();
        pureSoyaMilk.make();
    }

}

外观模式

是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

适配器模式

将一个类的接口转换成客户希望的另一个接口表示,目的是兼容性,别名为包装器(Wrapper)。

主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。

//适配器类
public class VoltageAdapter  implements IVoltage5V {

    private Voltage220V voltage220V; // 关联关系-聚合

    //通过构造器,传入一个 Voltage220V 实例
    public VoltageAdapter(Voltage220V voltage220v) {
        this.voltage220V = voltage220v;
    }

    @Override
    public int output5V() {

        int dst = 0;
        if(null != voltage220V) {
            int src = voltage220V.output220V();//获取220V 电压
            System.out.println("使用对象适配器,进行适配~~");
            dst = src / 44;
            System.out.println("适配完成,输出的电压为=" + dst);
        }
        return dst;
    }

}
//适配接口
interface IVoltage5V {
    public int output5V();
}

//被适配的类
class Voltage220V {
    //输出220V的电压,不变
    public int output220V() {
        int src = 220;
        System.out.println("电压=" + src + "伏");
        return src;
    }
}
class Phone {

    //充电
    public void charging(IVoltage5V iVoltage5V) {
        if(iVoltage5V.output5V() == 5) {
            System.out.println("电压为5V, 可以充电~~");
        } else if (iVoltage5V.output5V() > 5) {
            System.out.println("电压大于5V, 不能充电~~");
        }
    }
}

桥接模式

将抽象部分与实现部分分离,使它们都可以独立的变化

建造者模式

将一个复杂对象的构建与它的表示分离.

观察者模式

定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象,在它的状态发生变化时,会通知所有的观察者.

享元模式

命令模式

命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间调用关系更加灵活,实现解耦。在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求,同时命令模式也支持可撤销的操作。

  • Invoker 发送 Command ,Receiver接受并根据ConcreteCommand调用方法执行。
interface Command {
    public void execute();
    public void undo();
}
public class NoCommand implements Command {

    @Override
    public void execute() {
        // TODO Auto-generated method stub

    }

    @Override
    public void undo() {
        // TODO Auto-generated method stub

    }

}
public class LightReceiver {

    public void on() {
        System.out.println(" 电灯打开了.. ");
    }

    public void off() {
        System.out.println(" 电灯关闭了.. ");
    }
}
class LightOffCommand implements Command {

    // 聚合LightReceiver
    LightReceiver light;

    // 构造器
    public LightOffCommand(LightReceiver light) {
        super();
        this.light = light;
    }

    @Override
    public void execute() {
        // TODO Auto-generated method stub
        // 调用接收者的方法
        light.off();
    }

    @Override
    public void undo() {
        // TODO Auto-generated method stub
        // 调用接收者的方法
        light.on();
    }
}
class LightOnCommand implements Command {

    //聚合LightReceiver
    LightReceiver light;

    //构造器
    public LightOnCommand(LightReceiver light) {
        super();
        this.light = light;
    }

    @Override
    public void execute() {
        // TODO Auto-generated method stub
        //调用接收者的方法
        light.on();
    }

    @Override
    public void undo() {
        // TODO Auto-generated method stub
        //调用接收者的方法
        light.off();
    }
}
public class RemoteController {

    // 开 按钮的命令数组
    Command[] onCommands;
    Command[] offCommands;

    // 执行撤销的命令
    Command undoCommand;

    // 构造器,完成对按钮初始化

    public RemoteController() {

        onCommands = new Command[5];
        offCommands = new Command[5];

        for (int i = 0; i < 5; i++) {
            onCommands[i] = new NoCommand();
            offCommands[i] = new NoCommand();
        }
    }

    // 给我们的按钮设置你需要的命令
    public void setCommand(int no, Command onCommand, Command offCommand) {
        onCommands[no] = onCommand;
        offCommands[no] = offCommand;
    }

    // 按下开按钮
    public void onButtonWasPushed(int no) { // no 0
        // 找到你按下的开的按钮, 并调用对应方法
        onCommands[no].execute();
        // 记录这次的操作,用于撤销
        undoCommand = onCommands[no];

    }

    // 按下开按钮
    public void offButtonWasPushed(int no) { // no 0
        // 找到你按下的关的按钮, 并调用对应方法
        offCommands[no].execute();
        // 记录这次的操作,用于撤销
        undoCommand = offCommands[no];

    }

    // 按下撤销按钮
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
}

解释器模式

  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一棵抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器。
  • 指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)

状态模式

  • 主要用来解决在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以转换
  • 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。

策略模式

定义个策略接口,不同的实现类提供不同的具体策略算法, 同时它们之间可以互相替换。

  • 定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
  • 体现了:第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合、聚合,少用继承。
  • JDK中Arrays的Comparator使用了策略模式

简单工厂模式和策略模式的区别

  • 策略模式
    • 定义个策略接口,不同的实现类提供不同的具体策略算法, 同时它们之间可以互相替换.
  • 简单工厂模式
    • 定义一个用以创建对象的工厂, 根据不同的条件生成不同的对象
  • 简单工厂模式是根据给定的条件返回相应的对象,而策略模式是将不同的策略对象传递给使用者以实现不同策略。
  • 在简单工厂模式中实现了通过条件选取一个类去实例化对象,策略模式则将选取相应对象的工作交给模式的使用者,它本身不去做选取工作。