原型模式

不通过new关键字来产生一个对象,而通过对象复制来生产对象的模式,原型模式的核心是clone方法,通过该方法进行对象的拷贝。

也就是先生产出一个包含大量共有信息的类对象,然后拷贝出副本修正细节信息,建立一个完整的个性对象。

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

原型模式通用类图

优缺点

  • 性能好
    原型模式是内存二进制流侧拷贝,要比直接new一个对象性能好很多,特别在一个循环体内产生大量的对象时,原型模式可以更好的体现其优点。
  • 逃避构造函数的约束
    直接在内存中拷贝,构造函数不会执行。优点和缺点都是减少了约束。

使用场景

需要频繁的new一个对象,并且该对象需要繁琐的数据准备时;
一个对象要提供给其他对象访问,各个调用者可能需要修改其值时;

原型模式一般与工厂方法模式一起出现,通过clone的方法来创建对象,然后由工厂方法提供给调用者。

实现

1
2
3
4
5
6
7
8
9
10
11
12
public class PrototypeClass implements Cloneable{
@Override
public PrototypeClass clone(){
PrototypeClass p = null;
try{
p = (PrototypeClass)super.clone();
}catch(CloneNotSupportedException e){
//TODO
}
return p;
}
}

注意事项

构造函数不会被执行

Object类的clone方法原理是从内存中(堆内存)以二进制流的方式进行拷贝,重新分配一个内存快,构造函数是不会被执行的。

浅拷贝

Object类提供的方法clone只是拷贝本对象,对于其内部的数组、引用对象都不拷贝还是指向原声对象的内部元素地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PrototypeClass implements Cloneable{
public ArrayList<String> arrayList = new ArrayList<>();

@Override
public PrototypeClass clone(){
PrototypeClass p = null;
try{
p = (PrototypeClass) super.clone();
}catch(CloneNotSupportedException e) ) {
//
}
return p;
}
}

深拷贝

深拷贝需要拷贝对象自身外,还需要拷贝对象的所有引用的对象。

通过clone方式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PrototypeClass implements Cloneable{
public ArrayList<String> arrayList = new ArrayList<>();

@Override
public PrototypeClass clone(){
PrototypeClass p = null;
try{
p = (PrototypeClass) super.clone();
p.arrayList = (ArrayList<String>) this.arrayList.clone();
}catch(CloneNotSupportedException e) ) {
//
}
return p;
}
}

通过序列化方式来实现,通过序列化将对象(该对象的类必须实现Serializable接口)写到一个流中,然后再从流里把对象读出来以实现深克隆。

1
2
3
4
5
6
7
8
9
public static <T extends Serializable> T clone(T t) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(t);

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (T)ois.readObject();
}

clone与final的冲突

当使用clone方法的时候,类的成员变量不可以增加final关键字。

拓展:
Java提高篇——对象克隆(复制)

建造者模式

建造者模式与工厂模式关注点不同,建造者模式关注零件的组装过程,工厂模式注重零件的创建过程。

定义

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

建造者模式通用类图

优点

  • 封装性良好
    使用建造者模式可以使得客户端不需要知道产品内部组成的细节。
  • 建造者独立,易扩展
    不同的实现类Builder是相互独立的,对系统的扩展有利。
  • 便于控制细节风险
    由于建造者独立,因此可以个性化,修改一个建造者不会对其他模块产生影响。

缺点

  • 产品的组成部分必须相同,限制了其使用范围
  • 产品内部变化复杂时,需要增加很多建造者类

使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时;
  • 多个部件或者零件都可以装配到一个对象中,但是产生的运行结果不同时;
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能;

实现

Product 产品类

1
2
3
4
5
public class Product{
public void doSomething(){

}
}

Builder抽象建造者

1
2
3
4
public abstract class Builder{
abstract void setPart(); //设置个性部分
abstract Product buildProduct();//建造产品
}

ConcreteBuilder 具体建造者

1
2
3
4
5
6
7
8
public class ConcreteBuilder extends Builder{
public void setPart(){
//...
}
pbulic Product buildProduct(){
return new Product();
}
}

Director 导演类

导演类起到封装的作用,避免高层模块深入到建造者内部的实现类。当建造者模式比较庞大时,导演类可以有多个。

1
2
3
4
5
6
7
8
9
public class Director{

public Product buildProductA(){
Builder b = new ConcreteBuilder();
b.setPart();
return b.buildProduct();
}

}

抽象工厂模式

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,抽象工厂模式可以生产多个等级的产品。

概念

产品等级:不同产品的种类就是不同的产品等级(电视机和洗衣机就是不同的产品等级)
产品族:一个具体工厂生产的不同等级的产品(工厂生产的电视机和洗衣机属于一个产品族)

产品等级和产品族

定义

一种为访问类提供一个创建一组相关或者相互依赖对象的接口,且访问类无需指定所需要产品的具体类就能得到同族的不同等级的产品。

抽象工厂模式类图

使用场景

1) 客户端不依赖于产品类实例如何被创建、实现
2) 强调一系列相关的产品(同一产品族)一起使用创建对象需要大量的代码
3) 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
一个对象族都有相同的约束,则可以使用抽象工厂模式。

实现

产品角色实现

抽象产品:Product,定义产品的规范,描述产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品:ConcreteProduct,实现抽象产品角色所定义的接口由具体工厂来创建,与具体工厂之间是一对多的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract Product{
//all product base filds & methods.
abstract int on();
abstract int off();
}

public abstract AbstractTvProduct extends Product{
abstract int switchChannel();
}

public abstract AbstractFridgeProduct extends Product{
abstract int increaseTemp();
abstract int decreaseTemp();
}

public HaierTVProduct extends AbstractTvProduct{

}

public HaierFridgeProduct extends AbstractFridgeProduct{

}

public TCLTVProduct extends AbstractTvProduct{

}

public TCLFridgeProduct extends AbstractFridgeProduct{

}

工厂角色实现

抽象工厂:Abstract Factory,提供了创建产品的接口,包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂:实现抽象工厂中的多个抽象方法,完成具体产品的创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IFactory{
Product createTV();
Product createFridge();
}


public class HaierFactory implements IFactory{
Product createTV(){
return new HaierTVProduct();
}
Product createFridge(){
return new HaierFridgeProduct();
}
}

public class TCLFactory implements IFactory{
Product createTV(){
return new TCLTVProduct();
}
Product createFridge(){
return new TCLFridgeProduct();
}
}

优缺点

  • 隔离了具体类的生成,使得客户并不需要知道什么被创建,具有良好的封装性。
  • 如果要扩展产品族(有新的工厂要生产电视和冰箱时)的时候,只需要增加具体的产品类和实现工厂方法即可,原有的代码不需要变动,符合开闭原则。
  • 如果要扩展产品等级时,从抽象产品到工厂类,原有的代码都需要变动,违背了开闭原则。

参阅:
抽象工厂模式(详解版)

场景

  • new 对象的替代方案
  • 需要灵活、可扩展的框架时
  • 在异构项目中,从外部产生的对象
  • 测试驱动开发框架下使用。测试驱动开发 -TDD

工厂方法模式

定义

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

工厂方法模式的通用类图

抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;
Creator为抽象的创建类,即抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。

优点

  • 良好的封装,代码结构清晰,降低模块间的耦合;
  • 良好的扩展性;
  • 屏蔽具体的产品类,只要接口不变,系统中上层模块就不需要发生变化。

高层模块只需要知道产品的抽象类,不关心实现类,符合迪米特法则;
只依赖产品的抽象,符合倒置原则;
使用产品子类替换父类时也没有问题,符合里氏替换原则;

缺点

对于不同类型的产品都要增加不同类型的工厂来管理,增加代码的复杂性

实现

通用实现

产品类:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Product{
public abstract void method1();
public void method2(){}
}

public class Product1 extends Product{
public void method1(){}
}

public class Product2 extends Product{
public void method1(){}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class Factory{
public abstract <T extends Product> T create(Class<T> c);
}

public class ConcreteFactory extends Factory{

public <T extends Product> T create(Class<T> c){
Product p = null;
try{
p = (Product)Class.forName(c.getName()).newInstance();
}catch(Exception e){

}
return (T)p;
}
}

使用:

1
2
Factory factory = new ConcreteFactory();
Product p = factory.createt(Product1.class);

缩小为简单工厂

去掉抽象的Factory类,create()方法变为静态方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
public void Factory{

public static <T extends Product> T create(Class<T> c){
Product p = null;
try{
p = (Product)Class.forName(c.getName()).newInstance();
}catch(Exception e){

}
return (T)p;
}
}

调用代码:

1
Product p = Factory.create(Product1.class);

升级为多个工厂类

遇到复杂情况下,一个工厂类初始化需要耗费很多资源,这样可以将工厂类细化拆分。

1
2
3
4
5
6
7
8
9
10
11
12
public class Factory1 extends Factory{
public static Product create(){
return new Product1();
}

}

public class Factory2 extends Factory{
public static Product create(){
return new Product2();
}
}

这样的好处是职责清晰,结构简单,但是可扩展性和可维护性差了些。
一般情况下,再增加一个协调类,避免调用者直接与各个子工厂类交流,协调类的作用是封装子工厂类,对高层模块提供统一的访问接口。

替代单例模式

通过反射的方式来创建唯一一个实例

延迟初始化

一个对象被使用完毕后,并不立即释放,工厂类保持其初始状态,等待再次被使用。

延迟加载框架可以被扩展为,限制某个产品类的最大实例化数量(如链接池的最大链接数量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ProductFactory{

private static final Map<String,Product> pMap = new HashMap();

public static synchronized Product createProduct(String type) throws Exception{
Product p = null;
if(pMap.containsKey(type)){
p = pMap.get(type);
}else{
if(type.equals("Product1")){
p = new Product1();
}else{
p = new Product2();
}
}
return product;
}
}

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

https://www.zhihu.com/question/24843188

https://www.jianshu.com/p/83ef48ce635b

https://blog.csdn.net/buyulian/article/details/79203880

https://blog.csdn.net/qianhaifeng2012/article/details/52014773

https://www.cnblogs.com/aspirant/p/8980573.html

http://www.akathink.com/2016/08/01/%E5%BD%BB%E5%BA%95%E6%90%9E%E6%87%82%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/

单例模式

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“混乱”。

  • 要求生成唯一序号的场景
  • 整个项目中需要一个共享数据,如计数器
  • 创建对象要消耗过多资源(IO访问或者数据库资源等)
  • 定义大量的静态常量和静态方法的工具类

定义

确保一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例。

优点

单例模式在内存中只有一个实例,减少了内存开支。
当一个对象需要频繁地创建、销毁时,而且创建或者销毁时的性能无法优化时,单例模式的优势非常明显。

缺点

没有接口,无法扩展,没有接口也不能使用Mock方式虚拟对象。
在并行开发环境中,单例模式没有完成无法进行测试。
与单一职责原则冲突,单例模式把业务逻辑和单例混合在一个类中。

注意事项

高并发情况下,单例模式的线程同步问题。

提前加载

1
2
3
4
5
6
7
8
9
10
public class Singleton{

private static Singleton instance = new Singleton();

private Singleton(){}

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

延后加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton{

private volatile static Singleton instance = null;

private Singleton(){}

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

}

枚举

1
2
3
public enum Singleton{
instance;
}

创建软件应用程序时为了满足不断变化和发展的需求,一个成功的应用程序应该提供一种简单的方法来扩展它以满足不断变化的期望。

SOLID设计原则

单一职责原则

有且仅有一个原因引起类的变更。

问题:
Class IUser 负责两个不同的职责,职责1维护User的属性(id, name, age),职责2维护DB操作(save/update/delete/find).
当由于职责1需求发生改变要增加一个sex的时候,有可能会导致原本运行正常的职责2功能发生故障。

解决方法:
将IUser拆分为 UserInfo和UserDao。

优点:
降低类的复杂性,一个类只负责一项职责;
提高类的可读性,提高系统的可维护性;
降低变更引起的风险,当修改一个接口的功能时,可以降低对其他功能的影响。

开闭原则

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
也就是说通过扩展来实现变化,而不是通过修改已有的代码来实现变化。

可以通过抽象约束、封装变化来实现开闭原则。
通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

里氏替换原则

派生类型必须完全可替代其基类型。在设计模块和类时,必须确保派生类型从行为的角度来看是可替代的。当派生类型被其父类替换时,其余代码就像它是自类型那样使用它。

  • 子类必须完全实现父类的方法
  • 子类可以有自己的个性
  • 覆盖或者实现父类的方法时,输入参数可以被放大
  • 腹泻或者实现父类的方法时,输出结果可以被缩小

接口隔离原则

客户端不应该依赖于它所不需要的接口。接口隔离原则减少了代码耦合,使软件更强壮,更易于维护和扩展。接口隔离原则要求接口的定义和设计尽量的细化。

  • 接口尽量小,但是要有限度。
    对接口进行细化可以提高程序设计的灵活性,但是如果过小,会造成接口数量过多,使设计复杂化。
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,不需要的方法隐藏起来,只有专注地为一个模块提供定制服务,才能建立最小依赖关系。
  • 提高内聚,减少对外交互。使接口用最少的方法完成最多的事情。

依赖倒置原则

高级模块不应该依赖低级模块,两者都应该依赖抽象。
抽象不应该依赖于细节,细节应该依赖于抽象。

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,器依赖关系是通过接口或者抽象类产生。
  • 接口或者抽象类不依赖实现类
  • 实现类依赖接口或者抽象类

创建型模式(6/6)

行为型模式(12/12)

结构型模式(7/7)