Prototype Pattern(原型模式)

参考Prototype Design Pattern
Factory模式与Prototype模式的异同

the prototype is the object containing clone method used to clone similar objects to it for reducing the number of classes

a solution to these problems

How does one reduce the number of classes that share similar behavoir and relationships

Solution

Define a class to replace all the classes that share similar behavior and relationships. Save instances,called prototypes,of this class. To create an instance of any of the classes replaced, simply clone the desired prototype and modify its attributes

Liabilities(缺点)

  1. The subclasses of prototype must implement the clone() method. This requires deep-copying if the instances and original must be independent;otherwise, shallow-copying is sufficient
  2. Implementing clone() may be difficult or impossible if the class is final or there are circular references 循环引用 无法克隆

Example Prototype in Java

  1. Create a “contract” with clone() and getName() entries
  2. Design a “registry” that maintains a cache of prototypical objects
  3. Populate the registry with an initializePrototypes() function
  4. The registry has a findAndClone() “virtual constructor” that can transform a String into its correct object (it calls clone() which then calls “new”)
  5. All classes relate themselves to the clone() contract
  6. Client uses the findAndClone() virtual ctor instead of the “new” operator
package Prototype;
interface Prototype {
    Object clone();
    String getName();
}

//1.The clone() contract

interface Command
{
    void execute();
}
class PrototypesModule
{
    //2. "registry" of prototypical objs
    private static Prototype[] prototypes = new Prototype[9];
    private static int total = 0;

    // Adds a feature to the Prototype attribute of the PrototypesModule class
    //obj The feature to be added to the Prototype attribute
    public static void addPrototype(Prototype obj)
    {
        prototypes[total++] = obj;
    }
    public static Object findAndClone(String name)
    {
        //4. The "virtual ctor"
        for(int i=0;i<total;i++)
        {
            if(prototypes[i].getName().equals(name))
                return prototypes[i].clone();
        }
        System.out.println(name + "not found");
        return null;
    }
}
//5. Sign-up for the clone() contract
class This implements Prototype,Command
{
    public Object clone()
    {
        return new This();
    }
    public String getName()
    {
        return "This";
    }
    public void execute()
    {
        System.out.println("This:execute");
    }
}
class That implements Prototype,Command
{
    public Object clone()
    {
        return new That();
    }
    public String getName()
    {
        return "That";
    }
    public void execute()
    {
        System.out.println("That:execute");
    }
}
class TheOther implements Prototype,Command
{
    public Object clone()
    {
        return new TheOther();
    }
    public String getName()
    {
        return "TheOther";
    }
    public void execute()
    {
        System.out.println("TheOther:execute");
    }
}
public class PrototypeDemo {
    //3. Populate the "registry"
    public static void initializePrototypes()
    {
        PrototypesModule.addPrototype(new This());
        PrototypesModule.addPrototype(new That());
        PrototypesModule.addPrototype(new TheOther());
    }

    public static void main(String[] args) {
        initializePrototypes();
        Object[] objects = new Object[9];
        int total = 0;
        // 6. Client does not use "new"
        for(int i=0;i<args.length;i++)
        {
            objects[total] = PrototypesModule.findAndClone(args[i]);
            if(objects[total]!=null) total++;
        }
        for(int i=0;i<total;i++)
        {
            ((Command)objects[i]).execute();
        }       
    }
}

2016-04-24_154950

Singleton设计模式

参考
Efficetive Java page14
深入浅出单实例Singleton设计模式
如何正确地写出单例模式

Singleton 1.0版本

public class Singleton {
    private static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {
        if(instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}

Singleton的特点

  1. 私有(private)的构造函数,表明这个类是不可能形成实例了。这主要是怕这个类会有多个实例。
  2. 即然这个类是不可能形成实例,那么,我们需要一个静态的方式让其形成实例:getInstance()。注意这个方法是在new自己,因为其可以访问私有的构造函数,所以他是可以保证实例被创建出来的。
  3. 在getInstance()中,先做判断是否已形成实例,如果已形成则直接返回,否则创建实例。
  4. 所形成的实例保存在自己类中的私有成员中。
  5. 我们取实例时,只需要使用Singleton.getInstance()就行了。

单线程下

测试代码:

public class Tests {
    
    public static void main(String[] args) {
        for(int i=1;i<=1000;i++)
        {
            //Object相同则hashCode一样
            System.out.println(Singleton.getInstance().hashCode());
        }
    }
}

测试结果
2016-04-19_132745

多线程下

测试代码:

public class Tests {
    
    public static void main(String[] args) {
        TestSingletonThread T[]=new TestSingletonThread[101];
        for(int i=1;i<=100;i++)
        {
            T[i] = new TestSingletonThread();
            T[i].start();
         }
     }
}
    class TestSingletonThread extends Thread
    {
        @Override
        public void run()
        {
            System.out.println(Singleton.getInstance().hashCode());
        }
}

测试结果:
2016-04-19_133750

可以发现在多线程下会实例化多个实例

Singleton 1.1版本

因为在多线程下会实例化多个实例,所以加入synchronized关键字

public class Singleton {
    private static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static  synchronized Singleton getInstance()
    {
        if(instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}

但是此时在多线程下,当已经实例化一份实例后,之后的线程再去获得实例应该是并行的,而加上synchronized关键字后变成并行 影响程序性能
因为自己电脑为4线程所以开4个线程去获取Singleton的实例,来展示性能差异
代码

public class Tests {
    
    public static void main(String[] args) throws InterruptedException {
        TestSingletonThread T[]=new TestSingletonThread[5];
        long start,end;
        start = System.nanoTime();
        for(int i=1;i<=4;i++)
        {
            T[i] = new TestSingletonThread();
            T[i].start();
        }
        for(int i=1;i<=4;i++)
        {
            T[i].join();
        }
        end = System.nanoTime();
        System.out.println("运行时间"+(end-start)/1000.0+"微秒");
    }
}
class TestSingletonThread extends Thread
{
    @Override
    public void run()
    {
        System.out.println(Singleton.getInstance().hashCode());
    }
}

不加synchronized关键字 但会实例化多个

QQ截图20160419214859

QQ截图20160419215011

加synchronized关键字 不会实例化多个 但会损耗性能

QQ截图20160419214736

QQ截图20160419214826

Singleton1.2版本

为了改善性能同时又不实例化多个Singleton实例,可以使用双重检测(Double-Check)

代码

public class Singleton {
    private static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {
        
        if(instance==null){
            synchronized(Singleton.class){
                if(instance == null)
                    instance = new Singleton();
            }       
        }   
        return instance;
    }
}

说明
1. 第一个条件是说,如果实例创建了,那就不需要同步了,直接返回就好了。
2. 不然,我们就开始同步线程。
3. 第二个条件是说,如果被同步的线程中,有一个线程创建了对象,那么别的线程就不用再创建了。
但是,如果你认为这个版本大攻告成,你就错了。

主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 singleton 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
  3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

对此,我们只需要把singleton声明成 volatile 就可以了。下面是1.3版:

Singleton 1.3版本

代码

public class Singleton {
    private volatile static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {
        
        if(instance==null){
            synchronized(Singleton.class){
                if(instance == null)
                    instance = new Singleton();
            }       
        }   
        return instance;
    }
}

使用 volatile 有两个功用:

1)这个变量不会在多个线程中存在复本,直接从内存读取。

2)这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。

但是,这个事情仅在Java 1.5版后有用,1.5版之前用这个变量也有问题,因为老版本的Java的内存模型是有缺陷的。

Singleton 1.4版本

这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

 public class Singleton {
    private  static final Singleton instance = new Singleton();
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {   
        return instance;
    }
}

但是,这种玩法的最大问题是——当这个类被加载的时候,new Singleton() 这句话就会被执行,就算是getInstance()没有被调用,类也被初始化了。

于是,这个可能会与我们想要的行为不一样,比如,我的类的构造函数中,有一些事可能需要依赖于别的类干的一些事(比如某个配置文件,或是某个被其它类创建的资源),我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。

Singleton 1.5版本

静态内部类

public class Singleton {
    
    private static class SingletonHolder
    {
        private  static final Singleton instance = new Singleton();
    }
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static final Singleton getInstance()
    {   
        return SingletonHolder.instance;
    }
}

上面这种方式,仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它只有在getInstance()被调用时才会真正创建;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

Singleton 1.6 枚举 Enum

public enum Singleton {
    INSTANCE;
    public  void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }
}

我们可以通过Singleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

#其它问题(参考链接1)

Class Loader

序例化 (声明所有实例域都是瞬时的(transient)的 并提供一个readResolve方法

Effective Java 77条

多个Java虚拟机

volatile变量

关于代码重用

GRASP通用职责分配软件模式

GRASP设计模式及OO设计原则

GRASP设计模式的全称是General Responsibility Assignment Software Patterns,即通用职责分配软件模式。它定义了9个基本的OO设计原则或基本的设计构件。这9个设计模式分别是:
* 控制器(Controller)
* 创建者(Creator)
* 高内聚(High cohesion)
* 间接性(Indirection)
* 信息专家(Information expert)
* 低耦合(Low coupling)
* 多态(Polymorphism)
* 预防变化(Protected variations)
* 纯虚构(Pure fabrication)

1.The Controller pattern

  1. the controller collaborates with the bussiness objects to handle the actor request
  2. the controller delivers the result to the presentation,which displays the result to the actor
  3. 为了解决如下问题(a solution to these problems)
    A. Decouple the presentation and bussiness objects
    B. Remove the responsibility to handle an actor request from the presentation and assign it to another object
  4. 缺点(liabilities)
    A controller may be assigned too many responsibilities,resulting in a so-called bloated controller

2.Creator

  1. the creator is the object that is responsible for creating an object of a class
  2. a solution to these problems,supposed that an object of class A is the creator of an object of class B
    A. Class A is an aggregation(聚合) of class B
    B. An object of class A contains objects of class B
    C. An object of class A records object of class B
    D. An object of class A closely uses objects of class B
    E. An object of class A has the information to create objects of class B
  3. 缺点(liabilities)
    One same object may have different creation behaviors

3 6 High cohesion Low coupling

4 Indirection

间接性模式关注这样一个问题:为了避免两个或多个事务之间直接耦合,应该如何分配职责?如何使对象解耦合,以支持低耦合并提高复用性潜力?

间接性模式对此的回答是:将职责分配给中介对象,使其作为其他构件或服务之间的媒介,以避免它们之间的直接耦合。中介则实现了其他构件之间的间接性。

间接性模式的思想比较简单,即通过一个中介就能消除许多的耦合。在GoF的23种设计模式中,有许多模式都利用到了间接性的思想。比如桥接模式中,设计将抽象部分与其实现部分相分离,利用的就是在客户与实现之间增加了一个抽象层次。外观模式则是在整个子系统与客户之间增加了一个便于用户使用的外观类作为中介。而中介者模式中的中介者则更是典型的例子。

5 The expert pattern

  1. the expert is the object that is responsible for handling a request should have the information to fulfill the request
  2. a solution to these problems
    A. who should be assigned the responsibility to handle a request
    B. Remove the excessive responsibility from the controller and assigning them to other objects
  3. 缺点(liabilities)
    The expert may become a big object

7 多态(Polymorphism)

8 预防变化(Protected variations)

防止变异模式关注这样一个问题:如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响?

防止变异模式的回答是:识别预计变化或不稳定之处,分配职责用以在这些变化之外创建稳定的接口。

防止变异(PV)是非常重要和基本的软件设计原则,几乎所有的软件或架构设计技巧都是防止变异的特例。PV是一个根本原则,它促成了大部分编程和设计的模式和机制,用来提供灵活性和防止变化。在软件设计中,除了数据封装、接口、多态、间接性等机制是PV的核心机制之外,没有一种固定的或者是通用的办法能够防止一切变化的产生。因此PV的实现依赖的是一系列的OO设计方面的经验性原则,用以产生一个设计良好的高内聚、低耦合的系统,从而支持PV。

这里需要参考文章面向对象设计原则

9 纯虚构(Pure fabrication)

纯虚构模式关注这样一个问题:当你并不想违背高内聚和低耦合或其他目标,但是基于专家模式所提供的方案又不合适时,哪些对象应该承担这一职责?

OO设计中的领域模型是对领域内的概念内或现实世界中的对象的模型化表示。创建领域模型的关键思想是减小软件人员的思维与软件模式之间的表示差异。因此,在OO设计时,系统内的大多数类都是来源于现实世界中的真实类。然而,在给这些类分配职责时,有可能会遇到一些很难满足低耦合高内聚的设计原则。纯虚构模式对这一问题给出的方案是:给人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念,而代表虚构出来的事物,用以支持高内聚、低耦合和复用。

纯虚构模式强调的是职责应该置于何处。一般来说,纯虚构模式会通过表示解析或者行为解析来确定出一些纯虚构类,用于放置某一类职责。理想状况下,分配给这种虚构物的职责是要支持高内聚低耦合的,从而使整个系统处于一种良好的设计之中。

例如,在信息专家模式的最后一段所举的例子中提到,许多后台系统都需要对数据库进行操作,将系统中的一些对象进行持久化。信息专家模式给出的建议是将持久化的职责分配给具体的每一个模型类。但是这种建议已经被证明是不符合高内聚低耦合原则的,因为不会被采纳。于是,设计者往往会在系统中加入类似于DAO或者PersistentStorage这样的类。这些类在领域模型中是并不存在的,它们完全由设计者根据系统的行为而虚构得到。然而,这些类的引入,使得操作数据库进行持久化这种高内聚的职责可以顺理成章地分配给它们。从而在整个系统中实现了比较好的内聚和耦合。

在使用纯虚构模式时,不能毫无限制地对系统中的各种行为进行解析并纯虚构。如此往往会导致系统中大量的行为对象的存在,这样会对耦合产生不良的影响。

面向对象设计原则

一些软件设计的原则
面向对象设计原则和创建SOLID应用的5个方法

迪米特法则

定义:

一个对象应该对其它对象保持最少的了解 从而降低类之间的耦合

具体来说对于对象 ‘O’ 中一个方法’M’,M应该只能够访问以下对象中的方法:

  1. 对象O
  2. 与O直接相关的Component Object
  3. 由方法M创建或者实例化的对象
  4. 作为方法M的参数的对象
    例子:demeter

SOLID 原则

单一职责 (SRP)

一个类发生变化的原因不应该超过一个。这意味着代码中每个类,或者类似的结构只有一个功能。
QQ截图20160413142903

里氏替换原则 (LSP)

子类必须能够替换成它们的基类。即:子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。另外,不应该在代码中出现if/else之类对子类类型进行判断的条件。里氏替换原则LSP是使代码符合开闭原则的一个重要保证。正是由于子类型的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。
A. Derived types must be completely substitutable for their base types
B. This principle is just an extension of the Open Close Principle
C. Making sure that new derived classes are extending the base classes without
changing their behavior

QQ截图20160413145728

正方形不是矩形的例子

接口隔离原则 (ISP)

接口隔离原则(Interface Segregation Principle)指出客户不应该被强迫依赖于他们不使用的接口。当我们使用非内聚的接口时,ISP指导我们创建多个较小的内聚度高的接口。

当你应用ISP时,类和他们的依赖使用紧密集中的接口通信,最大限度地减少了对未使用成员的依赖,并相应地降低耦合度。小接口更容易实现,提升了灵活性和重用的可能性。由于很少的类共享这些接口,为响应接口的变化而需要变化的类数量降低,增加了鲁棒性。

A. Clients should not be forced to depend upon interfaces that they don’t use

QQ截图20160413153256

QQ截图20160413153318

依赖反转原则 (DIP)

依赖反转原则(Dependency Inversion Principle,DIP)指出高层次模块不应该依赖于低层次模块;他们应该依赖于抽象。第二,抽象不应该依赖于细节;细节依赖于抽象。方法是将类孤立在依赖于抽象形成的边界后面。如果在那些抽象后面所有的细节发生变化,那我们的类仍然安全。这有助于保持低耦合,使设计更容易改变。
A. High-level modules should not depend on low-level modules. Both should
depend on abstractions
B. Abstractions should not depend on details. Details should depend on
abstractions

QQ截图20160413154556

QQ截图20160413154612

QQ截图20160413155306

QQ截图20160413155326

开闭原则(OCP)

关于开发封闭原则,其核心的思想是:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

A. Software entities like classes, modules and functions should be open for
extensionbut closedformodifications
B. The design and writing of the code should be done in a way that new
nctionality should be added with minimum changes in the existing code
C. The design should be done in a way to allow the adding of new functionality as
new classes, keeping as much as possible existing code unchanged

QQ截图20160413160021

QQ截图20160413160030

其它原则

参考第一个链接一些软件设计的原则
* Common Closure Principle(CCP)– 共同封闭原则
* Common Reuse Principle (CRP) – 共同重用原则
* Hollywood Principle – 好莱坞原则
* High Cohesion & Low/Loose coupling & – 高内聚, 低耦合
* Convention over Configuration(CoC)– 惯例优于配置原则
* Separation of Concerns (SoC) – 关注点分离
* Design by Contract (DbC) – 契约式设计
* Acyclic Dependencies Principle (ADP) – 无环依赖原则
* Don’t Repeat Yourself (DRY)
* Keep It Simple, Stupid (KISS)
* Program to an interface, not an implementation
* Command-Query Separation (CQS) – 命令-查询分离原则
* You Ain’t Gonna Need It (YAGNI)