设计模式就是针对某类特定问题的代码设计经验。
复用成功的设计模式,可以减低开发成本和周期、提高复用、代码适用拓展和可维护性。
设计原则
-
单一职责原则
一个类只有一个引起他的变化。如果一个类承担太多责任,耦合度太高,一个职责变化可能会影响到其他职责。
-
开闭原则
对修改关闭,对拓展开放。
每次发生变化,通过添加代码增强实现,不是修改原代码
-
里氏替换原则
任何出现父类的地方都可以用他的子类替代
-
迪米特原则
也叫最少知道原则。只与你的朋友通信,不要和陌生人说话。外观模式和中介模式都是用了迪米特法则。
-
接口隔离原则
一个接口尽量实现单一的功能。不要把把多个功能放入一个总接口中,而应该把每个职责分离到多个专门的接口中,进行接口分离
-
依赖倒置原则
面向接口编程而不是面向实现编程,可以降低耦合性。
细节依赖于抽象,抽象依赖于细节
-
合成复用原则
在一个新对象中使用已有的对象,让他成为新对象的部分。尽量使用合成、聚合,尽量不要使用继承。
创建型
单例模式
实现一个类只有一个实例,并提供全局访问。
复制代码
类图
双重检查机制实现
public class Singleton {
private Singleton() {
};
private volatile static Singleton singleton = null;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
复制代码
静态内部类实现
public class SingletonInnner{
private SingletonInnner(){};
static class SingletonHolder{
private final static SingletonInnner instance = new SingletonInnner();
}
public static SingletonInnner getInstance(){
return SingletonHolder.instance;
}
public static void main(String args[]) {
SingletonInnner s1, s2;
s1 = SingletonInnner.getInstance();
s2 = SingletonInnner.getInstance();
System.out.println(s1 == s2);
}
}
复制代码
枚举类型写法
public enum SingletonEnum {
INSTANCE;
public void test(){
System.out.println("hello world");
}
public static void main(String[] args) {
SingletonEnum.INSTANCE.test();
}
}
复制代码
优点
- 提供对唯一实例的受控访问
- 在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能
- 可扩展,如实现双实例、多实例等
缺点
- 单例类职责过重,一定程度上违背了单一职责原则
简单工厂模式
简单工厂又是静态方法模式,负责生产对象的一个类,称为工厂类。
类实例化和类使用分开,使用者不必知道具体怎样产出所需产品类。
类图
实现
//抽象产品
public abstract class Product {
public abstract void show();
}
复制代码
//产品A
public class ProductA extends Product {
@Override
public void show() {
System.out.println("show A");
}
}
复制代码
//产品B
public class ProductB extends Product {
@Override
public void show() {
System.out.println("show B");
}
}
复制代码
//工厂类
public class Factory {
public Product getProduct(String productName) {
if ("A".equals(productName)) {
return new ProductA();
}
if ("B".equals(productName)) {
return new ProductB();
}
return null;
}
}
复制代码
//使用
public class Main {
public static void main(String[] args) {
Factory factory = new Factory();
Product productA = factory.getProduct("A");
productA.show();
Product productB = factory.getProduct("B");
productB.show();
}
}
复制代码
优点
- 将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,实现了解耦
- 把初始化实例时的工作放到工厂里进行,使代码更容易维护。
- 符合面向对象的原则、 面向接口编程,而不是面向实现编程。
缺点
- 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
- 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
- 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构
适用场景
- 客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时;
- 当工厂类负责创建的对象(具体产品)比较少时。
工厂方法模式
通过定义工厂父类负责定义创建对象的接口,而子类负责生成具体的对象。
将类的实例化延迟到工厂类的子类中完成,由子类觉得应该实例化哪个类。
类图
实现
//抽象产品
public abstract class Product {
public abstract void show();
}
复制代码
//产品A
public class ProductA extends Product {
@Override
public void show() {
System.out.println("show A");
}
}
复制代码
//产品B
public class ProductB extends Product {
@Override
public void show() {
System.out.println("show B");
}
}
复制代码
//抽象工厂类
public abstract class AbstarctFactory {
public abstract Product getProduct();
}
复制代码
//工厂A
public class FactoryA extends AbstarctFactory {
@Override
public Product getProduct() {
return new ProductA();
}
}
复制代码
//工厂B
public class FactoryB extends AbstarctFactory{
@Override
public Product getProduct() {
return new ProductB();
}
}
复制代码
//使用
AbstarctFactory factoryA = new FactoryA();
Product productA1 = factoryA.getProduct();
productA1.show();
AbstarctFactory factoryB = new FactoryB();
Product productB1 = factoryB.getProduct();
productB1.show();
复制代码
优点
- 符合开闭原则。当新增一种产品时,新增对应的产品和对应的工厂即可
- 单一职责原则,一个工厂只生产对应的产品
- 不使用静态工厂方法,可以形成基于继承的等级结构
缺点
- 添加新产品时,要增加新产品类和提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类
- 一个具体工厂只能创建一种具体产品
适用场景
- 当一个类不知道它所需要的对象的类时 在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可;
- 当一个类希望通过其子类来指定创建对象时
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
抽象工厂模式
抽象工厂相对于工厂方法模式来说,抽象工厂中每个工厂可以创建多种类的产品;而工厂方法每个工厂只能创建一类。这里略过...
类图
建造者模式
将一个复杂对象的构建与他的表示分离,使得用同样的构建过程可以创建不同的表示
在用户不知道对象的建造过程和细节的情况下就可以直接创建出复杂的对象。
用户只需给出对象的类型和内存,建造者模式负责按顺序创建复杂对象。
类图
实现
//product
//套餐类
public class Meal {
private String food;
private String drink;
public String getFood() {
return food;
}
public void setFood(String food) {
this.food = food;
}
public String getDrink() {
return drink;
}
public void setDrink(String drink) {
this.drink = drink;
}
}
复制代码
//builder
//套餐构造器
public abstract class MealBuilder {
Meal meal = new Meal();
public abstract void buildFood();
public abstract void buildDrink();
public Meal getMeal() {
return meal;
}
}
复制代码
//具体产品A
//套餐A
public class MealA extends MealBuilder{
@Override
public void buildFood() {
meal.setFood("一盒薯条");
}
@Override
public void buildDrink() {
meal.setDrink("一杯可乐");
}
}
复制代码
//Direct
//肯德基服务员,相当于一个指挥者
public class KFCWaiter {
private MealBuilder mealBuilder;
public void setMealBuilder(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
public Meal construct(){
//准备食物
mealBuilder.buildFood();
//准备饮料
mealBuilder.buildDrink();
//准备完毕,返回一个完整的套餐给客户
return mealBuilder.getMeal();
}
}
复制代码
//使用
public class Main {
public static void main(String[] args) {
//服务员
KFCWaiter waiter = new KFCWaiter();
//套餐A
MealA a = new MealA();
//服务员准备套餐A
waiter.setMealBuilder(a);
//获得套餐
Meal mealA = waiter.construct();
System.out.print("套餐A的组成部分:");
System.out.println(mealA.getFood()+"---"+mealA.getDrink());
}
}
复制代码
优点
- 解耦
- 精确控制对象的创建
- 扩展,新增具体构建者无需修改原来的代码
缺点
- 使用范围受限,产品间差异大的不适合使用建造者模式
- 内部变化复杂,会有很多的建造类。
使用场景
- 需要生产的对象具有复杂的内部结构,常用的StringBuffer、StringBuilder、以及Swagger(一种接口文档),都是以这种模式构建对象的
- 构建具有共同特性的复杂对象
原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
类图
浅克隆
深克隆
优点
- 创建新的对象比较复杂时,可以简化对象创建过程,提高效率
- 使用深克隆保持对象的状态
缺点
- 深克隆时可能需要很复杂的代码:原型模式最大的缺点就在于每个原型的子类都必须实现clone的操作,尤其在包含引用类型的对象时,clone方法会比较麻烦,必须要能够递归的让所有的相关对象都要正确的实现克隆。
使用场景
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得