七大设计原则
一、单一职责原则
各司其职
降低类的复杂性
提高可读性
1、类级别:一个类只负责它自己的事
2、方法级别:一个类中每个方法只负责它自己的事
二、接口隔离原则
根据实现类被依赖调用的情况,将一个类中的多个方法,拆分到多个接口中。
不需要实现的方法,是可以拆分的。
三、依赖倒装原则
面向对象、面向接口
依赖于抽象,而不是具体的实现
尽可能地调用接口方法,而不是具体的实现类,接口设计相对于稳定,且可拓展。
四、里氏替换原则
子类尽量不重写父类方法
若子类重写父类方法特别多
可以将两个类放在同一级别,继承于同一个类
不将二者看为继承关系,而是组合关系
降低耦合性
五、开闭原则
对拓展开放、对修改关闭
要增加一个功能,要通过拓展而不是修改代码
六、迪米特法则
内部依赖的类不要对外暴露逻辑
七、合成复合原则
要使用别的类的方法,尽量使用依赖、聚合等方式调用,而不是继承它。
设计模式
一、单例模式
1、饿汉式(静态变量)
内部定义静态变量,初始化直接new一个
对外暴露获取的方法
缺点:
类装载时执行,可能造成懒加载无效,内存浪费
2、饿汉式(静态代码块)
设置静态代码块
3、懒汉式(线程不安全)
需要的时候,判断是否有实例,再去创建实例
多线程不安全
4、懒汉式(线程安全 – 同步方法)
获取实例的方法上加synchronized ,防止多个线程进入判断内部
5、懒汉式( 并不安全 – 同步代码块)
在get方法内,在创建时给创建的语句代码块加锁
多线程并不安全
依旧会使多个线程进入判断内部产生多个实例
6、双重检查
volatile 加在内部静态变量上,保证可见性
判断对象存在、加锁同步代码块、内部再进行对象=null 的判断
7、静态内部类
设置一个静态内部类X,X内部设置了直接new的静态变量对象。
静态内部类只有第一次加载类时初始化,JVM保证线程安全
8、枚举
设为枚举类
enum Singleton{
INSTANCE;
}
不仅方法能正常使用,还能保证线程安全和单例,防止反序列化。
二、工厂模式
1、简单工厂模式
工厂类
- 工厂类: 模式核心,创建产品
- 抽象产品:具体的几种产品的抽象类父类或接口
- 具体产品:工厂类生产的实例
建立一个工厂类,想获取什么样的实例对象,告诉工厂
由唯一的一个工厂类去创建各种实例
缺点: 新增一种,都要修改工厂中的方法
静态工厂模式:将生产产品的方法设为静态
2、工厂方法模式
多个工厂类+工厂接口(抽象类)
-
抽象工厂 AbstractFactory: 具体工厂必须实现的接口或者必须继承的父类(抽象类)
-
具体工厂 Factory:生产具体产品的对应工厂类
-
抽象产品 AbstractProduct:具体产品必须实现的接口或者必须继承的父类(抽象类)
-
具体产品 Product:具体工厂生产的实例
由于种类的复杂性
根据不同种类,创建不同的工厂
饮品有:牛奶工厂、茶叶工厂、果汁工厂……
各种工厂都实现同一个工厂接口,由工厂接口规定基本的方法。
缺点: 每增加一个类就要增加一个对应的具体工厂类
3、抽象工厂模式
多个工厂类+工厂接口(抽象类)
每个产品类使用多个不同的工厂实例
-
抽象工厂 AbstractFactory:具体工厂类必须实现的接口或者必须继承的父类(抽象类)
-
具体工厂 ConcreteFactory:生产不同产品族
-
抽象产品 AbstractProduct:一个产品家族,每一个具体工厂都能够生产一整组产品。
-
具体产品 Product:由相同或不同产品组的实例组成
由于产品生产复杂性
方法不一样,需要定义产品族,也就是将产品分类
比如:
小明家(工厂接口):土豆地(工厂)、白菜地(工厂)、猪圈(工厂)……
小红家(工厂接口):土豆地(工厂)、白菜地(工厂)、猪圈(工厂)……土豆回锅肉 = 猪肉(小明家的猪)+ 土豆(小红家的土豆)
醋溜土豆丝 = 土豆(小明家的土豆)
……
每个工厂提供不同类别的具体实例
每个产品,内部定义它自己所需要的工厂,需要怎样的实例。
工厂方法模式和抽象工厂模式的区别:
- 工厂方法模式中一个工厂只能生产一种实例,而抽象工厂模式能生产多种实例
- 工厂方法模式中每个工厂生产出的实例就是一个单独的产品,而抽象工厂模式中虽然每个工厂创建的实例也属于一种产品,但重点解决的是由不同工厂生产的实例
组合
起来的复杂产品。
三、原型模式
实现 Cloneable 接口
重写clone()方法
获取复制体时,只要调用clone方法 完成对类及其属性的复制
浅拷贝:
clone提供的拷贝只针对基本数据类型
类的对象属性、集合属性等,并没有另外开辟空间
仍旧指向原来的属性地址
深拷贝:
集合类需要重写clone,集合类也能直接使用clone,都实现了 Cloneable
完全实现深拷贝
重写clone
:对所有引用类型都需要clone,很麻烦。
实现序列化
:以流的方式写入,再以流读出,返回一个新的对象。
应用场景:
在Spring中bean的创建,xml定义 scop=“prototype” 可以使用原型模式创建bean
在 doGetBean方法里,相对的有singleton单例创建
四、建造者模式
将 产品 和 产品生产 分离
动作 与 对象 不应在一个类中,将具体的动作细节封装
- 抽象建造者 Builder:规定建造中各种 “步骤” 的接口
- 具体建造者 ConcreteBuilder:实际上 步骤 的执行者,不同的建造者实现步骤的细节逻辑不一样
- 指挥者 Director:构建一个使用 Builder 接口的对象。主要有两个作用,一是隔离用户与对象的生产过程,二是负责控制产品对象的生产过程。
- 产品角色 Product:要创造的对象实体
注重 建造
的过程,产品差异不大,区别体现在生产过程中。
不同的建造者,方法实现的细节不一样 、方法的执行顺序不一样
生产出不一样的产品,注重在拓展性。
而抽象工厂模式关心工厂生产出什么样的产品
,产品有明显区分,封装生产过程。
注重在松耦合
StringBuilder 应用:
- Appendable 定义了 append方法 – 抽象建造者
- AbstractStringBuilder 实现了 append 方法 – 具体建造者
- StringBuilder 不仅为具体建造者,也是 指挥者 。 建造者角度:它继承父类方法,指挥者角度:调用了方法。
五、适配器模式
被适配的类
适配器
用户
类、对象、接口三种适配模式
举例:转接头
让原本不适用的接口能够兼容调用
用户只关系适配器能否给出对应的接口,而不管适配器是怎么兼容用户本不支持的接口的。
- 目标接口(Target):用户期望使用的抽象类或者接口
- 适配器(Adapter):包装需要适配的对象,把原接口转换成目标接口。
- 需要适配的类(Adaptee):需要适配的类。
1. 类适配器
适配器 继承
被适配类
可以通过重写
父类方法等方式达到适配效果
缺点: 适配器和被适配类,二者由于继承
关系耦合度变高了。
2. 对象适配器
持有实例
代替 继承
(合成复用原则)
适配器不再继承被适配类,而是在内部引入其实例再进行“兼容”。
3. 接口适配器
适配器实现接口,默认写好每一个方法空的或者其它。
用户在用接口适配器 的时候,只需要重写自己需要的方法
覆盖,而不用针对某个接口实现所有的方法
。
应用场景:
SpringMvc中,每种Controller都配有一个适配器,这些适配器实现了同一个接口
不同的适配器针对对应的Controller都实现了 handle
方法,DispatcherServlet 只关心执行handle
方法,不管适配器是怎么利用Controller的。
DispatcherServlet 根据Controller的类型判断它应该调用何种适配器,然通过执行handle
方法得到 ModelAndView
六、桥接模式
聚合代替继承
结构型模式
理论上:
抽象类 + 多个子类
实现类接口 + 多种实现类
抽象接口和具体实现分开。
在抽象类需要用到具体的实现类方法时,通过组合依赖实现类,而不是继承实现类。
比如:
做奶茶(抽象类):奶茶步骤(实现类接口)
做珍珠奶茶(抽象类子类):珍珠奶茶步骤(某个实现类)
做烧仙草(抽象类子类):烧仙草步骤(某个实现类
……
奶茶步骤(实现类接口)
奶茶步骤又有很多种:
珍珠奶茶步骤(某个实现类)、烧仙草步骤(某个实现类)、杨枝甘露步骤(某个实现类)……
最开始只知道做奶茶是有奶茶步骤的,但具体是什么,暂时不知道。
直到知道要做的是珍珠奶茶,就选用珍珠奶茶步骤,把具体的实现类注入抽象父类,然后在做珍珠奶茶的子类中super通过父类调方法。
不管是抽象类拓展到了多个,还是实现类有多少种手段
,两方都只需要拓展新类
,而不需要修改。
符合“开闭原则”。
应用举例:
JDBC属于不太标准的桥接模式
Driver 类中 DriverManager 要支持注册多种数据库链接,先指定一个Connection接口,不同数据库链接的实现类(ConnectionImpl)都实现了jdbc的Connection接口。
这样,在注册数据库链接时,只要是实现了jdbc的Connection接口的链接,都可以注册。
而DriverManager 没有抽象父类也没有子类,但作为抽象者它确实可以灵活地支持各种实现类。
七、装饰者模式
目的:动态地将新功能附加到对象上
实现:大肠包小肠、套娃……
- Component:被装饰者
抽象类
- ConcreteComponent:实际的被装饰者
Component的子类,一般有多种需要被装饰的对象
- Decorator:装饰者
抽象类,Component的子类,内聚了一个Component对象,并且定义了针对装饰者的方法
- ConcreteDecorator:实际的装饰者
Decorator的子类,同样也具有使用父类中Component对象的权利。
一般有多种类型的装饰者。
将需要装饰的对象,通过构造方法等方式,传入后得到一个新的对象。
即视为完成装饰
这一个过程。
举例:
饮品
奶茶 extends 饮品
果汁extends 饮品
……调料 extends 饮品 (内部包含一个饮品对象)
珍珠 extends 调料
燕麦 extends 调料
白糖 extends 调料
……
奶茶 drink = new 奶茶 ();
drink = new 珍珠(drink); 得到 带珍珠 的奶茶
drink = new 白糖(drink);得到 带白糖、带珍珠 的奶茶
……
使用的场景:
装饰者与被装饰者之间,具有某个或多个相同的特性
比如上述例子中计算加了多种调料后,奶茶的总价格是多少?
这里的相同特性就体现在他们都具有价格
。
应用场景:
JDK中InputStream就是被装饰者