【笔记】设计模式1

/ 0评 / 0

《设计模式的艺术》(刘伟著)笔记

面向对象设计原则

书中介绍了七条十分具有代表性的面向对象设计原则,遵循这些原则可以更好地实现可维护性复用。这些原则也作为实现和评价一个设计模式的准则。

 

单例模式 Singleton Pattern

所谓单例模式,就是保证类的实例始终唯一。单例模式经常作为某个服务的生命周期类型。它的实现用一句话概括就是:构造方法私有,公开静态方法获取唯一实例,解决并发问题。原因在于在类内部无法对 new 关键字创建的实例进行限定,所以我们必须隐藏构造方法,通过自己实现的方法对外公开实例,在这个方法内部保证实例的唯一性即可。

JAVA 基本实现:

public class Singleton 
{
    private static Singleton single = null;

    private Singleton()
    {

    }

    public static Singleton GetSingleton()
    {
        if(single == null) single = new Singleton();
        return single;
    }

    public static void main(String[] args)
    {
        Singleton single = Singleton.GetSingleton();
        System.out.println(Singleton.GetSingleton() == single);
    }
}

上面的这个实现有一个明显的问题,在并发情况下容易发生不一致的情况。原因在于GetSingleton方法并不是原子操作,当并发调用该方法时,可能前一次即将对single赋值前,第二次调用便已经进行判断并也准备对single赋值,这时将产生两个实例。下面给出三种解决方案。

预加载:

public class Singleton 
{
    private static final Singleton single = new Singleton();

    private Singleton()
    {

    }

    public static Singleton GetSingleton()
    {
        return single;
    }
}

懒加载:

public class Singleton 
{
    private static Singleton single = null;

    private Singleton()
    {

    }

    public static Singleton GetSingleton()
    {
        if(single == null)
        {
            synchronized(Singleton.class)
            {
                if(single == null)
                {
                    single = new Singleton();
                }
            }
        }
        return single;
    }
}

IoDH(Initialization on Demand Holder):该方法利用了JAVA类初始化的线程安全性。注意,实例只在第一次调用Holder类时(而不是第一次调用Singleton类时)才会被创建,即该方法是懒加载的。

public class Singleton 
{

    private Singleton()
    {

    }

    private static class Holder
    {
        private static final Singleton single = new Singleton();
    }

    public static Singleton GetSingleton()
    {
        return Holder.single;
    }
}

简单工厂模式 Simple Factory Pattern

简单工厂模式也叫做静态工厂模式(Static Factory Pattern)。先介绍下什么是工厂模式,工厂模式是十分常用的模式,它有很多种实现。在所有工厂模式中最主要的概念是工厂和产品角色。工厂是工厂模式的核心,它被外界直接调用,负责创建所需的产品对象,产品角色是工厂的创建目标,用户通过工厂创建产品对象,而不通过 new 关键字。即工厂模式将类的使用和创建这两个过程分离了

根据工厂类和产品类的抽象化的程度不同,分为不同的工厂模式。简单工厂模式对产品角色进行了抽象,为了让工厂更有扩展性,设定一个抽象产品角色(通常用抽象类实现),它作为所有产品对象的父类,所有具体产品角色都继承自抽象产品角色,工厂针对抽象产品角色编程。

如下图,Product为抽象产品角色,ConcreteProduct为具体产品角色,factoryMethod为工厂方法,它创建并返回一个产品对象。返回类型为抽象产品角色,由里氏代换原则,它可以用于创建任何具体产品对象。

JAVA实现:

public class StaticFactory 
{
    public static Product factoryMethod(String arg)
    {
        Product product = null;
        switch (arg)
        {
            case "A": product = new ConcreteProductA(); break;
            case "B": product = new ConcreteProductB(); break;
        }

        return product;
    }

    public static void main(String[] args)
    {
        Product productA = StaticFactory.factoryMethod("A");
        Product productB = StaticFactory.factoryMethod("B");
        productA.methodDiff();
        productB.methodDiff();
    }

}

abstract class Product 
{
    public void methodSame()
    {
        System.out.println("Product");
    }

    public abstract void methodDiff();
}

class ConcreteProductA extends Product 
{

    @Override
    public void methodDiff() 
    {
        System.out.println("A");
    }
}

class ConcreteProductB extends Product 
{

    @Override
    public void methodDiff() 
    {
        System.out.println("B");
    }
}

简单工厂模式的缺点也是显而易见的:工厂的职责过重了,如果产品角色的数量很多,工厂方法内部将充满各种产品角色的创建代码,且每增加一名产品角色,都要修改工厂类的代码,扩展性差。为此,引出下一种工厂模式:

工厂方法模式 Factory Mehod Pattern

工厂方法模式不仅抽象了产品角色,还抽象了工厂,为此设定了一个抽象工厂,通常用接口实现,所有具体工厂都实现该接口,针对不同的产品提供不同的工厂,工厂等级结构与产品等级结构保持一致。

JAVA实现:

interface Factory
{
    public Product factoryMethod();
}
class ConcreteFactoryA implements Factory 
{
    @Override
    public Product factoryMethod()
    {
        Product product = new ConcreteProductA();
        return product;
    }

}

class ConcreteFactoryB implements Factory 
{
    @Override
    public Product factoryMethod()
    {
        Product product = new ConcreteProductB();
        return product;
    }

}

abstract class Product 
{
    public void methodSame()
    {
        System.out.println("Product");
    }

    public abstract void methodDiff();

    public static void main(String[] args)
    {
        Factory factoryA = new ConcreteFactoryA();
        Factory factoryB = new ConcreteFactoryB();
        Product productA = factoryA.factoryMethod();
        Product productB = factoryB.factoryMethod();
        productA.methodDiff();
        productB.methodDiff();
    }
}

class ConcreteProductA extends Product 
{

    @Override
    public void methodDiff() 
    {
        System.out.println("A");
    }
}

class ConcreteProductB extends Product 
{

    @Override
    public void methodDiff() 
    {
        System.out.println("B");
    }
}

工厂方法模式充分利用了多态性,加入新的产品角色时无需修改抽象工厂接口或者抽象产品角色类,只需添加具体工厂和具体产品即可,扩展性良好。缺点也很明显,每增加一个新产品,类的数目成对增长,系统的复杂度增加了,当产品数量很大时,将存在大量的工厂类。为此引出下一种工厂模式:

抽象工厂模式 Abstract Factory Pattern

抽象工厂模式基于工厂方法模式进行了改进,目的就是为了减少工厂的数量。在现实系统中,产品角色之间并不是毫无关系的,比如上文中很多具体产品角色可以继承自某一抽象产品角色,这种分类称为产品类别结构(书中称为产品等级结构,但我觉得产品类别更合适),而不同类别结构的产品根据某个限定关系可以分成若干个产品族

举个例子:若现在要对一套系统的外观主题功能进行设计,假设该系统界面由按钮、标签、编辑框组成,有Dark、Light两个主题。可以进行如下类设计:

其中,有三个产品类别,分别是ButtonXXX、LabelXXX、TextEditorXXX。有两个产品族,分别是XXXDark、XXXLight。在程序中我们一般只会同时用到一个产品族。抽象工厂模式就是将同一个产品族的创建集中在一起,即一个工厂负责创建一个产品族的所有产品。在这个例子里,有六个具体产品角色,但只需要两个工厂。在代码中,产品类别既可以使用抽象类实现,也可以使用接口实现,甚至可以用具体类实现。

JAVA实现:

public class AbstractFactory {
    public static void main(String[] args)
    {
        ControlFactory dark = new DarkControlFactory();
        ControlFactory light = new LightControlFactory();
        Button button1 = dark.CreateButton();
        Button button2 = light.CreateButton();
        button1.click();
        button2.click();

    }
}

interface ControlFactory{
    public Button CreateButton();
    public Label CreateLabel();
}

abstract class Button{
    public void click()
    {
        System.out.println("Button Clicked");
    }
}

abstract class Label{

}

class ButtonDark extends Button {
    @Override
    public void click()
    {
        System.out.println("Dark Button Clicked");
    }
}

class ButtonLight extends Button{

    @Override
    public void click()
    {
        System.out.println("Light Button Clicked");
    }

}

class LabelDark extends Label {

}

class LabelLight extends Label{

}

class DarkControlFactory implements ControlFactory{

    @Override
    public Button CreateButton()     {
        return new ButtonDark();
    }

    @Override
    public Label CreateLabel()     {
        return new LabelDark();
    }
}

class LightControlFactory implements ControlFactory{

    @Override
    public Button CreateButton()     {
        return new ButtonLight();
    }

    @Override
    public Label CreateLabel()     {
        return new LabelLight();
    }
}

在抽象工厂模式中,增加产品族很简单,无需修改已有代码。但增加产品类别时,则需要修改系统中绝大部份的代码,违背了开闭原则,这是抽象工厂模式的主要缺点。

原型模式 Prototype Pattern

原型模式实际上是基于对象克隆(Clone)的一种解决方案。在JAVA语言中,我们可以借助 Cloneable 接口来标识一个可复制的对象,复制方法是实现在被复制对象内部的,这说明对象自身就是自己的“工厂”,原型模式只不过将这个过程给固定化了而已。

所谓原型,就是即将被复制的对象,在面对需要创建多个相同或相似的对象时,我们通常会保留一个或多个具有代表的实例并长期维护,其他对象都通过复制该对象来创建,这个代表实例就是原型。

一个原型类型可以有多个原型对象,这些原型对象就相当于实例模板一样,在需要的时候进行复制。有时候我们也存在多个原型类型,如果这些类型都实现自同一个抽象层,那么客户端只需要针对抽象层编程,便能适用所有具体类型了,并且扩展性也比较好。极端的一个抽象层就是JAVA中的Object类型,但我们更希望自己定义抽象层,以实现业务功能,但要保证抽象层实现 Cloneable 接口。

原型模式的主要角色有如下几个:

下面的代码实现了一个通过序列号实现深克隆的原型模式,其中原型管理器使用了单例模式。

import java.io.*;
import java.util.Hashtable;

class PrototypePattern{
    public static void main(String[] args)
    {
        PrototypeManager manager = PrototypeManager.getManager();
        System.out.println(PrototypeManager.getManager() == manager);
        AbstractPrototype defaultCopy = manager.getPrototypeCopy("default");
        defaultCopy.print();

        ConcretePrototype testPrototype = new ConcretePrototype("test");
        testPrototype.setAttachment(new Attachment("testAttach"));
        manager.addPrototype("test", testPrototype);
        ConcretePrototype testCopy = (ConcretePrototype) manager.getPrototypeCopy("test");
        testCopy.print();
        System.out.println(testCopy.getAttachment().getName());
        System.out.println(testCopy == testPrototype);
        System.out.println(testCopy.getAttachment() == testPrototype.getAttachment());

    }
}

class PrototypeManager{
    private Hashtable<String, AbstractPrototype> hash = new Hashtable<>();
    private static PrototypeManager manager = new PrototypeManager();

    private PrototypeManager()
    {
        addPrototype("default", new ConcretePrototype(null));
    }

    public static PrototypeManager getManager()
    {
        return manager;
    }

    public void addPrototype(String key, AbstractPrototype prototype)
    {
        hash.put(key, prototype);
    }

    public AbstractPrototype getPrototypeCopy(String key)
    {
        return hash.get(key).clone();
    }
}

interface AbstractPrototype extends Cloneable{
    public AbstractPrototype clone();
    public void print();
}
class Attachment implements Serializable{
    private String name;
    public String getName()
    {
        return name;
    }

    public Attachment(String name)
    {
        this.name = name;
    }
}

class ConcretePrototype implements AbstractPrototype, Serializable{

    private Attachment attach = null;

    private String name = null;

    public ConcretePrototype(String name)
    {
        this.name = name;
    }

    @Override
    public AbstractPrototype clone()
    {
        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
        ObjectOutputStream objectOutput = null;
        try {
            objectOutput = new ObjectOutputStream(byteOutput);
            objectOutput.writeObject(this);
            ByteArrayInputStream byteInput = new ByteArrayInputStream(byteOutput.toByteArray());
            ObjectInputStream objectInput = new ObjectInputStream(byteInput);
            return (ConcretePrototype)objectInput.readObject();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void print()
    {
        System.out.println("ConcretePrototype " + name);
    }

    public Attachment getAttachment()
    {
        return attach;
    }

    public void setAttachment(Attachment attach)
    {
        this.attach = attach;
    }
}

建造者模式 Builder Pattern

建造者模式适用于复杂对象的组装与创建。当一个对象由很多相对独立的部件(即成员对象)组装而成,并且各个部件的创建都有一定顺序要求的情况下,就应该使用建造者模式了。

建造者模式的核心就是Builder类,它的作用就是实现对象各个部件的构建,最终构建整个对象。与工厂模式有所不同的是,建造者模式将对象的构建拆解为更小的过程,具体来说,每一个部件的构建都实现为Builder类中的一个方法。

假设有如下产品类,给出一个Builder类的抽象层定义:

class ComplexProduct{
    private String partA;
    private String partB;
    private String partC;
}

abstract class Builder{
    protected ComplexProduct product = new ComplexProduct();

    public abstract Builder buildPartA();
    public abstract Builder buildPartB();
    public abstract Builder buildPartC();

    public ComplexProduct build()
    {
        return product;
    }
}

其中产品对象的三个部件分别由Builder类的三个buildPartX方法来构建,而最终整个对象由build方法来构建。如果有很多产品需要构建,这些产品的部件都不相同,那只需要基于Builder抽象层实现多个具体的Builder类,每个都按需实现部件构建的方法即可。

需要注意的是,Builder类并未对部件的构建顺序进行严格限定,有时部件可能具有复杂的依赖关系,这个时候就需要Director类(指挥者)了,这个类负责安排复杂对象的建造次序,Director类内维护一个Builder类,并实现一个按序构建对象的方法:

class Director{
    private Builder builder;
    public Director(Builder builder)
    {
        this.builder = builder;
    }
    public ComplexProduct build()
    {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
        return builder.build();
    }
}

有了Director类,为了构建一个复杂对象,只需要先实现一个Builder类,在其中定义各个部件的构建过程。为了保证构建顺序正确,用户并不直接调用Builder类中的方法,而是将Builder类传入Director类,由Director类来按正确顺序构建对象,甚至在Director类里可以决定哪些部件需要构建,哪些部件不需要构建。为了简化结构,也可以将Director类和Builder类合并,在Builder类直接提供按顺序构建对象的方法。

下面给出一个简单的例子,由于部件之间没有顺序要求,省略了Director类:

import java.time.LocalDate;

public class BuilderPattern {
   public static void main(String[] args)
   {
       PersonBuilder builder = new ConcretePersonBuilder();
       Person mike = builder.withName("Mike")
               .withAge(18)
               .withBirthday(LocalDate.of(2003,5,12))
               .withAddress("Moon")
               .build();
   }
}

class Person{
    private String name;
    private int age;
    private LocalDate birthday;
    private String address;

    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 LocalDate getBirthday()
    {
        return birthday;
    }

    public void setBirthday(LocalDate birthday)
    {
        this.birthday = birthday;
    }

    public String getAddress()     {         return address;     }

    public void setAddress(String address)     {        this.address = address;     }
}

abstract class PersonBuilder{
    protected Person person = new Person();

    public abstract PersonBuilder withName(String name);
    public abstract PersonBuilder withAge(int age);
    public abstract PersonBuilder withBirthday(LocalDate birthday);
    public abstract PersonBuilder withAddress(String address);
    public Person build()
    {
        return person;
    }
}

class ConcretePersonBuilder extends PersonBuilder {

    @Override
    public PersonBuilder withName(String name)     {
        person.setName(name);
        return this;
    }

    @Override
    public PersonBuilder withAge(int age)     {
        person.setAge(age);
        return this;
    }

    @Override
    public PersonBuilder withBirthday(LocalDate birthday)     {
        person.setBirthday(birthday);
        return this;
    }

    @Override
    public PersonBuilder withAddress(String address)     {
        person.setAddress(address);
        return this;
    }
}

注意,在这个例子里,Builder类的构建方法都返回其自身,这是为了实现 Fluent 风格,即可以通过链式调用来增加代码的可读性。在这里也可以看出建造者模式的一个优点,当一个对象用构造函数来创建时十分繁琐时,建造者模式可以提供一种“逐步骤”的创建过程,调用方在任一时刻只需关注单个部件的构建,并编写可读性较强的代码来创建整个对象。缺点也很明显,增加了代码行数。

在必要的时候,你可以引入Child Builder,同样以Builder的方式构建部件:

public class BuilderPattern
{
    public static void main(String[] args)
    {
        PersonBuilder builder = new ConcretePersonBuilder();
        Person mike = builder.withName("Mike")
                .withAge(18)
                .withBirthday(LocalDate.of(2003, 5, 12))
                .withAddress("Moon")
                .AddEducation()
                .withName("MIT")
                .withDescription("PhD")
                .build();
        System.out.println(mike.getEducation().getDescription());
    }
}

class Education
{
    private String name;
    private String description;
}

class Person
{
    private String name;
    private int age;
    private LocalDate birthday;
    private String address;
    private Education education;
}

class EducationBuilder
{
    protected Education education = new Education();
    protected Person person;

    public EducationBuilder(Person person)
    {
        this.person = person;
    }
    public EducationBuilder withName(String name)
    {
        education.setName(name);
        return this;
    }
    public EducationBuilder withDescription(String description)
    {
        education.setDescription(description);
        return this;
    }

    public Person build()
    {
        person.setEducation(education);
        return person;
    }

}

abstract class PersonBuilder
{
    protected Person person = new Person();

    public abstract PersonBuilder withName(String name);

    public abstract PersonBuilder withAge(int age);

    public abstract PersonBuilder withBirthday(LocalDate birthday);

    public abstract PersonBuilder withAddress(String address);

    public abstract EducationBuilder AddEducation();

    public Person build()
    {
        return person;
    }
}

class ConcretePersonBuilder extends PersonBuilder
{

    @Override
    public PersonBuilder withName(String name)
    {
        person.setName(name);
        return this;
    }

    @Override
    public PersonBuilder withAge(int age)
    {
        person.setAge(age);
        return this;
    }

    @Override
    public PersonBuilder withBirthday(LocalDate birthday)
    {
        person.setBirthday(birthday);
        return this;
    }

    @Override
    public PersonBuilder withAddress(String address)
    {
        person.setAddress(address);
        return this;
    }

    @Override
    public EducationBuilder AddEducation()
    {
        return new EducationBuilder(person);
    }
}

使用这种Child Builder的方法,你可以实现一个渐进式建造者:通过对返回值的Builder类型进行合理的安排,你可以规定Fluent模式中部件的构建顺序。这可以保证调用方创建的对象在业务上是合法的。

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *