资讯专栏INFORMATION COLUMN

23种设计模式

xiaokai / 1807人阅读

摘要:抽象工厂模式多个抽象产品类,个抽象产品类可以派生出多个具体产品类。用了工厂方法模式,你替换生成键盘的工厂方法,就可以把键盘从罗技换到微软。好处避免频繁创建对象,节省系统开销,减轻压力。

总体分为3大类:
创建型模式 (5种):工厂方法、抽象工厂、单例、建造者、原型
结构型模式(7种):适配器、装饰器、代理、外观、桥接、组合、享元
行为型模式(11种):策略、模板方法、观察者、迭代子、责任链、命令、备忘录、状态、访问者、中介者、解释器
其它(2种):并发型、线程池

设计模式的6大原则

开闭原则(总原则):对扩展开放,对修改关闭,需要使用接口和抽象类

单一职责:每个类应该实现单一的职责

里氏替换:基类可以出现的地方,子类一定可以出现。类似于向上转型,子类对父类的方法尽量不要重写和重载,会破坏父类定义好的与外界交互规范。是对开闭原则的补充,实现开闭原则的关键是抽象化,里氏替换原则是对实现抽象化规范

依赖倒转:面向接口编程,依赖于抽象,不依赖于具体。代码中:不与具体类交互,与接口交互。开闭原则的基础。

接口隔离:接口中的方法在子类中一定能用到,否则拆分成多个接口

迪米特法则(最少知道):对外暴露越少越好

合成复用:尽量使用合成/聚合的方式,而不是使用继承

创建型模式(5种):工厂方法、抽象工厂、单例、建造者、原型。 一、工厂方法模式


创建1个接口、2个实现类

public interface Sender {
    void send();
}
public class MailSender implements Sender {

    @Override
    public void send() {
        System.out.println("this is mailsender!");
    }
}
public class SmsSender implements Sender {

    @Override
    public void send() {
        System.out.println("this is smssender!");
    }
}

创建1个工厂接口、2个工厂实现类

public interface Provider {
    Sender produce();
}
public class SendMailFactory implements Provider {
    @Override
    public Sender produce() {
        return new MailSender();
    }
}
public class SendSmsFactory implements Provider {
    @Override
    public Sender produce() {
        return new SmsSender();
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        Provider provider = new SendMailFactory();
        Sender sender = provider.produce();
        sender.send();
    }
}

如果想增加一个功能,则只需做一个实现类,实现 Sender 接口,同时做一个工厂类,实现 Provider 接口,就 OK 了,无需去改动现成的代码。这样做,拓展性较好!

二、抽象工厂模式

工厂方法模式和抽象工厂模式的区别如下:
工厂方法模式:

1个抽象产品类可以派生出多个具体产品类。

1个抽象工厂类可以派生出多个具体工厂类。1个具体工厂类只能创建1个具体产品类的实例。

抽象工厂模式:

多个抽象产品类,1个抽象产品类可以派生出多个具体产品类。

1个抽象工厂类可以派生出多个具体工厂类。1个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。

对于 java 来说,你能见到的大部分抽象工厂模式都是这样的:
---它的里面是一堆工厂方法,每个工厂方法返回某种类型的对象。
比如说工厂可以生产鼠标和键盘。那么抽象工厂的实现类(它的某个具体子类)的对象都可以生产鼠标和键盘,但可能工厂 A 生产的是罗技的键盘和鼠标,工厂 B 是微软的。
用了工厂方法模式,你替换生成键盘的工厂方法,就可以把键盘从罗技换到微软。但是用了抽象工厂模式,你只要换家工厂,就可以同时替换鼠标和键盘一套。如果你要的产品有几十个,当然用抽象工厂模式一次替换全部最方便(这个工厂会替你用相应的工厂方法)所以说抽象工厂就像工厂,而工厂方法则像是工厂的一种产品生产线

三、单例模式(Singleton)

单例模式能保证在一个JVM中该对象只有一个实例存在。好处:
1、 避免频繁创建对象,节省系统开销,减轻 GC 压力。
2、在系统中某些对象只能有一个(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团)

简单的单例类(单线程):
只能在单线程中用,不能用于多线程。

public class Singleton {
    /* 持有私有静态实例,防止被引用,此处赋值为 null,目的是实现延迟加载 */
    private static Singleton instance = null;

    /* 私有构造方法,防止被实例化 */
    private Singleton() {
    }

    /* 静态工程方法,创建实例 */
    public static Singleton getInstance() {
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    public Object readResolve(){
        return instance;
    }
}

同步代码块:

/* 静态工程方法,创建实例 */
public static Singleton getInstance() {
    if (instance == null){
        synchronized (instance){
            if(instance == null){
                instance = new Singleton();
            }
        }
    }
    return instance;
}

在 Java 指令中创建对象和赋值操作是分开进行的,也就是说 instance = new Singleton();语句是分两步执行的,可能会先为Singleton实例分配空间,再赋值给instance,最后初始化Singleton实例。
A、B 两个线程为例:

A、B 线程同时进入了第一个 if 判断

A首先进入 synchronized 块,由于 instance 为 null,所以它执行 instance = new Singleton();

由于 JVM 内部的优化机制,JVM 先画出了一些分配给 Singleton 实例的空白内存,并赋值给 instance 成员(注意此时 JVM 没有开始初始化这个实例),然后 A 离开了 synchronized块。

B进入 synchronized 块,由于 instance 此时不是 null,因此它马上离开了 synchronized 块并将结果返回给调用该方法的程序。

此时 B 线程打算使用 Singleton 实例,却发现它没有被初始化,于是错误发生了。
上面这些话可以理解为A线程从同步代码块出来后,JVM没有初始化Singleton实例,B线程调用instance时发现Singleton没有初始化。

多线程单例
使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用 getInstance 的时候,JVM 能够帮我们保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。

public class Singleton {
    /* 私有构造方法,防止被实例化 */
    private Singleton() {
    }    
    /* 此处使用一个内部类来维护单例 */
    private static class SingletonFactory{
        private static Singleton instance = new Singleton();
    }    
    /* 获取实例 */
    public static Singleton getInstance(){
        return SingletonFactory.instance;
    } 
    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    public Object readResolve(){
        return getInstance();
    }
}

将创建和赋值分开,多带带为创建类加静态同步方法
因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,多带带为创建加 synchronized 关键字,也是可以的

public class SingletonTest {
    private static SingletonTest instance = null;
    public SingletonTest() {
    }
    private static synchronized void syncInit(){
        if(instance == null){
            instance = new SingletonTest();
        }
    }
    public static SingletonTest getInstance() {
        if (instance == null){
            syncInit();
        }
        return instance;
    }
}

补充:用 采用" 影子实例"的办法为单例对象的属性同步更新

public class SingletonTest {
    private static SingletonTest instance = null;
    private Vector properties = null;
    public Vector getProperties() {
        return properties;
    }
    public SingletonTest() {
    }
    private static synchronized void syncInit(){
        if(instance == null){
            instance = new SingletonTest();
        }
    }
    public static SingletonTest getInstance() {
        if (instance == null){
            syncInit();
        }
        return instance;
    }
    public void updateProperties(){
        SingletonTest shadow = new SingletonTest();
        properties = shadow.getProperties();
    }
}

类和静态方法与静态类区别:

静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有 static 修饰的方法,所以即使实现了也是非静态的)

单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。

单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是 static,无法被覆写。

四、建造者模式(Builder) 五、原型模式(Prototype)

将一个对象作为原型,对其进行复制、克隆后产生一个和原对象类似的新对象
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。
一个原型类,只需要实现 Cloneable 接口,覆写 clone 方法,此处 clone 方法可以改成任意的名称,因为 Cloneable 接口是个空接口,你可以任意定义实现类的方法名,如 cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在 Object 类中,clone()是 native 的。这里写一个深浅复制的例子

public class Prototype implements Cloneable, Serializable {

    private static final long serialVersionUID = 1L;
    private String string;
    private SerializableObject obj;

   /*浅复制*/
    public Object clone() throws CloneNotSupportedException{
        Prototype proto = (Prototype)super.clone();
        return proto;
    }

    /*深复制*/
    public Object deepClone() throws IOException, ClassNotFoundException {
        /* 写入当前对象的二进制流 */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        /* 读出二进制流产生的新对象 */
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();

    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }

    public SerializableObject getObj() {
        return obj;
    }

    public void setObj(SerializableObject obj) {
        this.obj = obj;
    }
}

class SerializableObject implements Serializable{
    private static final long serialVersionUID = 1L;
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71052.html

相关文章

  • Nacos系列:Nacos的三部署模式

    摘要:数据源内嵌的数据库,通过命令直接启动即可,无需额外安装。参考资料部署手册集群部署说明推荐阅读系列欢迎来到的世界系列基于的注册中心系列基于的配置中心系列的使用 三种部署模式 Nacos支持三种部署模式 1、单机模式:可用于测试和单机使用,生产环境切忌使用单机模式(满足不了高可用) 2、集群模式:可用于生产环境,确保高可用 3、多集群模式:可用于多数据中心场景 单机模式 启动 Nacos ...

    vibiu 评论0 收藏0
  • PHP完整实战23设计模式

    摘要:前言设计模式是面向对象的最佳实践实战实战创建型模式单例模式工厂模式抽象工厂模式原型模式建造者模式实战结构型模式桥接模式享元模式外观模式适配器模式装饰器模式组合模式代理模式过滤器模式实战行为型模式模板模式策略模式状态模式观察者模式责任链模式访 前言 设计模式是面向对象的最佳实践 实战 PHP实战创建型模式 单例模式 工厂模式 抽象工厂模式 原型模式 建造者模式 PHP实战结构型模式 ...

    _ang 评论0 收藏0
  • PHP面试常考之设计模式——策略模式

    摘要:策略模式介绍策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。使用策略模式的好处策略模式提供了管理相关的算法族的办法。使用策略模式可以避免使用多重条件转移语句。 你好,是我琉忆,PHP程序员面试笔试系列图书的作者。 本周(2019.3.11至3.15)的一三五更新的文章如下: 周一:PHP面试常考之设计模式——工...

    Drinkey 评论0 收藏0
  • Redux进阶系列2: 如何合理地设计Redux的State

    摘要:设计一个好的并非易事,本文先从设计时最容易犯的两个错误开始介绍,然后引出如何合理地设计。错误以为设计的依据以为设计的依据,往往是一个对应一个子,的结构同返回的数据结构保持一致或接近一致。至此,的结构设计完成。 Redux是一个非常流行的状态管理解决方案,Redux应用执行过程中的任何一个时刻,都是一个状态的反映。可以说,State 驱动了Redux逻辑的运转。设计一个好的State并非...

    刘明 评论0 收藏0
  • Java 23设计模式 之单例模式 7实现方式

    摘要:一懒汉式线程不安全懒汉式线程不安全私有构造方法只允许在内部进行实例的创建创建实例二懒汉式线程安全懒汉式线程安全私有构造方法只允许在内部进行实例的创建创建实例线程安全三饿汉式线程安全饿汉式私有构造方法只允许在内部进行实例的创建静态初始化由保证 一、懒汉式(线程不安全) package com.java.singleton; //懒汉式 线程不安全 public class LazySi...

    IntMain 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<