设计模式
一、七大设计原则
设计原则是设计模式的基础
单一设计原则
-
一个类之应该负责一项职责
-
避免过多的使用if_else(耦合性过高)
接口隔离原则
-
一个类对另外一个类的依赖,应该建立在最小的接口上
-
如果一个接口包含了很多方法,但是这个接口的实现类从逻辑或业务上只需要实现其中部分方法。
那么这个接口就违背了接口隔离原则,可以考虑将此接口拆了
依赖倒转原则
- 核心思想:面向接口编程,使用多态的特性:用接口去声明,真正使用时调用具体子类的具体实现
- 抽象属于高层,比细节实现,要更加的稳定
- 依赖关系传递的三种方式
- 方法参数接口传递
- 构造方法传递
- set注入
里氏替换原则
- 所有引用基类的地方必须能透明的使用其子类对象
- 子类尽量不要重写父类的方法
- 子类重写父类方法可能改变父类的行为,特别是这个被子类重写的方法还被父类的其它方法调用了,那么父类的其他方法在执行时,行为就完全不受控制了
- 让原来的父类和子类都继承一个更通俗的基类,把原有的继承关系去掉,采用依赖,聚合,组合等关系替代
- 也就是说,如果B类想使用A类中的方法,我们不让B类继承A类,但是我们传一个A类对象给B类,B类使用A类这个对象调用A类中的方法,这样B类和A类就没关系了,因此就不用担心方法被重写的问题了
开闭原则
- 最重要、最基础的原则
- 遵循其它原则的目的就是为了实现开闭原则
- 当功能需求变化时,应当:对扩展开放(对提供方而言),对修改关闭(对使用方而言)。
- 尽量通过扩展软件实体的行为来 实现变化,而不是通过修改已有代码
迪米特原则
- 最少知道原则
- 只与直接朋友通信(一个类中,如果要出现另外的类,最好只出现在以下3个位置)
- 方法参数
- 返回值
- 成员变量
- 一个类对自己依赖的类知道的越少越好
- 对于被依赖的类,都尽量将逻辑封装在类自己的内部,对外提供public方法
合成复用原则
-
尽量使用合成/聚合的方式,而不是使用继承
-
依赖、聚合、组合 ( B类要用到A类中的方法)
-
依赖
class B{ void invokeA( A a){ ... } }
-
聚合
class B{ A a; void set(A a){ this.a = a; } }
-
组合
class B{ A a = new A(); }
-
二、类的关系与UML图
依赖
- 只要A类有用到B类,那么就是A 依赖B ; 没有B类,A不能通过编译
- A ---------->B
泛化
- 就是继承关系
- A —空三角形 B
实现
- A -----空三角形 B
关联
- 实线
聚合
- 整体和部分可以分开
- 实线 + 空心菱形
组合
- 整体和部分不可分开,共生
- 实现 + 实心菱形
三、23种设计模式
分类
-
创建型模式
-
结构型模式
-
行为型模式
1. 单例模式
-
饿汉式 1 可以用
//饿汉式(静态变量) class Singleton { //1. 构造器私有化, 外部能new private Singleton() { } //2.本类内部创建对象实例 private final static Singleton instance = new Singleton(); //3. 提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { return instance; } }
如果该单例未被使用,则会造成内存浪费
-
饿汉式 2 可以用
//饿汉式(静态变量) class Singleton { //1. 构造器私有化, 外部能new private Singleton() { } //2.本类内部创建对象实例 private static Singleton instance; static { // 在静态代码块中,创建单例对象 instance = new Singleton(); } //3. 提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { return instance; } }
-
懒汉式 1 不能使用
class Singleton { private static Singleton instance; private Singleton() {} //提供一个静态的公有方法,当使用到该方法时,才去创建 instance //即懒汉式 public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
不安全
-
懒汉式2 不推荐使用
// 懒汉式(线程安全,同步方法) class Singleton { private static Singleton instance; private Singleton() {} //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题 //即懒汉式 public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
安全,但因为加了同步效率不高
-
懒汉式3 不能使用
使用同步代码块,不能解决线程安全问题
-
懒汉式4 推荐使用
使用同步代码块,双重检查
// 懒汉式(线程安全,同步方法) class Singleton { private static volatile Singleton instance; private Singleton() {} //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题 //同时保证了效率, 推荐使用 public static synchronized Singleton getInstance() { if(instance == null) { synchronized (Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } }
-
静态内部类 推荐使用
- 外部类加载,静态内部类不会加载
- 加载静态内部类时,在静态属性上实例化,同时类加载是线程安全的
// 静态内部类完成, 推荐使用 class Singleton { private static volatile Singleton instance; //构造器私有化 private Singleton() {} //写一个静态内部类,该类中有一个静态属性 Singleton private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE public static synchronized Singleton getInstance() { return SingletonInstance.INSTANCE; } }
-
枚举推荐使用
//使用枚举,可以实现单例, 推荐 enum Singleton { INSTANCE; //属性 public void sayOK() { System.out.println("ok~"); } }
2.工厂模式
-
简单工厂模式
-
定义一个创建对象的类,由这个类来封装实例化对象的代码
-
其它类需要对象时,只要聚合该简单工厂类对象,调用该简单工厂类创建并返回对象即可;
-
静态工厂
- 同上,将工厂类创建对象的方法改为静态即可
-
class PizzaStore{ // 工厂类
public Pizza createPizza(String type){ // 工厂类创建产品的方法
if(type="cheese"){
return new CheesePizza();
}else if(type="veggie"){
return new VeggiePizza();
}
}
}
abstract class Pizza{} // 抽象产品
class CheesePizza extends Pizza{} // 具体产品1
class VeggiePizza extends Pizza{} // 具体产品2
// 如果需要创建产品,那么只需要聚合该工厂类作为成员变量,那么使用工厂类对象的创建产品的方法创建产品了
// 将创建产品的细节封装到工厂类中,方便对创建实例时的统一管理(要修改的话,就修改他就行了)
- 工厂方法模式
- 定义一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类
- 直接使用工厂子类,会调用抽象类中的方法(找父类),然后该方法中会调用子类具体实现
public abstract class PizzaStore{
Pizza orderPizza(String type){
Pizza pizza = createPizza(String type);// 调用子类
pizza.prepare(); // 不变的部分(默认的行为)
pizza.bake();
pizza.cut();
pizza.box();
}
abstract Pizza createPizza(); // 交给子类实现
}
// 子类工厂1
class NYPizzaStore{
Pizza createPizza(String type){
Pizza nyStylePizza = new NyStylePizza(); // 生产具体产品
// 根据type...
return nyStylePizza;
}
}
public abstract class Pizza{ // 产品的抽象类
void pizza.prepare();
void pizza.bake();
void pizza.cut();
void pizza.box();
}
class NYStylePizza{ // 具体产品1
void pizza.prepare(){/*...*/}
void pizza.bake() {/*...*/}
void pizza.cut() {/*...*/}
void pizza.box() {/*...*/}
}
// 对于抽象类PizzaStore来说,它在orderPizza()方法里是直接面向接口的(并非具体实现),方便了松耦合
// 由客户端去决定该生产哪种产品,因此须找到具体的PizzaStore的子类,调用这个子类的创建对象的方法,这个子类就是具体工厂
// 如果需要扩展的话,那么只要新添子类去实现PizzaStore即可
- 抽象工厂模式
- 对工厂方法模式进一步抽象,直接将抽象方法定义在接口中,逻辑全部交给子类实现
3.策略模式
通过组合的方式,将需要实现的功能从本类中剥离出来成一个接口。
public class Duck{
// 少用继承(避免继承的局限性),多用组合。将功能的实现委托给其它具体实现类,使用接口引用具体行为。
FlyBehavior flybehavior;
void setFlyBehavior(FlyBehavior flybehavior){
this.flybehavior = flybehavior;
}
performFlyBehavior(){
this.flybehavior.fly(); // 这样可以使得Duck的子类的行为灵活多变,且可扩展,易于修改
}
}
interface FlyBehavior{ // 使用接口定义行为 ,将fly这种行为封装在一个类中 ,这样就可以让Duck类能引用一族算法
void fly;
}
比如:spring中就在解析xml文件的时候,就引入了一个BeDefinitionReader成员变量,专门用来读取bean的定义信息
shiro在给securityManager修改cacheManager的时候,就通知了realm。
4.观察者模式
当数据改变时,需要通知其它对象做出相应的动作。并且实现可扩展,松耦合
// 主题对象,包含需要被观察的数据(被观察者) ,此处可以向上抽取被观察者的接口。
class Subject{
Object data;
List<Observer> observerList; // 观察者列表
registerObserver(Observer observer){ // 注册
observerList.add(observer);
}
removeObserver(Observer observer){ // 移除
int idx = observerList.indexOf(observer);
if(idx>-1){
observerList.remove(idx);
}
}
notifyObserver(){ // 通知
for(observer : observerList){
observer.update(); // 可选择传递数据过去,或者直接将主题对象直接传过去,这里使用构造方法就传过去了
}
}
void SetData(){
// ... data数据变化
notifyObserver();// 数据变化时,通知观察者
}
}
// 观察者
interface Observer{
void update();
}
class ObserverImpl implements Observer{
Subject subject;
public ObserverImpl(Subject subject){
this.subject = subject; // 将主题对象传给观察者,方便观察者后期移除或添加
}
void update(Object args); // 实现接口,给主题对象调用
}
比如:spring容器中的BeanPostProcessor后置处理器就大量的采用了观察者模式;
gui中注册监听事件就是使用的是观察者模式
java中内置了观察者模式:Observable和observer两个类,方便了观察者模式的使用
5.装饰者模式
定义抽象装饰者抽象类继承自Beverage(与目标对象同类型),将目标对象组合到装饰者子类中(可以使用构造方法设置进去)。
然后使用装饰者代替目标对象即可。
特点:把自己的父类放到自己里面作为成员变量来使用。
因为装饰者和其他类都继承了抽象类,那么其实就不好分辨出到底谁是装饰者,那么通过看谁把父类放在成员位置来使用,那么就可以判断出谁是装饰者。
那么一般在设计装饰者的时候,就应该在构造器的时候就要求一定要传入目标对象才行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w9Qt4CNN-1593279094266)(assets/image-20200627205501837.png)]
abstract class Beverage{ // 抽象接口
void cost();
}
class HouseHold extends Beverage{ // 具体实现
void vost(){
// ...
}
}
class Decorator extends Beverage{ // 装饰者也实现同一套接口
Beverage beverage; // 将父类放到成员变量,并将具体实现传入
public AbstractDecorator(Beverage beverage){
this.beverage = beverage;
}
void cost(){
// ...前
beverage.cost();
// ...后
}
}
java.io中的FilterInputStream就是典型的装饰者模式。
还有Reader(抽象父类),FileReader(Reader的子类)BufferedReader(装饰者,同时也是Reader的子类),
BufferedReader可以传入FileReader,来改造FileReader中的方法,让其更加的强大,便于使用,比如读取一行的方法。
分析:为什么装饰者需要继承抽象父类?
如果不继承的话,也能用。但是只能被装饰一次,不能装饰完了之后再被其它装饰类装饰。采用了继承的话,就可以将装饰类对象当作被装饰的目标对象继续装饰下去。可扩展。