文章目录
1 设计模式
1 策略模式
策略模式:
为某个可以变化的功能,将其设计成一个接口
为该接口扩展出一系列实现类,在生成该对象时再赋予该对象特有的特征
让功能的实现与对象脱离,将对象和行为分离,以降低耦合,提高代码复用
如模拟一个鸭子院,有绿头鸭,红头鸭
起初设计每个鸭子都有各自的成员和方法,但随着鸭子数量的增多
需要维护设计太多类,为此可以考虑使用继承创建一个鸭子父类
所有的鸭子子类来继承它,并选择性重写父类中的方法
但是随着系统逐渐臃肿时,当需要更改父类中的一个方法时
成千上万个子类中的方法也需要被重写,继承牵一发而动全身
为此可以将飞行,鸣叫等行为做成接口,为这些接口做各种实现类
父类将这些接口作为成员,子类选择性的实例化这些接口的实现类,以完成不同的功能
并可以通过set方法来替换功能
2 观察者模式
观察者模式:
将系统中的角色分成了主题和观察者,观察者依附主题而工作,主题可以发布数据让所有观察者得到同一份数据,以得到更干净的设计,Swing和许多GUI框架都大量使用了观察者模式
参照报社的工作模式,观察者模式就等于 出版社+订阅者
出版社管理所有的订阅者,每当需要发布消息时,向它的所有订阅者发布信息
可以设计两个接口来完成观察者模式
ISpeaker和IListener,ISpeaker作为主题,IListener作为观察者
ISpeaker的抽象方法有 addOneListener,removeOneListener,speakMessage
IListener的抽象方法有 readMessage
作为主题的类实现了ISpeaker接口后 再维护一个List<IListener>
每当发布信息speakMessage时,轮询List调用所有观察者的readMessage即可
主题不知道观察者的任何信息,它不在于观察者到底是什么,到底在干什么
观察者不知道主题的具体实现,它只在于来自主题的消息,这就是松耦合
上述由主题向观察者发布消息的行为可以看作主题向观察者的推送
同时也可以直接将主题对象发给观察者,由观察者自己get主题里的数据,来主动拉取信息
3 装饰者模式
装饰者模式:
被装饰者和装饰者属于同一类型,将被装饰者作为装饰者的成员,装饰的过程就是把被装饰者作为装饰者的参数,生成新的装饰者的过程,把对象作为成员通过组合来扩展功能,Java.io种就充满了装饰者模式
当设计一家咖啡厅菜单时,起初可以为每种饮品设计一个类
但是随着饮品的增多,需要的类也越来越多,且还伴随着大杯,小杯,加糖,加奶等需求
如果为每种需求都设计一个类,如大杯加糖咖啡,小杯加糖加奶拿铁
那么系统中就会出现"类爆炸",需要维护太多的类
为此可以采用装饰者模式,将基础的饮品作为"被装饰者",将大杯,加糖等作为"装饰者"
它们有共同的父类"材料",将被装饰者作为装饰者的成员
当需要小杯加糖加奶拿铁时,先创建一个拿铁对象
再将其作为参数使用小杯"装饰",将得到的对象再使用加糖装饰...
示例:
public class Tea extends Beverage {
public Tea() { super("Tea"); }
@Override
public double cost() { return 10; }
}
public class Milk extends Decorator {
private Beverage beverage;
public Milk(Beverage beverage) {
super("牛奶");
this.beverage = beverage;
}
@Override
public double cost() {
return 3 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + " + Milk";
}
}
eg:
Tea tea = new Tea();
tea = new Milk(tea); // 被牛奶装饰
tea = new Mocha(tea); // 被摩卡装饰
tea = new BigCup(tea); // 被大杯装饰
获得了加奶加摩卡大杯茶
java.io中的装饰者模式:
FileInputStream inputStream = new FileInputStream("");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
4 工厂模式
工厂模式:
当在一个类中创建了大量的对象,对象越多这个类越脆弱,稍微有一个依赖进行了修改,整个类就无法正常工作,为此需要把功能实现和对象的创建分离,将对象的创建过程封装起来,使程序解耦,将功能实现和创建对象分离,以降低依赖
工厂模式可以分为简单工厂,工厂方法
简单工厂提供一个生产对应对象的方法或静态方法,返回值为该类对象
将对象的创建过程放到了另一个类的方法中
public static Pizza createPizza(String type) {
Pizza pizza = null;
if("cheese".equals(type)) {
pizza = new CheesePizza();
} else if("clam".equals(type)) {
pizza = new ClamPizza();
} else {
return null;
}
return pizza;
}
工厂方法定义一个创建对象的抽象类,把创建对象的过程推迟到了子类
当子类试图从工厂中获取对象时,具体生产对象是由子类中完成的抽象方法实现的
public abstract class PizzaStore {
public Pizza getPizza(String type) {
Pizza pizza;
pizza = createPizza(type);
return pizza;
}
protected abstract Pizza createPizza(String type);
}
5 单例模式
单例模式:
有时候某个对象整个应用中只需要一个,如注册中心,日志,线程池,如果这些对象不加以约束的随意创建,就会导致程序的行为异常,出现预期之外的结果,单例模式确保一个类只有一个实例,提供一个全局访问点
单例模式常常用来管理共享唯一的资源
普通单例模式:
public class Registry {
private static Registry instance;
private Registry() {}
public static Registry getInstance() {
if(instance == null) {
instance = new Registry();
}
return instance;
}
}
多线程下同步单例模式:
public class Registry {
private volatile static Registry registry;
private Registry() {}
public static Registry getInstance() {
if(registry == null) {
synchronized (Registry.class) {
if(registry == null) {
return new Registry();
}
}
}
return registry;
}
}
上面两种单例的创建方式都采用了懒汉模式
即需要获取时再生产,饿汉模式也可以
但如果有大量由饿汉模式创建的对象,当应用启动时,会带给用户不太好的体验
6 命令模式
命令模式:
通过命令模式,可以将方法封装起来,将动作封装成命令对象,这样就可以随意存储,传递和调用它们,命令模式将发出请求的对象和执行请求的对象解耦
在设计一个遥控器的过程中,遥控器可以控制电视,台灯的开关
不采用设计模式的情况下,将电视和台灯等对象封装到遥控器对象中
遥控器的开关方法包装着电视,台灯的开关方法
这样的设计首先造成了依赖,功能又与对象耦合到了一起
如果采用命令模式,设计一个Command接口,该接口内存在一个抽象方法execute()
为该Command接口实现不同的命令对象,如开灯对象,关灯对象
开灯对象和关灯对象中封装着具体的对象Light,由命令对象来完成具体的功能实现
将该接口采用组合作为调用者(遥控器)的成员,通过set来替换命令对象
当调用者进行某此调用时,调用的是命令对象中的方法
而命令对象将来完成具体的操作,以此将发出请求的对象和执行请求的对象解耦
7 适配器模式
适配器模式:
将一个类的接口,转换成期望的另一个接口
适配器模式让原本不兼容的类可以轻松合作
当需要一个接口实现多种功能时,采用适配器模式
在JDK的swing技术中,通常要为各种组件增加侦听器,如焦点的获取/失去
/*编号文本框获取焦点,编号文本框被全部选中*/
this.jtxtId.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
jtxtId.selectAll();
}
});
/*名称文本框获取焦点,名称文本框被全部选中*/
this.jtxtName.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
jtxtName.selectAll();
}
});
FocusAdapter就是一个适配器,它实现了FocusListener接口
这里将具体的实现方式交给用户,让用户重写其中的方法来根据场景使用
通过适配器模式可以将功能与实现解耦,通过组合可以替换不同的适配器
且适配器可以为空,由用户来自己实现
8 模板方法模式
模板方法模式:
在一个方法中定义了一个算法的骨架,将一些步骤延迟到子类中,模板方法可以使得子类在不更改算法框架的结构下,重新定义算法中的某些步骤
即父类中有一个包含了若干抽象方法的模板方法,不同的子类继承父类实现不同的抽象方法
模板方法就有了不同的功能
在制作咖啡和茶的过程中
茶: 1,把水煮沸 -> 2,用沸水冲泡茶叶 -> 3,把茶倒进杯子里 -> 4,加柠檬
咖啡: 1,把水煮沸 -> 2,用沸水冲泡咖啡 -> 3,把咖啡倒进杯子里 -> 4,加糖和牛奶
我们可以简单的把这每个步骤做成一个方法放在具体的Tea和Coffee类中
但这样又造成了功能与实现耦合,且发现了第一步和第三步是一样的,为此可以采用模板方法模式
public abstract class Drink {
public Drink() {}
public final void prepare() {
boilWater();
brew();
pourInCup();
add();
}
public abstract void brew();
public abstract void add();
public void boilWater() {
System.out.println("把水煮沸");
}
public void pourInCup() {
System.out.println("倒进杯子里");
}
}
public class Tea extends Drink {
public Tea() {}
@Override
public void brew() { System.out.println("用沸水冲泡茶叶"); }
@Override
public void add() { System.out.println("加柠檬"); }
}
当调用Tea中的prepare()方法时,其实是调用了Drink类中的prepare()方法
此时prepare()方法就是一个模板方法,会依次执行boilWater() brew() pourInCup() add()
由于是Tea调用的prepare() 所以模板方法中的抽象方法选择了Tea中的brew()和add()
将部分步骤延迟到了子类,使得功能与实现解耦
同时还可以在模板方法中增加hook() 来控制模板方法的流程 如:
public abstract class Drink {
public Drink() {}
public final void prepare() {
boilWater();
brew();
pourInCup();
//挂钩点
if(wantAddSomething()) {
add();
}
}
public abstract void brew();
public abstract void add();
public void boilWater() { System.out.println("把水煮沸"); }
public void pourInCup() { System.out.println("倒进杯子里"); }
//hook,可以被子类覆盖,从而对模板方法进行一定的控制
public boolean wantAddSomething() {
return true;
}
}
子类可以重写hook(),即上述的wantAddSomething()来控制模板方法的流程
hook()能影响模板方法的流程,使子类有一定能力控制模板方法的走向
在java.util中的Arrays.sort()方法可以进行排序,对于简单的Integer类型数组可以直接排序,但也可以让某个类实现Comparable接口中的compareTo()来指定自己想要的排序条件,这个过程中,sort()就可以看作模板方法,compareTo()可以看作模板方法中的一环,由用户实现来更改最终模板方法执行的结果
9 状态模式
状态模式:
允许对象在内部状态改变时改变它的行为
考虑在设计一个糖果机时,糖果机有不同的状态
如未投币,已投币,出货中,退款中等状态
如果不采用设计模式,需要在多个方法中不断进行if-else的判断当前的状态,如:
public void insertMoney() {
if(this.state == NO_MONEY) {
System.out.println("已投币");
this.state = HAS_MONEY;
System.out.println("请点击 <购买> 或 <退款> 按钮");
return;
} else if(this.state == HAS_MONEY) {
this.state = BACK_MONEY;
System.out.println("请不要连续投币,马上会退出您的硬币");
backMoney();
return;
}
}
虽然能够简单完成,但是一旦增加了新的需求,如中奖需求
整个机器的代码大部分都要重写,先前的做法违反了开闭原则,并且系统没有弹性,逻辑混乱
现在采用状态模式来设计,定义一个State接口,糖果机的每种状态都来实现这个State接口
将糖果机的动作委托到状态类中
public class Machine {
private State soldOutState;
private State noMoneyState;
private State hasMoneyState;
private State soldState;
private State state;
private int count = 0;
public Machine(int count) {
this.soldOutState = new SoldOutState(this);
this.noMoneyState = new NoMoneyState(this);
this.hasMoneyState = new HasMoneyState(this);
this.soldState = new SoldState(this);
this.count = count;
if(count > 0) {
this.state = noMoneyState;
}
}
// 状态接口
public interface State {
//投币
void insertMoney();
//退款
void ejectMoney();
//转动曲柄
void turnCrank();
//分发糖果
void dispense();
}
// 没有钱投入机器时的糖果机状态
public class NoMoneyState implements State {
private Machine machine;
public NoMoneyState(Machine machine) {
this.machine = machine;
}
@Override
public void insertMoney() {
System.out.println("你投入了一枚硬币");
machine.setState(machine.getHasMoneyState());
}
@Override
public void ejectMoney() {
System.out.println("尚未投币,无法退款");
}
@Override
public void turnCrank() {
System.out.println("尚未投币,无法转动曲柄");
}
@Override
public void dispense() {
System.out.println("尚未投币,无法获取糖果");
}
}
这是一个双向组合的过程,各种状态类被组合到了糖果机中,糖果机被组合到了状态类中
糖果机的各种行为是状态类来执行的,状态类工作后会对糖果机进行调用,以进入下一个状态
10 代理模式
代理模式:
为一个对象提供一个"替身",以控制对该对象的访问
代理模式为真实对象提供替身,当调用代理中的方法时
代理对象会委托真实对象来进行处理,但在委托的前后
可以增加更多的功能,如权限检测,安全判断,远程调用...
从而非侵入式地扩展了真实对象的功能并保护真实对象
代理的应用场景很多,如
防火墙代理: 控制网络资源的访问,避免非法操作
缓存代理: 为开销大的运算结果提供暂时存储,允许多个客户共享结果,以减少计算
同步代理: 多线程环境下,为真实对象提供安全的访问
...
2 设计原则
2.1 区分变与不变
让系统的某部分改变不会影响其它部分,以使得代码变化引起的更改减少,让系统更有弹性
找出应用中可能需要变化的地方,将它们独立出来
不要和那些不需要变化的代码放在一起
2.2 多用组合,少用继承
尽可能少地使用继承,将接口当作成员以扩展类的功能
继承虽能获取父类的代码,以完成代码复用
但是继承同时压缩了子类变化的空间,并且父类如果要做更改
那么所有子类都会被殃及
2.3 松耦合
两个对象之间松耦合,它们仍然可以交互,只是不清楚彼此的细节
松耦合能让两个对象不清楚,不在于彼此的具体实现
还能够完成数据的交互,能够提高系统的弹性
2.4 开闭原则
类应该对扩展开发,对修改关闭
程序应该免于变化且善于扩展
在尽量不修改源码的基础上,就可以搭配新的行为
2.5 依赖倒置原则
当直接实例化一个对象时,就是在依赖它的具体实现类,要尽量减少这种依赖
不要让高层组件依赖底层组件,且不管高层还是底层组件都应该依赖于"抽象"
父类和接口就是抽象
如在披萨店中,如果让PizzaStore直接依赖各种CheesePizza,BeefPizza就是高层组件依赖底层组件
整个PizzaStore大量的依赖实现类,为此可以让PizzaStore依赖父类Pizza,让各种披萨作为Pizza的子类
以遵循依赖倒置原则,将功能与创建对象分离
2.6 最少知识原则
不要让太多的类耦合在一起
不要让太多类耦合在一起,免得修改系统中的一部分,影响到其它部分
如果许多类之间相互依赖,那么这个系统就是一个易碎的系统,需要更多精力去维护
2.7 好莱坞原则
别调用我,我会调用你
好莱坞原则可以降低对象之间的依赖
如在一个糟糕的系统中,高层组件依赖低层组件,低层组件又依赖边侧组件
边侧组件又依赖低层组件,整个系统耦合严重
好莱坞原则允许低层组件将自己挂钩在系统上,由高层组件决定怎么使用和什么时候使用这些低层组件
如在模板方法中的Coffee和Drink中,执行coffee.prepare()时
Drink是高层组件,Coffee是低层组件,执行coffee.prepare()时,是Drink调用了Coffee中的方法
而不是Drink依赖于Coffee的实现,通过这样来减少依赖
2.8 单一职责原则
每个类只负责自己的事,一个类只负责一个工作
遵循单一职责原则,可以提高类的可读性,提高系统的可维护性
当修改一个功能时,能显著降低对其它功能的影响,分模块按功能的设计类