设计模式

184 阅读40分钟

谈谈对生成器模式的理解

生成器模式:将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。

  • 何时使用
    1. 当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式老构造这样的对象。
    2. 当某些系统要求对象的构造过程必须独立于创建该对象的类时。
  • 优点
    1. 生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
    2. 生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。
    3. 可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。
    4. 生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。
    5. 当增加新的具体的生成器是,不必修改指挥者的代码,即该模式满足开-闭原则。

模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。

比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。

首先是这个类(产品):

//产品
public class MyDate {
    String date;
}

然后就是抽象生成器,描述生成器的行为:

//抽象生成器
public interface IDateBuilder {
    IDateBuilder buildDate(int y,int m,int d);
    String date();
}

接下来是具体生成器,一个以“-”分割年月日,另一个使用空格:

/具体生成器
public class DateBuilder1 implements IDateBuilder{
    private MyDate myDate;
    public DateBuilder1(MyDate myDate){
        this.myDate = myDate;
    }
    @Override
    public IDateBuilder buildDate(int y, int m, int d) {
        myDate.date = y+"-"+m+"-"+d;
        return this;
    }
    @Override
    public String date() {
        return myDate.date;
    }
}
//具体生成器
public class DateBuilder2 implements IDateBuilder{
    private MyDate myDate;
    public DateBuilder2(MyDate myDate){
        this.myDate = myDate;
    }
    @Override
    public IDateBuilder buildDate(int y, int m, int d) {
        myDate.date = y+" "+m+" "+d;
        return this;
    }
    @Override
    public String date() {
        return myDate.date;
    }
}

接下来是指挥官,向用户提供具体的生成器:

//指挥者
public class Derector {
    private IDateBuilder builder;
    public Derector(IDateBuilder builder){
        this.builder = builder;
    }
    public String getDate(int y,int m,int d){
        builder.buildDate(y, m, d);
        return builder.date();
    }
}

最后使用

public class MainGenerate {
    public static void main(String args[]){
        MyDate date = new MyDate();
        IDateBuilder builder;
        builder = new DateBuilder1(date).buildDate(2066, 3, 5);
        System.out.println(builder.date());
        builder = new DateBuilder2(date).buildDate(2066, 3, 5);
        System.out.println(builder.date());
    }
}

使用不同生成器,可以使原有产品表现得有点不一样。

谈谈对责任链模式的理解

使很多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

何时使用:

  1. 有许多对象可以处理用户请求,希望程序在运行期间自动确定处理用户的那个对象。
  2. 希望用户不必明确指定接收者的情况下,想多个接受者的一个提交请求
  3. 程序希望动态的指定可处理用户请求的对象集合

优点:

  1. 低耦合
  2. 可以动态的添加删除处理者或重新指派处理者的职责
  3. 可以动态改变处理者之间的先后顺序

通常来说,一个纯粹的责任链是先传给第一个处理,如果处理过了,这个请求处理就此结束,如果没有处理,再传给下一个处理者。

比如我们有一个数学公式,有一个整数输入,要求小于0时返回绝对值,其次,小于10的时候返回他的二次幂,否则,返回他本身:

首先我们要定义一个接口(处理者),来描述他们共有的行为:

public interface Handler {
    int handleRequest(int n);
    void setNextHandler(Handler next);
}

然后是具体的处理者

public class Handler1 implements Handler {
    private Handler next;
    @Override
    public int handleRequest(int n) {
        if(n<0) return -n;
        else{
            if(next==null)
                throw new NullPointerException("next 不能为空");
            return next.handleRequest(n);
        }
    }
    
    @Override
    public void setNextHandler(Handler next) {
        this.next = next;
    }
}
public class Handler2 implements Handler {
    private Handler next;
    @Override
    public int handleRequest(int n) {
        if(n<10) return n*n;
        else{
            if(next==null)
                throw new NullPointerException("next 不能为空");
            return next.handleRequest(n);
        }
    }
    
    @Override
    public void setNextHandler(Handler next) {
        this.next = next;
    }
}
public class Handler3 implements Handler {
    private Handler next;
    @Override
    public int handleRequest(int n) {
        if(n<=Integer.MAX_VALUE) return n;
        else{
            if(next==null)
                throw new NullPointerException("next 不能为空");
            return next.handleRequest(n);
        }
    }
    
    @Override
    public void setNextHandler(Handler next) {
        this.next = next;
    }
}

最后使用

public class TestUse {
    public static void main(String args[]){
        Handler h1,h2,h3;
        h1 = new Handler1();
        h2 = new Handler2();
        h3 = new Handler3();
        h1.setNextHandler(h2);
        h2.setNextHandler(h3);
        System.out.println(h1.handleRequest(-1));
        System.out.println(h1.handleRequest(5));
        System.out.println(h1.handleRequest(9999));
    }
}

此处责任链中的具体处理者的顺序是不能重设的,否则可能会引发错误,但更多的情况是完全可以随意更改他们的位置的,就上例中,只要把if中的条件重新设置(各自独立,不相互依赖),就可以了。

我们使用责任链模式的时候,不一定非得某一处理者处理后就得终止请求的传递,如果有其他需求,我们依然可以继续传递这个请求到下一个具体的处理者。

From Taonce

责任链模式就是:转移责任,比如:董事需要做一件事,找到总监,总监找到部门组长,组长再找到组员,这些人就像在一条链子上,任务在这条链子上传递。 Android中用到责任链的地方有:点击事件的分发(Activity - ViewGroup - View),Okhttp中拦截器的使用

谈谈对抽象工厂方法模式的理解

抽象工厂模式,即Abstract Factory Pattern,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。

抽象工厂模式与工厂方法模式最大的区别:抽象工厂中每个工厂可以创建多种类的产品;而工厂方法每个工厂只能创建一类

  1. 主要作用

允许使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么,这样就可以从具体产品中被解耦。

  1. 解决的问题

每个工厂只能创建一类产品

即工厂方法模式的缺点

  • 背景:小成有两间塑料加工厂(A厂仅生产容器类产品;B厂仅生产模具类产品);随着客户需求的变化,A厂所在地的客户需要也模具类产品,B厂所在地的客户也需要容器类产品;
  • 冲突:没有资源(资金+租位)在当地分别开设多一家注塑分厂
  • 解决方案:在原有的两家塑料厂里增设生产需求的功能,即A厂能生产容器+模具产品;B厂间能生产模具+容器产品。

即抽象工厂模式

步骤1: 创建抽象工厂类,定义具体工厂的公共接口

abstract class Factory {
   public abstract Product ManufactureContainer();
   public abstract Product ManufactureMould();
}

步骤2: 创建抽象产品族类 ,定义具体产品的公共接口;

abstract class AbstractProduct {
    public abstract void Show();
}

步骤3: 创建抽象产品类 ,定义具体产品的公共接口;

//容器产品抽象类
abstract class ContainerProduct extends AbstractProduct {
    @Override
    public abstract void Show();
}

//模具产品抽象类
abstract class MouldProduct extends AbstractProduct {
    @Override
    public abstract void Show();
}

步骤4: 创建具体产品类(继承抽象产品类), 定义生产的具体产品;

//容器产品A类
class ContainerProductA extends ContainerProduct{
    @Override
    public void Show() {
        System.out.println("生产出了容器产品A");
    }
}

//容器产品B类
class ContainerProductB extends ContainerProduct{
    @Override
    public void Show() {
        System.out.println("生产出了容器产品B");
    }
}

//模具产品A类
class MouldProductA extends MouldProduct{

    @Override
    public void Show() {
        System.out.println("生产出了模具产品A");
    }
}

//模具产品B类
class MouldProductB extends MouldProduct{

    @Override
    public void Show() {
        System.out.println("生产出了模具产品B");
    }
}

步骤5:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;

//A厂 - 生产模具+容器产品
class FactoryA extends Factory {
    @Override
    public Product ManufactureContainer() {
        return new ContainerProductA();
    }

    @Override
    public Product ManufactureMould() {
        return new MouldProductA();
    }
}

//B厂 - 生产模具+容器产品
class FactoryB extends Factory {
    @Override
    public Product ManufactureContainer() {
        return new ContainerProductB();
    }

    @Override
    public Product ManufactureMould() {
        return new MouldProductB();
    }
}

步骤6:客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例

//生产工作流程
public class AbstractFactoryPattern {
    public static void main(String[] args){
        FactoryA mFactoryA = new FactoryA();
        FactoryB mFactoryB = new FactoryB();
        //A厂当地客户需要容器产品A
        mFactoryA.ManufactureContainer().Show();
        //A厂当地客户需要模具产品A
        mFactoryA.ManufactureMould().Show();

        //B厂当地客户需要容器产品B
        mFactoryB.ManufactureContainer().Show();
        //B厂当地客户需要模具产品B
        mFactoryB.ManufactureMould().Show();
    }
}

结果:

生产出了容器产品A
生产出了容器产品A
生产出了模具产品B
生产出了模具产品B

最后补充下三种工厂模式的区别

  • 简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。
  • 工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
  • 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。

谈谈对「简单工厂模式」和「工厂方法模式」的理解

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

  1. 何时使用
    1. 用户需要一个类的子类的实例,但不希望与该类的子类形成耦合
    2. 用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用
  2. 优点
    1. 使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦
    2. 工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法即可。
  • 简单工厂模式

    简单工厂模式又称静态工厂方法模式。它存在的目的很简单:定义一个用于创建对象的接口。

    如果一个一些对象(产品),已经确定了并不易改变和添加新的产品,那么就可以使用简单工厂模式

    interface Phone {
        void call();
    }
    
    class HwMobilePhone implement Phone {
        @Override
        public void call() {
            System.out.println("Hw make a call");
        }
    }
    
    class OnePlusMobilePhone implement Phone {
        @Override
        public void call() {
            System.out.println("OnePlus make a call");
        }
    }
    
    class Factory {
        Phone produce(String product) {
            if(product.equals("Hw"))
                return new HwMobilePhone();
            else if(product.equals("OnePlus"))
                return new OnePlusMobilePhone();
            else throw new IllegalArgumentException("No such product");
        }
    }
    
    public class SimpleFactoryPattern {
        public static void main(String args[]) throws Exception{
            Factory factory = new Factory();
            factory.produce("Hw").call();
            factory.produce("OnePlus").call();
        }
    }
    

    可以看出,简单工厂模式是不易维护的,如果需要添加新的产品,例如要增加一个 MiMobilePhone,则整个系统都需要修改。

  • 工厂方法模式

    工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

    interface Phone {
        void call();
    }
    
    class HwMobilePhone implement Phone {
        @Override
        public void call() {
            System.out.println("Hw make a call");
        }
    }
    
    class OnePlusMobilePhone implement Phone {
        @Override
        public void call() {
            System.out.println("OnePlus make a call");
        }
    }
    
    interface IFactory {
        Phone produce();
    }
    
    class HwFactory implement IFactory {
        @Override
        public Phone produce() {
            return new HwMobilePhone();
        }
    }
    
    class OnePlusFactory implement IFactory {
         @Override
        public Phone produce() {
            return new OnePlusMobilePhone();
        }
    }
    
    public class FactoryPattern {
        public static void main(String args[]) {
            IFactory factory;
            factory = new HwFactory();
            factory.produce().call();
            factory = new OnePlusFactory();
            factory.produce().call();
        }
    }
    

工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。 反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了

From Taonce

简单工厂 工厂方法模式
是否有基础的工厂类
是否有实现基础工厂类的具体类
是否有基础的产品类
产品类是否可拓展 可以 可以
工厂类可扩展 不可以(需要改变基础工厂类,如果增加产品类,那么工厂类就要增加这种产品类的判断) 可以(可以直接添加产品类,通过反射机制获取的具体产品,不需要修改代码,只需要增加产品类就行)

From safier

简单工厂模式

简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式。简单工厂模式是一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

image

具体实现如下:

  1. 定义一个操作接口:
public interface Operation {
 
    public double getResult(double numberA,double numberB) throws Exception;
 
}
  1. 定义具体的操作类
public class Add implements Operation{
 
    // 加法计算
    public double getResult(double numberA, double numberB) {
 
        return numberA + numberB;
    }
}
 
 
public class Sub implements Operation{
 
    // 减法计算
    public double getResult(double numberA, double numberB) {
        return numberA-numberB;
    }
}
 
 
public class Mul implements Operation{
 
    // 乘法计算
    public double getResult(double numberA, double numberB) {
        return numberA * numberB;
    }
}
 
 
public class Div implements Operation {
 
    // 除法计算
    public double getResult(double numberA, double numberB) throws Exception {
        if (numberB == 0) {
            throw new Exception("除数不能为0!");
        }
        return numberA / numberB;
    }
}
  1. 定义简单工厂类
public class EasyFactory {
 
    // 简单工厂,根据字符串创建相应的对象
    public static Operation createOperation(String name) {
        Operation operationObj = null;
        switch (name) {
            case "+":
                operationObj = new Add();
                break;
            case "-":
                operationObj = new Sub();
                break;
            case "*":
                operationObj = new Mul();
                break;
            case "/":
                operationObj = new Div();
                break;
        }
        return operationObj;
    }
}
  1. 客户端代码
public class Client {
 
    public static void main(String[] args) throws Exception {
 
        Operation add = EasyFactory.createOperation("+");
        Operation sub = EasyFactory.createOperation("-");
        Operation mul = EasyFactory.createOperation("*");
        Operation div = EasyFactory.createOperation("/");
 
        System.out.println(add.getResult(1, 1));
        System.out.println(sub.getResult(1, 1));
        System.out.println(mul.getResult(1, 1));
        System.out.println(div.getResult(1, 1));
    }
}
  1. Result:
2.0
0.0
1.0
1.0

缺点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。

优点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则

工厂方法模式

工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

image

  1. 首先定义一个工厂接口:
import org.zero01.operation.Operation;
 
public interface Factory {
 
    public Operation createOperation() ;
 
}
  1. 然后是具体的工厂类
// 加法类工厂
public class AddFactory implements Factory{
 
    public Operation createOperation() {
        System.out.println("加法运算");
        return new Add();
    }
}
 
// 减法类工厂
public class SubFactory implements Factory{
 
    public Operation createOperation() {
        System.out.println("减法运算");
        return new Sub();
    }
}
........
  1. 运算类跟简单工厂一样
  2. 客户端代码:
public class Client {
 
    public static void main(String[] args) throws Exception {
 
        // 使用反射机制实例化工厂对象,因为字符串是可以通过变量改变的
        Factory addFactory = (Factory) Class.forName("org.zero01.factory.AddFactory").newInstance();
        Factory subFactory=(Factory) Class.forName("org.zero01.factory.SubFactory").newInstance();
 
        // 通过工厂对象创建相应的实例对象
        Operation add = addFactory.createOperation();
        Operation sub = subFactory.createOperation();
 
        System.out.println(add.getResult(1, 1));
        System.out.println(sub.getResult(1, 1));
    }
}

优点:

  • 子类提供挂钩。基类为工厂方法提供缺省实现,子类可以重写新的实现,也可以继承父类的实现。加一层间接性,增加了灵活性。
  • 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不会变化。
  • 典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不需要关心,符合迪米特法则,符合依赖倒置原则,符合里氏替换原则。
  • 多态性:客户端代码可以做到与特定应用无关,适用于任何实体类。

缺点:需要Creator和相应的子类作为factory method的载体,如果应用模型确实需要creator和子类存在,则很好;否则的话,需要增加一个类层次。

两种模式的比较

工厂模式中,要增加产品类时也要相应地增加工厂类,客户端的代码也增加了不少。工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。

你想要加功能,本来是改工厂类的,而现在是修改客户端。而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。

但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。

谈谈对单例的理解,以及实现方式

单例的特点

  1. 构造方法不对外开放,为private
  2. 确保单例类只有一个对象,尤其是多线程模式下
  3. 通过静态方法或枚举返回单例对象
  4. 确保单例类在反序列化是不会重新创建新的对象

实现方式主要有如下几种:

  1. 饿汉式

    public class Singleton {
        /*
        * 饿汉式是在声明的时候就已经初始化Singleton1,确保了对象的唯一性
        *
        * 声明的时候就初始化对象会浪费不必要的资源
        * */
        private static Singleton instance = new Singleton();
    
        private Singleton() {
        }
    
        // 通过静态方法或枚举返回单例对象
        public Singleton getInstance() {
            return instance;
        }
    }
    
  2. 懒汉式

    public class Singleton {
    
        private static Singleton instance;
    
        private Singleton() {
        }
    
        /*
        * getInstance 添加了synchronized 关键字,,也就是说 getInstance 是一个同步方法,
        * 这就是懒汉式在多线程中保持唯一性的方式
        *
        * 懒汉式存在的问题是,即是instance已经被创建,每次调用getInstance方法还是会同步,这样就会浪费一些不必要的资源
        * */
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
  3. Double Check Lock(DCL模式)

    class Singleton {
        private static Singleton instance;
    
        private Singleton() {
        }
    
        /**
         * getInstance 进行了两次判空,第一次判空是为了不必要的同步,第二次判空为了在instance 为 null 的情况下创建实例
         * 既保证了线程安全且单例对象初始化后调用getInstance又不会进行同步锁判断
         * <p>
         * 优点:资源利用率高,效率高
         * 缺点:第一次加载稍慢,由于java处理器允许乱序执行,偶尔会失败
         *
         * @return
         */
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton3();
                    }
                }
            }
            return instance;
        }
    }
    
  4. 静态内部类实现单例模式

    public class Singleton {
        /*
        *当第一次加载Singleton类时并不会初始化SINGLRTON,只有第一次调用getInstance方法的时候才会初始化SINGLETON
        *第一次调用getInstance 方法的时候虚拟机才会加载SingletonHoder类,这种方式不仅能够保证线程安全,也能够保证对象的唯一,
        *还延迟了单例的实例化,所有推荐使用这种方式
        * */
        private Singleton() {
        }
    
        public Singleton getInstance() {
            return SingletonHolder.SINGLETON;
        }
    
        private static class SingletonHolder {
            private static final Singleton SINGLETON = new Singleton();
        }
    }
    
  5. 枚举实现单例模式

    public enum Singleton {
        /**
         *枚举是写法最简单的
         * 默认枚举实例的创建时线程安全的,且在任何一种情况下它都是单例,包括反序列化
         * */
        INSTANCE;
    }
    
  6. 使用容器实现单例

    public class Singleton {
        /**
         * 将多种类型的单例放到统一的Map将集合中,根据相应的Key获取对应的对象
         *
         * 这种方式是我们可以管理多种类型的单例,可以使用统一接口进行获取操作
         * 降低了使用成本,也隐藏了具体实现,降低了耦合度
         */
        public static Map<String, Object> objMap = new HashMap<String, Object>();
    
        private Singleton() {
        }
    
        public static void registerInstance(String key, Object instance) {
            if (!objMap.containsKey(key)) {
                objMap.put(key, instance);
            }
        }
    
        public static Object getInstance(String key) {
            return objMap.get(key);
        }
    }
    

From midNightHz

/**

单例模式:一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在
单例模式的设计思路 1、构造方法私有化 2、提供静态方法实例化对象
@author chen
*/
public class Singleton {
// 传说中的饿汉模式,在类加载时实例化。
private static Singleton singleton = new Singleton();

private Singleton() {

}

public static Singleton instance() {
	return singleton;
}
}

/**

懒汉模式
@author chen
*/
class Singleton1 {
private static Singleton1 singleton;

private Singleton1() {

}

public synchronized static Singleton1 instance() {
	/**
	 * 懒汉模式会有线程安全问题,可能会构造多个实例,最简单的就是对方法进行加锁
	 */
	if (singleton == null) {
		singleton = new Singleton1();
	}
	return singleton;
}
From safier

单例模式是设计模式中最简单也是最常见的设计模式,保证了在程序中只有一个实例并可以全局访问到。Singleton通过将构造方式限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。 (事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。)

目的:

希望对象只创建一次,并提供一个全局的访问点

规范:

  1. 每次从getInstance()都能返回一个且唯一的对象
  2. 资源共享情况下,getInstance()必须适应多线程并发访问
  3. 访问性能
  4. 懒加载(lazy Load),在需要的时候才被构造

使用场景:

  1. 应用中某个实例会被频繁的访问
  2. 某个实例对象初始化是会加载大量的资源
  3. 应用启动后只会存在一个的实例,如账号管理系统,数据库系统

一. 懒汉式

//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    //私有化构造方法,防止被实例化
    private Singleton() {}  
    //持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载    
    private static Singleton single=null;  
    //静态工厂方法   
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
} 

以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,以下有三种方式,都是对getInstance这个方法改造,保证了懒汉式了懒汉式单例的线程安全。

1.在getInstance方法上加同步

/*懒汉式变种,解决线程安全问题**/  
 public static synchronized Singleton getInstance() {  
     if (instance == null) {  
         instance = new Singleton();  
     }  
     return instance;  
 }  

/*加上synchronized,但是每次调用实例时都会加载**/  
 public static Singleton getInstance() {  
     synchronized (Singleton.class) {  
         if (instance == null) {  
             instance = new Singleton();  
         }  
     }  
     return instance;  
 }  

优点:解决了线程不安全的问题。

缺点 效率有点低,每次调用实例都要判断同步锁。

在Android源码中使用该单例方法有:InputMethodManager,AccessibilityManager等都是使用这种单例模式

2.双重检验锁

/*双重锁定:只在第一次初始化的时候加上同步锁*/  
  public static Singleton getInstance() {  
      if (instance == null) {  
          synchronized (Singleton.class) {  
              if (instance == null) {  
                  instance = new Singleton();  
              }  
          }  
      }  
      return instance;  
  } 

这种方法在并发量不多,安全性不高的情况下能完美运行,但是,这种方法也有不适用的地方,问题在这一句: instance = new Singleton() 。在JVM编译的过程中会出现指令重排的优化过程,这就会导致当instance实际上还没有初始化,就可能被分配了内存空间,也就是说会出现instance != null 但是又没有初始化的情况,这样就会导致返回的instance不完整。

优化:在并发量不多,安全性不高的情况下或者能很完美运行单例模式。

缺点:不同平台编译过程中可能会存在严重安全隐患。

在Android图像开源项目Android-Universai-Imager-Loader中使用的是这种方式。

3.内部类的实现

public class SingletonInner {  
  
    /** 
     * 内部类实现单例模式 
     * 延迟加载,减少内存开销 
     *  
     * @author xuzhaohu 
     *  
     */  
    private static class SingletonHolder {  
        private static SingletonInner instance = new SingletonInner();  
    }  
  
    /** 
     * 私有的构造函数 
     */  
    private SingletonInner() {  
  
    }  
  
    public static SingletonInner getInstance() {  
        return SingletonHolder.instance;  
    }  
  
    protected void method() {  
        System.out.println("SingletonInner");  
    }  
}

优点:延迟加载,线程安全(Java中class加载时互斥的),也减少了内存消耗。

二.饿汉式

public class Singleton{  
    private static Singleton instance = new Singleton();  
    private Singleton(){}  
    public static Singleton newInstance(){  
        return instance;  
    }  
} 

饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。

三.枚举法

class Resource{

    public enum SomeThing {
        INSTANCE;
        private Resource instance;
        SomeThing() {
            instance = new Resource();
        }
        public Resource getInstance() {
            return instance;
        }
    }
}

在用enum实现Singleton时有三个特性,自由序列化,线程安全,保证单例。

enum是由class实现的,换言之,enum可以实现很多class的内容,包括可以有member和member function,这也是我们可以用enum作为一个类来实现单例的基础。另外,由于enum是通过继承了Enum类实现的,enum结构不能够作为子类继承其他类,但是可以用来实现接口。此外,enum类也不能够被继承,在反编译中,我们会发现该类是final的。

enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫

From 吉文杰 jiwenjie

单例模式

  1. 懒汉式(线程不安全)

单例模式最后的目的无非就是获取当前存在的实例对象,如果没有实例对象就实例化一个,有就直接返回。所谓懒汉式,可以从名字进行理解,就是在你第一次使用这个实例之前我都(懒得)不去进行实例化,一直等到第一次需要使用到这个单例的地方再(迫不得已)实例化。

提到单例模式,大多数开发者会直接写出下面的代码:

public class SingleTon {

    private static SingleTon instance;

    private SingleTon(){

    }

    public static SingleTon getInstance(){
        if (instance==null){
            instance = new SingleTon();
        }
        return instance;
    }
}

而上面的代码也就是懒汉式的单例模式,只有在第一次调用了getInstance方法的地方才会对instance进行实例化。

这种单例模式的写法在单线程的时候没有问题,但需要注意的是,在 多线程的情况下,这种单例模式的写法可能会出现实例化多个单例的情况,即这种懒汉模式是线程不安全的,所以一般情况下也是不推荐这么写单例的。

  1. 懒汉式(线程安全)

像多数线程不安全的问题的解决方法相同,实现线程安全的懒汉单例进行加锁即可,例如直接将锁加在上面的例子里获取单例的静态方法getInstance上:

public static synchronized SingleTon getInstance(){
        if (instance==null){
            instance = new SingleTon();
        }
        return instance;
    }

显然,这种方法可以解决线程不安全的问题,即使多线程并行也只会存在一个单例,但这种方法的缺点是效率太低,仔细想想,其实有必要进行加锁的只有第一次判断instance为空而进行实例化的时候,而在instance实例化完成后,后面无论是哪个线程读取到的instance都已经不为null,可以直接将当前instance返回。而这种对整个方法进行加锁的操作则会使得每一次获取单例都进行加锁,效率十分低下。

  1. 双重检验锁

针对上面提出的问题,出现了第三种单例模式的写法,即双重检验锁的单例模式:

public static SingleTon getInstance(){
        if (instance==null){
            synchronized (SingleTon.class){
                if (instance==null){
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }

所谓双层检验锁,即像上面的例子中所示的,在加锁前后对实例对象进行两次判空的检验,加锁是为了第一次对象实例化的线程同步,而锁内还要有第二层判空是因为可能会有多个线程进入第一层if判断内部,而在加锁代码块外排队等候,如果锁内不进行第二次检验,仍然会出现实例化多个对象的情况。

而进行了双重检验之后,除了第一次实例化需要进行加锁同步,之后的线程只要进行第一层的if判断不为空即可直接返回,而不用每一次获取单例都加锁同步,因此相比前面两种单例模式的写法,更推荐用这种双层检验锁的方式来写单例。

然而,双层检验锁也不是完美的,问题出现在对单例对象进行实例化的过程中,在执行instance = new SingleTon()这句代码时大概做了三件事:

  1. 给instance分配内存空间
  2. 通过调用SingleTon的构造函数初始化成员变量
  3. 将instance这个引用指向先前分配好的内存空间

JVM在执行编译的时候实际上不能保证上述三件事中2和3的执行顺序,也就是说可能发生的执行顺序是123或132,如果是123的执行顺序则不会发生问题,而如果是132的执行顺序,则instance作为一个引用,在指向分配好的内存空间的时候本身就已经不为null了,此时如果有另一条线程调用getInstance方法,则会直接将还没有对成员变量完成初始化的instance返回,程序就可能发生崩溃。

为了解决这个问题,我们加入了violate关键字:

private static violate SingleTon instance;

violate的作用可以理解为一个读写锁,使用violate修饰后,一旦开始对instance进行操作,则只能将前面所说的三步全部进行完之后才能再读取instance,有了violate关键字,才能解决上面提到的问题。

  1. 饿汉式

可以看到,前面说的所有问题的根源都出在多线程上,如果可以解决多线程同步的问题,那么前面的问题也就迎刃而解了,于是就出现了饿汉式的单例模式。

对比懒汉式,懒汉是懒得进行实例化,直到要用到这个单例的时候才进行实例化,那么饿汉就是饥饿难耐、等不及的就进行了实例化,直接来看代码:

public class SingleTon {

    private static SingleTon instance = new SingleTon();

    private SingleTon(){

    }

    public static SingleTon getInstance(){
        return instance;
    }
}

显然,由于被修饰为static变量,instance会在类第一次加载到内存中时就进行了实例化,由于类加载机制的特性,这种写法本身就是线程安全的,所以不会有前面三种方式中存在的问题。

由于static修饰,instance会在类第一次被加载到内存中时就被初始化,如果某次运行期间并没有用到这个单例,就浪费了资源去加载了这个对象,不能做到延时加载;另一方面,有些时候我们需要依赖其他类创建的资源来创建这个单例,即需要在我们第一次调用getInstance方法的时候才真正的创建单例对象,这种一开始就初始化完成的方式也无法满足这种需求。

  1. 静态内部类式

针对上面饿汉式方法存在的问题,能不能有一种既能延迟加载、又能解决同步问题的单例模式写法呢?这便需要用静态内部类的方式了。

public class SingleTon {

    private static class SingleTonInstance{
        private static SingleTon instance = new SingleTon();
    }

    private SingleTon(){

    }

    public static SingleTon getInstance(){
        return SingleTonInstance.instance;
    }
}

由于静态内部类是私有的,因此除了getInstance方法没有其他方式能访问到它,因此只有在getInstance调用时才能真正创建单例,实现了单例创建的延迟,同时由于静态类只会加载一次的特性,因此也不会出现线程同步问题

  • 都需要额外的工作实现序列化,否则每次反序列化一个序列化对象时都会创建一个新的实例;
  • 仍然可以使用反射强行调用私有的构造函数构建单例。
  1. 枚举

实际上,在Java中最优雅的单例写法是使用枚举。

public enum  EnumSingle {
    INSTANCE;
}

简单的一个枚举,实际上就解决了线程同步、防止反射调用构造函数的问题,另一方面,枚举提供了自动序列化机制,防止了反序列化的时候重新创建对象,因此这是新版effective Java最推荐的单例写法。

注意

前面说枚举是最推荐的单例写法,从理论上来说,同时解决了前面所说的同步问题、反射调用构造函数问题、反序列化重新创建对象问题的枚举写法确实是最优雅的单例模式,但是需要注意的是,枚举类并不是普通类,使用枚举类对象作为单例自然丢弃了一些普通类的特性(继承等),而且枚举和饿汉式写法一样,无法实现延迟加载。

所以综上所述,如果不需要延迟加载,可以优先使用枚举实现单例;如果需要延迟加载,可以优先使用静态内部类。

当然,上面的分析是针对Java来说的,但是实际上在Android中因为性能问题不推荐使用枚举,而且Android中使用的Java都是在1.5版本以上的,所以Android使用的单例推荐使用静态内部类或者双重检验锁。

设计模式的基本原则

  1. 单一职责原则(Single Responsibility Principle)

    单一职责原则表示一个模块的组成元素之间的功能相关性。从软件变化的角度来看,就一个类而言,应该仅有一个让它变化的原因;通俗地说,即一个类只负责一项职责。

    • SRP 是一个简单又直观的原则,但是在实际编码的过程中很难将它恰当地运用,需要结合实际情况进行运用。
    • 单一职责原则可以降低类的复杂度,一个类仅负责一项职责,其逻辑肯定要比负责多项职责简单。
    • 提高了代码的可读性,提高系统的可维护性。
  2. 开放-关闭原则(Open-Closed Principle)

    开放-关闭原则表示软件实体 (类、模块、函数等等) 应该是可以被扩展的,但是不可被修改。

    如果一个软件能够满足 OCP 原则,那么它将有两项优点:

    • 能够扩展已存在的系统,能够提供新的功能满足新的需求,因此该软件有着很强的适应性和灵活性。
    • 已存在的模块,特别是那些重要的抽象模块,不需要被修改,那么该软件就有很强的稳定性和持久性。
  3. 里氏替换原则(Liskov Substitution Principle)

    将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。即子类可以扩展父类的功能,但不能改变父类原有的功能

  4. 依赖倒转原则(Dependence Inversion Principle)

    高层模块不应该依赖低层模块,二者都应该于抽象。进一步说,抽象不应该依赖于细节,细节应该依赖于抽象。遵循依赖倒转原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。依赖倒转原则的核心就是要我们面向接口编程

  5. 接口隔离原则(Interface Segregation Principle)

    客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上

    • 接口隔离原则的思想在于建立单一接口,尽可能地去细化接口,接口中的方法尽可能少
    • 但是凡事都要有个度,如果接口设计过小,则会造成接口数量过多,使设计复杂化。所以一定要适度
  6. 迪米特法则(Law Of Demeter)

    迪米特法则又称为 最少知道原则,它表示一个对象应该对其它对象保持最少的了解。通俗来说就是,只与直接的朋友通信。

    直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

    对于被依赖的类来说,无论逻辑多么复杂,都尽量的将逻辑封装在类的内部,对外提供 public 方法,不对泄漏任何信息。

  7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle)

    组合/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分; 新的对象通过向这些对象的委派达到复用已有功能的目的。

    在面向对象的设计中,如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类;如果基类的实现发生了改变,则子类的实现也不得不改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性。于是就提出了组合/聚合复用原则,也就是在实际开发设计中,尽量使用组合/聚合,不要使用类继承。

    • 总体说来,组合/聚合复用原则告诉我们:组合或者聚合好过于继承
    • 聚合组合是一种 “黑箱” 复用,因为细节对象的内容对客户端来说是不可见的

    From jiwenjie

    1、单一职责原则

    核心思想:应该有且仅有一个原因引起类的变更

    问题描述:假如有类Class1完成职责T1,T2,当职责T1或T2有变更需要修改时,有可能影响到该类的另外一个职责正常工作。

    好处:类的复杂度降低、可读性提高、可维护性提高、扩展性提高、降低了变更引起的风险。

    需注意:单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可以度量的,因项目和环境而异。

    2、里氏替换原则

    核心思想:在使用基类的的地方可以任意使用其子类,能保证子类完美替换基类。

    通俗来讲:只要父类能出现的地方子类就能出现。反之,父类则未必能胜任。

    好处:增强程序的健壮性,即使增加了子类,原有的子类还可以继续运行。

    需注意:如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系 采用依赖、聚合、组合等关系代替继承。

    3、依赖倒置原则

    核心思想:高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象;

    说明:高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类。

    通俗来讲:依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。

    问题描述:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

    解决方案:将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。

    好处:依赖倒置的好处在小型项目中很难体现出来。但在大中型项目中可以减少需求变化引起的工作量。使并行开发更友好。

    4、接口隔离原则

    核心思想:类间的依赖关系应该建立在最小的接口上

    通俗来讲:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

    问题描述:类A通过接口interface依赖类B,类C通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。 需注意:

    接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度

    提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情

    为依赖接口的类定制服务。只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。

    5、迪米特法则

    核心思想:类间解耦。

    通俗来讲: 一个类对自己依赖的类知道的越少越好。自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。

    6、开放封闭原则

    核心思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化

    通俗来讲: 一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性。

    总结:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。