常用设计模式整理

197 阅读21分钟

前言

本次总结模式有: 单例模式、工厂模式、代理模式、策略模式、原型模式、委派模式、模板模式、观察者模式、装饰器模式。

单例模式

使用前提

单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。

  • 整个项目需要一个共享访问点或共享数据。
  • 创建一个对象需要消耗的资源过多,比如访问I/O、数据库等资源。
  • 有状态的工具类对象。
  • 需要频繁实例化,销毁的对象 例如,创建一个对象需要消耗过多资源,如要访问IO、数据库等资源时就需要考虑使用单例模式

缺点

  1. 单例对继承、多态特性的支持不友好。从理论上来讲,单例类也可以被继承、也可以实现多态,只是实现起来会非常奇怪,导致代码的可读性变差。所以,一旦选择将某个类设计成到单例类,也就意味着放弃了继承和多态这两个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性。
  2. 单例违背了基于接口而非实现的设计原则,也就是违背了广义上理解的 OOP 的抽象特性。
  3. 单例不支持有参数的构造函数单例不支持有参数的构造函数,比如创建一个连接池的单例对象,没法通过参数来指定连接池的大小。解决方式是,将参数配置化。在单例实例化时,从外部读取参数。

定义

确保一个类只有一个实例,并且提供一个全局访问点。 图中Client为客户端,Singletion为单例类,通过调用Singleton.getInstance()来获取实例对象。

单例模式的使用

  • 构造函数不对外开放,一般为Private;
  • 通过一个静态方法或者枚举返回单例类对象;
  • 确保单例类的对象有且只有一个,尤其是在多线程环境下;
  • 确保单例类对象在反序列化时不会重新构建对象。

写法1:饿汉模式

这种方式在类加载时就完成了初始化,所以类加载较慢,但是获取对象的速度快。这种方式基于类加载机制,避免了多线程的同步问题。如果从来没有使用过这个实例,则会造成内存的浪费。

public class HungryDemo {
    private static HungryDemo mInstance = new HungryDemo();
    // 构造函数
    private HungryDemo(){
        System.out.println("创建了饿汉对象");
    }
    // 共有静态函数,对外暴露获取单例对象的接口
    public static HungryDemo getInstance(){
        return mInstance;
    }
}

写法2:懒汉模式

线程不安全写法

懒汉模式声明一个静态对象,在用户第一次调用时初始化。

public class IdlerDemo {

    private static IdlerDemo mInstance;
    private IdlerDemo() {
        System.out.println("创建实例");
    }
    public static IdlerDemo getInstance() {
        // 在第一次调用时再创建实例
        if (null == mInstance) {
            mInstance = new IdlerDemo();
        }
        return mInstance;
    }

}

注意:这种懒汉模式虽然节约了资源,到那时第一次加载时需要实例化,反应稍慢些。并且在多线程的时候不能保证唯一性。

线程安全写法
public static synchronized IdlerDemo getInstanceSyn() {
        // 在第一次调用时再创建实例
        if (null == mInstance) {
            System.out.println("加锁调用");
            mInstance = new IdlerDemo();
        }
        return mInstance;
    }

我们可以发现在getInstance方法中添加了synchronized关键字,即getInstance方法是一个同步方法,可以在多线程情况下保证单例对象的唯一性。而每次调用getInstance方法都会进行同步造成不必要的同步开销,而我们大部分时候是用不到同步的。所以这种模式一般不建议使用。

写法3:双重检查模式(DCL)

Double Check Lock(DCL) 方式实现单例模式的优点是既能够在需要时才实例化,又能够保证线程的安全,且单例对象初始化后调用getInstance不进行同步锁。

// DCL模式创建对象
    public static IdlerDemo getInstanceDcl() {
        if (null == mInstance) {
            synchronized (IdlerDemo.class) {
                if (null == mInstance) {
                    mInstance = new IdlerDemo();
                }
            }
        }
        return mInstance;
    }

这种写法,通过第一次判空,避免掉不必要的锁住线程,如果同一时间两线程都通过了第一次判空,则会进入锁争夺,第一个争夺成功的线程会创建实例,而第二次争夺成功的线程会判断到非空而不重复创建

因为在某些情况下会出现失效问题即DCL失效问题,可以使用volatile关键字处理这个问题。使用volatile或多或少的会影响到性能,但是考虑到程序的正确性,牺牲这点性能还是值得的。

DCL的优点:资源利用效率高,第一次执行getInstance时对象才被实例化,效率高。 DCL的缺点:第一次加载时反应稍慢,在高并发环境下也有一定的缺陷。

写法4:静态内部类的单例模式

public class StaticClassDemo {

    private StaticClassDemo() {
        System.out.println("静态内部类创建实例对象");
    }

    public static StaticClassDemo getInstance() {
        return SingletonHoler.sInstance;
    }

    private static class SingletonHoler {
        private static final StaticClassDemo sInstance = new StaticClassDemo();
    }
}

Java静态内部类的特性是,加载的时候不会加载内部静态类,使用的时候才会进行加载。第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder并初始化sInstance,且sInstance为属性直接实例化,这样不仅能确保线程安全,也能保证Singleton类的唯一性。所以,推荐使用静态内部类单例模式

写法5:枚举单例模式

最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum IdGeneratorEnum {
        INSTANCE;
        private AtomicLong id = new AtomicLong(0);
        public long getId() {
            return id.incrementAndGet();
        }
    }

优点:

  • 写法简洁,代码短小精悍。
  • 线程安全。
  • 防止反序列化和反射的破坏。
  1. 防止反序列化Java的序列化专门对枚举的序列化做了规定,在序列化时,只是将枚举对象的name属性输出到结果中,在反序列化时通过java.lang.EnumvalueOf方法根据名字查找对象,而不是新建一个新的对象,所以防止了反序列化对单例的破坏。
  2. 防止反射的破坏:对于反射,枚举类同样有防御措施,反射在通过newInstance创建对象时会检查这个类是否是枚举类,如果是枚举类就会throw new IllegalArgumentException("Cannot reflectively create enum objects");

参考文章:
juejin.cn/post/684490… blog.csdn.net/weixin_3658…

工厂模式

简单工厂模式

简单工厂模式,又称:静态工厂方法模式,在简单工厂模式中,工厂有静态方法,可以根据参数的不同返回不同类的产品实例,而返回的实例对象都实现同一个产品接口,产品接口下写所有产品共同需要实现的方法。

产品接口,以及各个具体产品类的具体实现:

interface EasyProductDemo {

    // 获取电脑
    String getComputer();

}

class HpComputer implements EasyProductDemo {

    HpComputer() {
        System.out.println("实例化一个惠普电脑");
    }

    public String getComputer() {

        return "惠普电脑";
    }
}

class HuaWeiComputer implements EasyProductDemo {

    HuaWeiComputer() {
        System.out.println("实例化一个华为电脑");
    }

    public String getComputer() {

        return "华为电脑";
    }
}

静态方法工厂类:

public class EasyFactoryDemo {

    public static EasyProductDemo getComputerFactory(String name) {

        if (name == null) {
            return null;
        }
        switch (name) {
            case "HP":
                return new HpComputer();
            case "HUAWEI":
                return new HuaWeiComputer();
            default:
                return null;
        }

    }

}

使用该工厂的方法:

    @Test
    public void EasyFactoryTest() {

        // 实例化一个hp电脑对象
        EasyProductDemo hp = EasyFactoryDemo.getComputerFactory("HP");
        // 实例化一个华为电脑对象
        EasyProductDemo hw = EasyFactoryDemo.getComputerFactory("HUAWEI");

        System.out.println("得到"+hp.getComputer());
        System.out.println("得到"+hw.getComputer());

    }

优点:

  1. 隐藏了每个方法具体的实现,开发人员使用的时候无需关心其具体实现,只需知道如何使用

  2. 创建实例的时候,无需new,不用关心对象是如何new出来的

  3. 由于代码中无new,方便进行解耦 缺点:

  4. 扩展麻烦,需要改动现有代码。如果想要增加一个新的产品类,就需要改动工厂类代码。

  5. 所有逻辑全放在工厂类,产品一多将变得异常难阅读。

普通工厂模式

工厂方法模式(Factory Method)是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说每个对象都有一个与之对应的工厂。

工厂类:

public interface CommonFactoryDemo {

    // 获取产品类
    CommonProductDemo getProductFactory();
}

各个产品工厂的类

public interface CommonProductDemo {
    // 获取产品
    String getProduct();
}

/**
 * 可乐工厂类,实现工厂类
 */
class KLFactoryDemo implements CommonFactoryDemo {

    @Override
    public CommonProductDemo getProductFactory() {
        return new KL();
    }
}

/**
 * 雪碧工厂类,实现工厂类
 */
class XBFactoryDemo implements CommonFactoryDemo {

    @Override
    public CommonProductDemo getProductFactory() {
        return new XB();
    }
}


/**
 * 可乐类,实现产品类
 */
class KL implements CommonProductDemo {

    KL() {
        System.out.println("实例化可乐");
    }


    @Override
    public String getProduct() {
        return "可乐";
    }
}

/**
 * 雪碧类,实现产品类
 */
class XB implements CommonProductDemo {

    XB() {
        System.out.println("实例化雪碧");
    }

    @Override
    public String getProduct() {
        return "雪碧";
    }
}

优点:

  1. 相对于简单工厂模式,保留了简单工厂模式的优点

  2. 由于普通工厂模式给每一个产品对应了一个产品的工厂类,所以新增产品时,不需要改动现有的代码,只需实现对应的 产品类、工厂实现类 缺点:

  3. 由于给每个产品增加了产品的生产工厂,增加了系统负担。

  4. 当新增产品较多时,会导致代码量剧增。

  5. 一个工厂只能生产一个产品。

抽象工厂模式

观察普通工厂模式可以发现,每个工厂对应了一个产品,对于多种类的产品只能扩展工厂去一一实现,而抽象工厂模式中,每一个工厂对应了多种产品,不同的种类的工厂内都有对应的一套不同种类的产品,可以去对自己种类下的不同商品进行自己的实现

优点:

  1. 代码解耦
  2. 实现多个产品族(相关联产品组成的家族),而工厂方法模式的单个产品,可以满足更多的生产需求
  3. 很好的满足OCP开放封闭原则
  4. 抽象工厂模式中我们可以定义实现不止一个接口,一个工厂也可以生成不止一个产品类 对于复杂对象的生产相当灵活易扩展
  5. 原普通工厂模式在增加产品时需要增加一个工厂,抽象工厂模式只有在增加一个工厂品类的情况下才需要去增加一个工厂

缺点:

  1. 扩展产品族相当麻烦 而且扩展产品族会违反OCP,因为每个工厂都必须有所有的产品,在增加产品族时,每个工厂都要新增该产品实现
  2. 由于抽象工厂模式是工厂方法模式的扩展,总体的来说,很笨重

每一个不同类型的产品有一个自己的接口

/*
 * 抽象产品接口之主板
 */
public interface AbstractMainboardProductDemo {

    void productionMainboard();
}
/*
 * 抽象产品接口之CPU
 */
public interface AbstractCPUProductDemo {

    void productionCPU();

}

统一的工厂接口:

public interface AbstractFactoryDemo {

    // 获取CPU产品
    AbstractCPUProductDemo getCPUProduct();

    // 获取主板产品
    AbstractMainboardProductDemo getMainboardProduct();
}

各个工厂实现工厂接口:

/*
 * 抽象AMD工厂
 */
public class AbstractAMDFactoryDemo implements AbstractFactoryDemo {


    @Override
    public AbstractCPUProductDemo getCPUProduct() {
        return new AbstractAMDCPU();
    }

    @Override
    public AbstractMainboardProductDemo getMainboardProduct() {
        return new AbstractAMDMainboard();
    }
}
/*
 * Inter抽象工厂
 */
public class AbstractInterFactoryDemo implements AbstractFactoryDemo {
    @Override
    public AbstractCPUProductDemo getCPUProduct() {
        return new AbstractInterCPU();
    }

    @Override
    public AbstractMainboardProductDemo getMainboardProduct() {
        return new AbstractInterMainboard();
    }
}

各个工厂产品类的实现:

public class AbstractAMDCPU implements AbstractCPUProductDemo {

    @Override
    public void productionCPU() {
        System.out.println("生产AMD的CPU");
    }
}
public class AbstractAMDMainboard implements AbstractMainboardProductDemo {
    @Override
    public void productionMainboard() {
        System.out.println("生产AMD主板");
    }
}
public class AbstractInterCPU implements AbstractCPUProductDemo {

    @Override
    public void productionCPU() {
        System.out.println("生产Inter的CPU");
    }
}
public class AbstractInterMainboard implements AbstractMainboardProductDemo {
    @Override
    public void productionMainboard() {
        System.out.println("生产Inter主板");
    }
}

参考文章:

juejin.cn/post/684490…

juejin.cn/post/684490…

代理模式

定义

  1. 在一些情况下对象不适合或者不能直接访问调用一个方法,就需要代理类来实现该功能
  2. 代理类可以在原代理实现方法的基础上添加额外实现,达到扩展一些功能的目的

静态代理

静态代理需要包含三个部分:

  1. 代理类与被代理类共同实现的接口对象
  2. 实现接口的代理类
  3. 实现接口的被代理类

接口对象:

public interface StaticInterface {

    // 要做的一些事情
    void doSomeThing();
}

代理类:

public class StaticProxy implements StaticInterface  {

    private StaticInterface staticInterface;

    StaticProxy(StaticInterface staticInterface) {
        this.staticInterface=staticInterface;
    }

    @Override
    public void doSomeThing() {

        System.out.println("可额外拓展");
        staticInterface.doSomeThing();

    }


}

被代理类

public class StaticUser implements StaticInterface {
    @Override
    public void doSomeThing() {

        System.out.println("目标方法");

    }
}

测试方法:

    @Test
    public void StaticTest(){

        // 用户对象
        StaticUser staticUser=new StaticUser();
        // 代理对象
        StaticProxy staticProxy=new StaticProxy(staticUser);
        // 代理对象实现目标方法
        staticProxy.doSomeThing();

    }

优缺点

  1. 静态代理实现简单且不侵入原代码
  2. 当需要代理多个类时,代理对象要实现与目标对象一致的接口。要么,只维护一个代理类来实现多个接口,但这样会导致代理类过于庞大。要么,新建多个代理类,但这样会产生过多的代理类。
  3. 当接口需要增加、删除、修改方法时,目标对象与代理类都要同时修改,不易维护。

动态代理

实现动态代理通常有两种方式:JDK原生动态代理和CGLIB动态代理。

JDK原生动态代理

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler InvocationHandler接口定义了如下方法:

/**
 * 调用处理程序
 */
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object[] args); 
}

java原生的动态代理需要3部分:

  1. 被代理类实现的接口类
  2. 被代理类
  3. 实现InvocationHandler接口的代理类

接口类:

public interface JdkDynamicInterface  {

    void userSpeak();
}

被代理类:

public class JdkDynamicUser implements JdkDynamicInterface {

    @Override
    public void userSpeak() {
        System.out.println("被代理对象要执行的方法");
    }
}

代理类:

public class JdkDynamic  implements InvocationHandler {
    //被代理对象
    Object user;

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

    public JdkDynamic(Object user) {
        this.user = user;
    }

    // 调用invoke方法之前执行
    private void before() {
        System.out.println(String.format("方法之前执行,时间:[%s] ", new Date()));
    }

    // 调用invoke方法之后执行
    private void after() {
        System.out.println(String.format("方法之后执行,时间:[%s] ", new Date()));
    }
}

测试方法:

@Test
    public void jDKDynamicTest() {

        // 创建中介类实例
        JdkDynamic jdkDynamic = new JdkDynamic(new JdkDynamicUser());

        // 获取代理类实例JdkDynamicInterface
        JdkDynamicInterface jdkDynamicUser = (JdkDynamicInterface) (Proxy.newProxyInstance(JdkDynamicInterface.class.getClassLoader(), new Class[]{JdkDynamicInterface.class}, jdkDynamic));

        // 通过代理类对象调用代理类方法,实际上会转到invoke方法调用
        jdkDynamicUser.userSpeak();
    }

通过这种源生jdk动态代理可以给被代理类的指定方法前后加入操作

CGLib动态代理

原理是对指定的目标生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

注意:jdk的动态代理只可以为接口去完成操作,而cglib它可以为没有实现接口的类去做代理,也可以为实现接口的类去做代理。

被代理类:

public class CglibUser  {

    public void userSpeak() {
        System.out.println("被代理类的方法");
    }

}

代理类:

  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        // 注意这里是调用invokeSuper而不是invoke,否则死循环;
        // methodProxy.invokeSuper执行的是原始类的方法;
        // method.invoke执行的是子类的方法;
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println(method.getName()+"方法执行后的包装");
        return result;

    }

结果类:

 @Test
    public void cglibTest() {

        // 通过CGLIB动态代理获取代理对象的过程
        // 创建Enhancer对象,类似于JDK动态代理的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置目标类的字节码文件
        enhancer.setSuperclass(CglibUser.class);
        // 设置回调函数
        enhancer.setCallback(new CglibDemo());
        // create方法正式创建代理类
        CglibUser cglibUser = (CglibUser) enhancer.create();
        // 调用代理类的具体业务方法
        cglibUser.userSpeak();

    }

可以看出,该方法被代理类无需实现接口,开发较为高效

JDK动态代理与CGLIB对比

  1. JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象。
  2. CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
  • JDK Proxy的优势: 最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。
  • CGLIB的优势: 无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。

参考文章:

mp.weixin.qq.com/s/fhhHhepVD…

juejin.cn/post/684490…

策略模式

定义

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

主要解决在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

使用:一个系统有许多许多类,这些类都实现同一接口,而区分它们的只是他们直接的行为。

实现

统一实现的接口类:

public interface StrategyInterface {

    String getName();
}

各实现类:

public class StrategyOne implements StrategyInterface {
    @Override
    public String getName() {
        return "One";
    }
}
public class StrategyTwo implements StrategyInterface {
    @Override
    public String getName() {
        return "TWO";
    }
}

类似于静态代理模式的代理类,根据传入的不同类执行不同的实现方法:

public class StrategyDemo {
    private StrategyInterface strategyInterface;

    public StrategyDemo(StrategyInterface strategyInterface) {

        this.strategyInterface = strategyInterface;

    }

    public String getNameWithThey() {

        return strategyInterface.getName();

    }

}

优缺点

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

使用场景:

1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

2、一个系统需要动态地在几种算法中选择一种。

3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

4、推荐如果只有固定的几个判断,无需变化,直接使用if-else,如果需要经常扩展,可以使用策略模式

参考文章:

www.runoob.com/design-patt…

原型模式

定义

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()

浅克隆

浅克隆重写:

public class ShallowClone implements Cloneable {

    String name;
    ShallowAge age;

    public ShallowAge getAge() {
        return age;
    }

    public void setAge(ShallowAge age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Object clone()  {

        Object o=null;

        try {
            o=super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return o;
    }

}

Age类:

public class ShallowAge {
    Integer age;

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

执行类:

       @Test
    public void ShallowTest(){

        ShallowAge age=new ShallowAge();
        ShallowClone shallowClone=new ShallowClone();
        age.setAge(10);
        shallowClone.setAge(age);

        ShallowClone clone = (ShallowClone) shallowClone.clone();

        age.setAge(20);
        System.out.println(clone.getAge().getAge());
        
    }

1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象; 2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值; 3)浅拷贝是使用默认的 clone()方法来实现

深克隆

1)复制对象的所有基本数据类型的成员变量值;

2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝;

3)深拷贝实现方式:通过对象序列化实现深拷贝

深克隆学生类(序列化):

public class DeepStuden implements Serializable {
    public static final long serialVersionUID = 456465456L;
    private String name;
    private int age;
    private  String gender;
    public DeepStuden() {
    }
    public DeepStuden(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "DeepStuden{" + 
            "name='" + name + '\'' +
            ", age=" + age +
            ", gender='" + gender + '\'' +
            '}';
    }
}

执行类:

 @Test
    public void DeepTest(){

        DeepStuden student = new DeepStuden("张三", 21, "男");

//        序列化
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            oos.writeObject(student);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null)
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }

//        反序列化
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));
            DeepStuden str = (DeepStuden)ois.readObject();
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

特点

优点: 1、性能提高。 2、逃避构造函数的约束。

缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。

使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

参考文章:

juejin.cn/post/687622…

www.runoob.com/design-patt…

委派模式

委派模式有点像代理模式又有点像策略模式。

委派模式就是静态代理和策略模式的一种特殊组合,代理模式注重的是过程,委派模式注重的是结果。策略模式注重的是可扩展(外部扩展),委派模式注重的是内部的灵活和复用。

具体实现

总任务的接口方法

public interface TrusteeInterfaceDemo {

    void doSomething(String work);
}

所有员工的集合类,一样实现接口

public class EveryWorker implements TrusteeInterfaceDemo {

    private Map<String, TrusteeInterfaceDemo> targets = new HashMap<>();

    /**
     * 项目经理持有小组成员可供选择,类似策略模式
     */
    public EveryWorker() {
        targets.put("任务1", new WorkerOne());
        targets.put("任务2", new WorkerTwo());
    }

    @Override
    public void doSomething(String work) {
        targets.get(work).doSomething(work);
    }

}

一号实现:

public class WorkerOne implements TrusteeInterfaceDemo {

    @Override
    public void doSomething(String work) {
        System.out.println("员工1完成工作"+work);
    }
}

二号实现:

public class WorkerTwo implements TrusteeInterfaceDemo {
    @Override
    public void doSomething(String work) {
        System.out.println("员工2完成工作"+work);
    }
}

参考文章:

blog.csdn.net/caoyue_new/…

模板模式

优缺点

优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

具体实现

抽象模板类:

public abstract class TemplateAbstractDemo {


    public final void play() {

        open();

        comeOn();

        end();
    }

    abstract void open();

    abstract void comeOn();

    abstract void end();

}

实现模板的1号类:

public class TemplateOneDemo extends TemplateAbstractDemo {
    @Override
    void open() {
        System.out.println("ONE打开游戏");
    }

    @Override
    void comeOn() {

        System.out.println("ONE进入游戏");
    }

    @Override
    void end() {

        System.out.println("ONE结束游戏");
    }
}

实现模板的2号类:

public class TemplateTwoDemo extends TemplateAbstractDemo {
    @Override
    void open() {
        System.out.println("TWO打开游戏");
    }

    @Override
    void comeOn() {
        System.out.println("TWO进入游戏");
    }

    @Override
    void end() {

        System.out.println("TWO结束游戏");
    }
}

执行类:

    @Test
    public void TemplateAbstractTest() {

        TemplateAbstractDemo tone=new TemplateOneDemo();

        TemplateAbstractDemo ttwo=new TemplateTwoDemo();

        tone.play();
        ttwo.play();
    }

参考文章:

www.runoob.com/design-patt…

观察者模式

意义

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

需要的对象:

  1. 被观察的目标类,继承于广播类,一旦发生改变,会调用广播类中的广播方法,告知观察者类们状态改变
  2. 广播类,该类中储存了观察者类,并且配有添加,删除,广播观察者的方法
  3. 观察者接口
  4. 实际观察者类,在使用的时候需要先在广播类中注册

具体实现

广播类:

public class ObserverSubject {

    // 保存注册的观察者对象
    private List<Observer> list = new ArrayList<Observer>();

    // 注册观察者对象
    public void attach(Observer observer) {
        list.add(observer);
        System.out.println("添加新的观察者");
    }

    // 删除观察者对象
    public void detach(Observer observer) {
        list.remove(observer);
        System.out.println("删除一个观察者");
    }

    // 通知所有注册的观察者对象
    public void nodifyObservers(String newState) {
        for (Observer observer : list) {
            observer.update(newState);
        }
    }

}

目标类:

public class ObserverConcreteSubject extends ObserverSubject {

    private String state;

    public String getState() {
        return state;
    }

    // 状态发生改变,通知各个观察者
    public void change(String newState) {
        state = newState;
        this.nodifyObservers(state);
    }
    
}

观察者接口:

public interface Observer {

    void update(String state);

}

观察者类:

public class ObserverConcrete implements Observer {

    private String observerState;

    @Override
    public void update(String state) {
        observerState = state;
        System.out.println("观察者状态跟新了:" + observerState);
    }

}

参考文件:

www.runoob.com/design-patt…

装饰器模式

意义

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类

如何解决:将具体功能职责划分,同时继承装饰者模式

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂。

使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。

具体实现

接口类:

public interface Shape {
    void draw();
}

实现类:

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("矩形实现类");
    }
}
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("圆形实现类");
    }
}

实现接口的装饰类:

public class ShapeDecorator implements Shape {

    protected Shape shape;

    public ShapeDecorator(Shape shape) {
        this.shape = shape;
    }

    @Override
    public void draw() {
        shape.draw();
        System.out.println("装饰类");
    }
}

继承装饰类的实现:

public class RedShapeDecorator extends ShapeDecorator {
    protected Shape decoratedShape;

    public RedShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
        this.decoratedShape = decoratedShape;

    }

    @Override
    public void draw() {
        decoratedShape.draw();
        setRedBorder(decoratedShape);
    }

    private void setRedBorder(Shape decoratedShape) {
        System.out.println("染上红色");
    }
}

执行方法:

    @Test
    public void DecoratorPatternTest() {

        Shape circle = new Circle();
        ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
        ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());

        System.out.println("画圆");
        circle.draw();

        System.out.println("\n红色圆");
        redCircle.draw();

        System.out.println("\n红色矩形");
        redRectangle.draw();
    }

参考文章:

www.runoob.com/design-patt…