文章目录
面向对象设计原则
书中介绍了七条十分具有代表性的面向对象设计原则,遵循这些原则可以更好地实现可维护性复用。这些原则也作为实现和评价一个设计模式的准则。
- 单一职责原则 Single Responsibility Principle
- 一个类只负责一个功能领域中的相应职责,用于控制类的粒度大小。
- 实际使用中需要根据情况确定合适的粒度,将不同的功能封装在不同的类中。
- 开闭原则 Open-Closed Principle
- 软件实体应当对扩展开发,对修改关闭。即在扩展时无须修改现有代码。
- 开闭原则需要通过接口、抽象类等机制来实现,具体类的扩展不影响抽象层的变化。
- 里氏代换原则 Liskov Substitution Principle
- 子类对象可以透明地代替基类(父类)对象。
- 强调在程序中应该多使用基类类型定义,使用子类实例代之,以获得更高的扩展性。
- 依赖倒转原则 Dependency Inversion Principle
- 针对接口编程,使抽象不依赖于具体而具体依赖于抽象。该原则主要提供了一种实现抽象化的机制。
- 依赖注入(Dependency Injection)体现了依赖倒转原则。针对抽象层(接口)编程时,由DI机制来注入具体对象,在抽象层中不含注入细节。
- 接口隔离原则 Interface Segregation Principle
- 接口职责单一。
- 不使用过大的接口,而是定义细化的、专门的接口。
- 合成复用原则 Composite Reuse Principle
- 尽量使用组合,而不是继承。组合是黑箱调用,基础是白箱调用。
- 首先考虑组合,慎用继承。只有具有明显is-A关系才使用继承。
- 迪米特原则 Law of Demeter
- 尽可能减少对象间交互。对象应当只与自身或较为亲密的对象(如成员对象)交互。
- 应当减少对象间的直接通讯,或以中间类实现通讯。每个类尽量隐蔽自身和减少对外部的引用,只要可能就应当设计为不可变类。
单例模式 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两个主题。可以进行如下类设计:
- Button
- ButtonDark
- ButtonLight
- Label
- LabelDark
- LabelLight
- TextEditor
- TextEditorDark
- TextEditorLight
其中,有三个产品类别,分别是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 接口。
原型模式的主要角色有如下几个:
- 抽象原型:所有的原型类型都实现自该抽象层(接口、抽象类/具体类)
- 具体原型:实现Clone方法的原型类,这些原型类都实现自抽象原型
- 原型管理器(可选):管理多个具体原型对象,提供这些对象的复制
下面的代码实现了一个通过序列号实现深克隆的原型模式,其中原型管理器使用了单例模式。
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模式中部件的构建顺序。这可以保证调用方创建的对象在业务上是合法的。