设计模式基于六大原则:
- 单一职责原则:一个类应该只做一件事,一个类应该只有一个引起它修改的原因。
- 开闭原则:一个软件实体如类、模块、函数应该对扩展开发,对修改关闭。
- 依赖倒置原则:细节应该依赖于抽象,抽象不应该依赖于细节。把抽象层放在程序设计的高层,并保持稳定,程序的细节变化由底层的实现层来完成。
- 里氏替换原则:子类应该可以完全替换父类,也就是说在使用继承时,只扩展新功能,而不要破坏父类原有的功能。
- 迪米特原则:又叫最少知道原则,一个类不应该知道自己操作的类的细节,换言之,只和朋友谈话,不和朋友的朋友谈话。
- 接口隔离原则:客户端不应依赖它不需要的接口。即如果一个接口实现时,部分方法由于冗余被客户端实现了,则应该将接口拆分,让实现类只依赖自己需要的接口方法。
所有的设计模式都是为了程序能够更好的满足这六大原则,设计模式一共有23种,我们先来学习五种构建性模式,分别是:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
5种构建型模式
工厂方法模式
工厂方法模式规定每个产品都有一个专属工厂,比如苹果有专属的苹果工厂,梨子有专属的梨子工厂,如下所示:
public class AppleFactory{
public Fruit create(){
return new Apple();
}
}
public class PearFactory{
public Fruit create(){
return new Pear();
}
}
public class User{
private void eat(){
AppleFactory appleFactory = new AppleFactory();
Fruit apple = appleFactory.create();
PearFactory pearFactory = new PearFactory();
Fruit pear = pearFactory.create();
apple.eat();
pear.eat();
}
}
优点:调用者无需知道工厂的生产细节,当生产过程需要更改时也无需更改调用端;每一个产品对应一个工厂,当产品越来越多时,工厂类也会越来越多,符合单一职责原则,也符合开闭原则。
抽象工厂模式
它是对工厂方法模式的优化,比如提取出工厂接口:
public interface IFactory {
Fruit create();
}
public class AppleFactory implements IFactory {
@Override
public Fruit create(){
return new Apple();
}
}
public class PearFactory implements IFactory {
@Override
public Fruit create(){
return new Pear();
}
}
public class User {
private void eat(){
IFactory appleFactory = new AppleFactory();
Fruit apple = appleFactory.create();
IFactory pearFactory = new PearFactory();
Fruit pear = pearFactory.create();
apple.eat();
pear.eat();
}
}
优点:客户端只需要和抽象类IFactory打交道,调用的是其接口中的方法,使用的时候不需要知道是在哪个具体工厂中实现的这些方法,这就使得替换工厂变得非常容易。抽象工厂模式很好的发挥了开闭原则、依赖倒置原则。
缺点:抽象工厂模式太重了,如果IFactory接口需要新增功能,则会影响到所有的具体类。所以抽象工厂模式适用于增加同类工厂这样的横向扩展需求,不适合新增功能这样的纵向扩展。
单例模式
饿汉式写法是线程安全的,懒汉式写法线程不安全,我们可以使用double check的方式保证懒汉式写法线程安全,写法如下:
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
//必须要在外层加判空,要不然每次调用都加锁,影响执行效率
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
饿汉式类加载的时候就创建了对象,需要立即占用内存空间,而懒汉式只有需要使用的时候才会创建对象占用内存控件的。
除了双检锁方式以外,我们还可以使用静态内部类方式保证懒汉式单例的线程安全:
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder{
public static Singleton instance = new Singleton();
}
}
那静态内部类的写法是怎么实现懒加载的,又是怎样保证线程安全的呢?Java类的加载过程包括:加载、验证、准备、解析、初始化,在初始化阶段包括为类的静态变量赋初始值和执行静态代码块中的内容,但不会立即加载内部类,内部类在使用时才加载,所以当次Singleton类加载时,SingletonHolder并不会被立即加载,所以不会像饿汉式那样占用内存。另外,Java虚拟机规定,当访问一个类的静态字段时,如果该类尚未初始化,则立即初始化此类。第二个问题的答案是,Java虚拟机的设计是非常稳定的,早已经考虑了多线程并发执行的情况,类加载的过程,通过使用加锁、同步,保证线程安全。
一般建议:对于构建不复杂,加载完成后会立即使用的单例对象,推荐使用饿汉式;对于构建过程耗时较长,并不是所有使用此类都会用到的单例对象,推荐使用懒汉式。
建造者模式
用于创建过程稳定,但配置多变的对象。将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。比如我们要制作一杯珍珠奶茶,它的制作过程稳定,除了必须要知道奶茶的种类和规格外,是否加珍珠和是否加冰是可选的。使用建造者模式Java表示如下:
public class MilkTea {
private final String type;
private final String size;
private final boolean pearl;
private final boolean ice;
private MilkTea(Builder builder) {
this.type = builder.type;
this.size = builder.size;
this.pearl = builder.pearl;
this.ice = builder.ice;
}
public String getType() {
return type;
}
public String getSize() {
return size;
}
public boolean isPearl() {
return pearl;
}
public boolean isIce() {
return ice;
}
public static class Builder {
private final String type;
private String size = "中杯";
private boolean pearl = true;
private boolean ice = false;
public Builder(String type) {
this.type = type;
}
public Builder size(String size) {
this.size = size;
return this;
}
public Builder pearl(boolean pearl) {
this.pearl = pearl;
return this;
}
public Builder ice(boolean cold) {
this.ice = cold;
return this;
}
public MilkTea build() {
return new MilkTea(this);
}
}
}
优点:不用担心忘了指定某个配置,保证了构建过程是稳定的。在OkHttp、Retrofit等著名框架中都使用到了该模式。
原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
public class MilkTea {
public String type;
public boolean ice;
public MilkTea clone(){
MilkTea milkTea = new MilkTea();
milkTea.type = this.type;
milkTea.ice = this.ice;
return milkTea;
}
}
以上就是原型模式,Java中有一个语法糖,让我们并不需要手写clone方法,这个语法糖就是cloneable接口,我们只要让需要拷贝的类实现此接口即可。
public class MilkTea implements Cloneable{
public String type;
public boolean ice;
@NonNull
@Override
protected MilkTea clone() throws CloneNotSupportedException {
return (MilkTea) super.clone();
}
}
需要注意的是,Java自带的clone方法是浅拷贝,也就是调用此对象的clone方法,只有基本数据类型的参数会被拷贝一份,非基本类型的对象不会被拷贝一份,而是继续使用传递引得方式。如果需要实现深拷贝,必须自己手动修改clone方法才行。
总结
抽象工厂模式:适合横向增加同类工厂的扩展,不适合纵向增加功能的扩展(线程池ThreadPoolExecutors);
单例模式:适用于全局公用的对象(OKHTTP对象);
建造者模式:使用于构造过程稳定,配置多变的对象(各种Notification、Dialog的创建);
文章参考:设计模式