读书笔记-设计模式之禅
第一章 单一职责原则
单一职责原则:要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情
完美的设计: 一个类实现了两个接口,把两个职责融合在一个类中。
不好的设计:
好的设计:
接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化
第二章 里氏替换原则
如果子类有必要拥有父类的所有方法。那就继承父类,如果有些方法用得到,有些方法用不到,那就别继承了
只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。
但是,反过来就不行了,有子类出现的地方,父类未必就能适应
1.子类必须完全实现父类的方法
注意:如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发
生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承
例如:有很多枪可以继承枪的接口,但如果是玩具枪就不适合继承枪的接口,因为它不能杀人,脱离了正常的业务逻辑,应该把玩具枪独立出来,脱离继承
2.子类可以有自己的个性
在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子
类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀——委屈了
点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替
换的标准。
第三章 依赖倒置原则
● 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
● 抽象不应该依赖细节;
● 细节应该依赖抽象
就是面向接口编程
稳定性较高的设计,在周围环境频繁变化的时候,依然可以做到“我自岿然不动
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立不互相影响,实现模块间的松耦合
一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。
因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,
是否可以修改为private、package-private(包类型,在类、方法、变量前不加访问权限,则默认为包类型)、protected等访问权限,是否可以加上final关键字等
第四章接口隔离原则: 接口尽量细化,同时接口中的方法尽量少
第五章 迪米特法则: 高内聚低耦合
第六章 开闭原则: 对扩展开放,对修改关闭
第八章 工厂模式
建立人类的实现类
public class BlackHuman implements Human {
public void getColor(){
System.out.println("黑色人种的皮肤颜色是黑色的!");
}
public void talk() {
System.out.println("黑人会说话,一般人听不懂。");
}
}
public class WhiteHuman implements Human {
public void getColor(){
System.out.println("白色人种的皮肤颜色是白色的!");
}
public void talk() {
System.out.println("白色人种会说话,一般都是但是单字节。");
}
}
建立抽象类
public abstract class AbstractHumanFactory {
public abstract <T extends Human> T createHuman(Class<T> c);
}
建立工厂类,通过名称的选择,利用反射构建类
public class HumanFactory extends AbstractHumanFactory {
public <T extends Human> T createHuman(Class<T> c) {
//定义一个生产的人种
Human human = null;
try {
//产生一个人种
human = (T) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
System.out.println("人种生成错误!");
}
return (T) human;
}
}
使用:
public class NvWa {
public static void main(String[] args) {
//声明阴阳八卦炉
AbstractHumanFactory YinYangLu = new HumanFactory();
//女娲第一次造人,火候不足,于是白人产生了
System.out.println("--造出的第一批人是白色人种--");
Human whiteHuman = YinYangLu.createHuman(WhiteHuman.class);
whiteHuman.getColor();
whiteHuman.talk();
//女娲第二次造人,火候过足,于是黑人产生了
System.out.println("\n--造出的第二批人是黑色人种--");
Human blackHuman = YinYangLu.createHuman(BlackHuman.class);
blackHuman.getColor();
blackHuman.talk();
//第三次造人,火候刚刚好,于是黄色人种产生了
System.out.println("\n--造出的第三批人是黄色人种--");
Human yellowHuman = YinYangLu.createHuman(YellowHuman.class);
yellowHuman.getColor();
yellowHuman.talk();
}
}
比较通用的工厂模式
1.定义产品抽象类,抽象类可以定义公共方法
public abstract class Product {
//产品类的公共方法
public void method1() {
//业务逻辑处理
}
//抽象方法
public abstract void method2();
}
2.具体产品类
public class ConcreteProduct1 extends Product {
public void method2() {
//业务逻辑处理
}
}
public class ConcreteProduct2 extends Product {
public void method2() {
//业务逻辑处理
}
}
3.抽象创建工厂
public abstract class Creator {
/*
* 创建一个产品对象,其输入参数类型可以自行设置
* 通常为String、Enum、Class等,当然也可以为空
*/
public abstract <T extends Product> T createProduct(Class<T> c);
}
4.具体的工厂类
public class ConcreteCreator extends Creator {
public <T extends Product> T createProduct(Class<T> c) {
Product product = null;
try {
product = (Product) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
//异常处理
}
return (T) product;
}
}
5.使用
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct1.class);
/*
* 继续业务处理
*/
}
}
好处:
1.易于扩展,如果有新增功能,直接加上实现类,然后从工厂方法获取即可
2.屏蔽产品类,产品如何变化,调用者都不需要关心,如jdbc如果要从oracle切换到mysql只需要修改驱动类的名称即可
工厂模式扩展:
1.缩小为简单工厂
public class HumanFactory {
public static <T extends Human> T createHuman(Class<T> c) {
//定义一个生产出的人种
Human human = null;
try {
//产生一个人种
human = (Human) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
System.out.println("人种生成错误!");
}
return (T) human;
}
}
使用方法
public class NvWa {
public static void main(String[] args) {
//女娲第一次造人,火候不足,于是白色人种产生了
System.out.println("--造出的第一批人是白色人种--");
Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class);
whiteHuman.getColor();
whiteHuman.talk();
//女娲第二次造人,火候过足,于是黑色人种产生了
System.out.println("\n--造出的第二批人是黑色人种--");
Human blackHuman = HumanFactory.createHuman(BlackHuman.class);
blackHuman.getColor();
blackHuman.talk();
//第三次造人,火候刚刚好,于是黄色人种产生了
System.out.println("\n--造出的第三批人是黄色人种--");
Human yellowHuman = HumanFactory.createHuman(YellowHuman.class);
yellowHuman.getColor();
yellowHuman.talk();
}
}
变化点:去掉继承抽象类,并在createHuman前增加static关键字;工厂类发生变化,也同时引起了调用者NvWa的变化
2.升级为多个工厂类:为每个产品增加一个工厂
public abstract class AbstractHumanFactory {
public abstract Human createHuman();
}
public class BlackHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new BlackHuman();
}
}
public class YellowHumanFactory extends AbstractHumanFactory {
public Human createHuman() {
return new YellowHuman();
}
}
使用方法
public class NvWa {
public static void main(String[] args) {
//女娲第一次造人,火候不足,于是白色人种产生了
System.out.println("--造出的第一批人是白色人种--");
Human whiteHuman = (new WhiteHumanFactory()).createHuman();
whiteHuman.getColor();
whiteHuman.talk();
//女娲第二次造人,火候过足,于是黑色人种产生了
System.out.println("\n--造出的第二批人是黑色人种--");
Human blackHuman = (new BlackHumanFactory()).createHuman();
blackHuman.getColor();
blackHuman.talk();
//第三次造人,火候刚刚好,于是黄色人种产生了
System.out.println("\n--造出的第三批人是黄色人种--");
Human yellowHuman = (new YellowHumanFactory()).createHuman();
yellowHuman.getColor();
yellowHuman.talk();
}
}
延迟加载工厂类
public class ProductFactory {
private static final Map<String, Product> prMap = new HashMap();
public static synchronized Product createProduct(String type) throws Exception {
Product product = null;
//如果Map中已经有这个对象
if (prMap.containsKey(type)) {
product = prMap.get(type);
} else {
if (type.equals("Product1")) {
product = new ConcreteProduct1();
} else {
product = new ConcreteProduct2();
}
//同时把对象放到缓存容器中
prMap.put(type, product);
}
return product;
}
}
通过定义一个Map容器,容纳所有产生的对象,如果在Map容器中已经有的对象,则直接取出返回;
如果没有,则根据需要的类型产生一个对象并放入到Map容器中,以方便下次调用
延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断
Map中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会
要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量
第九章 抽象工厂模式
Java的典型类图,一个接口,多个抽象类,然后是N个实现类
抽象工厂模式的通用类图:
public abstract class AbstractProductA {
//每个产品共有的方法
public void shareMethod() {
}
//每个产品相同方法,不同实现
public abstract void doSomething();
}
public class ProductA1 extends AbstractProductA {
public void doSomething() {
System.out.println("产品A1的实现方法");
}
}
public class ProductA2 extends AbstractProductA {
public void doSomething() {
System.out.println("产品A2的实现方法");
}
}
public abstract class AbstractProductB {
//每个产品共有的方法
public void shareMethod() {
}
//每个产品相同方法,不同实现
public abstract void doSomething();
}
public class ProductB1 extends AbstractProductB {
public void doSomething() {
System.out.println("产品B1的实现方法");
}
}
public class ProductB2 extends AbstractProductB {
public void doSomething() {
System.out.println("产品B2的实现方法");
}
}
public abstract class AbstractCreator {
//创建A产品家族
public abstract AbstractProductA createProductA();
//创建B产品家族
public abstract AbstractProductB createProductB();
}
public class Creator1 extends AbstractCreator {
//只生产产品等级为1的A产品
public AbstractProductA createProductA() {
return new ProductA1();
}
//只生产产品等级为1的B产品
public AbstractProductB createProductB() {
return new ProductB1();
}
}
public class Creator2 extends AbstractCreator {
//只生产产品等级为2的A产品
public AbstractProductA createProductA() {
return new ProductA2();
}
//只生产产品等级为2的B产品
public AbstractProductB createProductB() {
return new ProductB2();
}
}
public class Client {
public static void main(String[] args) {
//定义出两个工厂
AbstractCreator creator1 = new Creator1();
AbstractCreator creator2 = new Creator2();
//产生A1对象
AbstractProductA a1 = creator1.createProductA();
//产生A2对象
AbstractProductA a2 = creator2.createProductA();
//产生B1对象
AbstractProductB b1 = creator1.createProductB();
//产生B2对象
AbstractProductB b2 = creator2.createProductB();
/*
* 然后在这里就可以为所欲为了...
*/
}
}
在场景类中,没有任何一个方法与实现类有关系,对于一个产品来说,我们只要知道它
的工厂方法就可以直接产生一个产品对象,无须关心它的实现类
第十章 模板方法模式
public abstract class AbstractClass {
//基本方法
protected abstract void doSomething();
//基本方法
protected abstract void doAnything();
//模板方法
public void templateMethod() {
/*
* 调用基本方法,完成相关的逻辑
*/
this.doAnything();
this.doSomething();
}
}
public class ConcreteClass1 extends AbstractClass {
//实现基本方法
protected void doAnything() {
//业务逻辑处理
}
protected void doSomething() {
//业务逻辑处理
}
}
public class ConcreteClass2 extends AbstractClass {
//实现基本方法
protected void doAnything() {
//业务逻辑处理
}
protected void doSomething() {
//业务逻辑处理
}
}
public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
AbstractClass class2 = new ConcreteClass2();
//调用模板方法
class1.templateMethod();
class2.templateMethod();
}
}
模板方法模式的优点:
封装不变部分,扩展可变部分
提取公共部分代码,便于维护
行为由父类控制,子类实现
使用场景:
● 多个子类有公有的方法,并且逻辑基本相同时。
● 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
● 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。
通过父类中定义钩子函数,约束父类的行为,在子类中重写父类方法就可以改变模板中的行为
public abstract class HummerModel {
/*
* 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
* 是要能够发动起来,那这个实现要在实现类里了
*/
protected abstract void start();
//能发动,还要能停下来,那才是真本事
protected abstract void stop();
//喇叭会出声音,是滴滴叫,还是哔哔叫
protected abstract void alarm();
//引擎会轰隆隆的响,不响那是假的
protected abstract void engineBoom();
//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
final public void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//要让它叫的就是就叫,喇嘛不想让它响就不响
if(this.isAlarm()){
this.alarm();
}
//到达目的地就停车
this.stop();
}
//钩子方法,默认喇叭是会响的
protected boolean isAlarm(){
return true;
}
}
public class HummerH1Model extends HummerModel {
private boolean alarmFlag = true; //要响喇叭
protected void alarm() {
System.out.println("悍马H1鸣笛...");
}
protected void engineBoom() {
System.out.println("悍马H1引擎声音是这样的...");
}
protected void start() {
System.out.println("悍马H1发动...");
}
protected void stop() {
System.out.println("悍马H1停车...");
}
protected boolean isAlarm() {
return this.alarmFlag;
}
//要不要响喇叭,是由客户来决定的
public void setAlarm(boolean isAlarm){
this.alarmFlag = isAlarm;
}
}
第十一章 建造者模式
通过传入的顺序决定汽车的功能
public abstract class CarModel {
//这个参数是各个基本方法执行的顺序
private ArrayList<String> sequence = new ArrayList<String>();
//模型是启动开始跑了
protected abstract void start();
//能发动,还要能停下来,那才是真本事
protected abstract void stop();
//喇叭会出声音,是滴滴叫,还是哔哔叫
protected abstract void alarm();
//引擎会轰隆隆地响,不响那是假的
protected abstract void engineBoom();
//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
final public void run() {
//循环一边,谁在前,就先执行谁
for (int i = 0; i < this.sequence.size(); i++) {
String actionName = this.sequence.get(i);
if (actionName.equalsIgnoreCase("start")) {
this.start(); //启动汽车
} else if (actionName.equalsIgnoreCase("stop")) {
this.stop(); //停止汽车
} else if (actionName.equalsIgnoreCase("alarm")) {
this.alarm(); //喇叭开始叫了
} else if (actionName.equalsIgnoreCase("engine boom")) {
//如果是engine boom关键字
this.engineBoom(); //引擎开始轰鸣
}
}
}
//把传递过来的值传递到类内
final public void setSequence(ArrayList sequence) {
this.sequence = sequence;
}
}
实现类:
public class BenzModel extends CarModel {
protected void alarm() {
System.out.println("奔驰车的喇叭声音是这个样子的...");
}
protected void engineBoom() {
System.out.println("奔驰车的引擎是这个声音的...");
}
protected void start() {
System.out.println("奔驰车跑起来是这个样子的...");
}
protected void stop() {
System.out.println("奔驰车应该这样停车...");
}
}
public class BMWModel extends CarModel {
protected void alarm() {
System.out.println("宝马车的喇叭声音是这个样子的...");
}
protected void engineBoom() {
System.out.println("宝马车的引擎是这个声音的...");
}
protected void start() {
System.out.println("宝马车跑起来是这个样子的...");
}
protected void stop() {
System.out.println("宝马车应该这样停车...");
}
}
这样两个汽车就能根据不同的顺序执行
public class Client {
public static void main(String[] args) {
/*
* 客户告诉XX公司,我要这样一个模型,然后XX公司就告诉我老大
* 说要这样一个模型,这样一个顺序,然后我就来制造
*/
BenzModel benz = new BenzModel();
//存放run的顺序
ArrayList<String> sequence = new ArrayList<String>();
sequence.add("engine boom"); //客户要求,run的时候先发动引擎
sequence.add("start"); //启动起来
sequence.add("stop"); //开了一段就停下来
//我们把这个顺序赋予奔驰车
benz.setSequence(sequence);
benz.run();
}
}
如果某一天说要做出启动顺序不同的奔驰车,可以直接修改sequence即可,可以满足不同的需求
- 通用源码
产品类:
public class Product {
public void doSomething(){
//独立业务处理
}
}
抽象建造者
public abstract class Builder {
//设置产品的不同部分,以获得不同的产品
public abstract void setPart();
//建造产品
public abstract Product buildProduct();
}
具体建造者
public class ConcreteProduct extends Builder {
private Product product = new Product();
//设置产品零件
public void setPart(){
/*
* 产品类内的逻辑处理
*/
}
//组建一个产品
public Product buildProduct() {
return product;
}
}
导演类
public class Director {
private Builder builder = new ConcreteProduct();
//构建不同的产品
public Product getAProduct(){
builder.setPart();
/*
* 设置不同的零件,产生不同的产品
*/
return builder.buildProduct();
}
}
使用场景:
相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式
多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式
产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适
第十二章 代理模式
通用源码
public interface Subject {
//定义一个方法
public void request();
}
public class RealSubject implements Subject {
//实现方法
public void request() {
//业务逻辑处理
}
}
public class Proxy implements Subject {
//要代理哪个实现类
private Subject subject = null;
//默认被代理者
public Proxy(){
this.subject = new Proxy();
}
public Proxy(Subject _subject){
this.subject = _subject;
}
//通过构造函数传递代理者
public Proxy(Object...objects ){
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
private void before(){
//do something
}
//善后处理
private void after(){
//do something
}
}
你要代理谁就产生该代理的实例,然后把被代理者传递进来
给代理类增加一个接口
public interface IProxy {
//计算费用
public void count();
}
public class GamePlayerProxy implements IGamePlayer,IProxy {
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(IGamePlayer _gamePlayer){
this.gamePlayer = _gamePlayer;
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
this.count();
}
//计算费用
public void count(){
System.out.println("升级总费用是:150元");
}
}
只要在代理类中增加实现计费的接口,就可以对用户进行计费
动态代理:动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
第十三章 原型模式
不通过new关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式
使用场景:
资源优化场景
性能和安全要求的场景
一个对象多个修改者的场景
注意:拷贝的时候构造函数不会被执行
第十四章 中介者模式
加入了一个中介者作为三个模块的交流核心
MVC框架中的C就是一个中介,将M和V隔离开,协调M和V工作
第十五章 命令模式
public abstract class Command {
//把三个组都定义好,子类可以直接使用
protected RequirementGroup rg = new RequirementGroup(); //需求组
protected PageGroup pg = new PageGroup(); //美工组
protected CodeGroup cg = new CodeGroup(); //代码组
//只有一个方法,你要我做什么事情
public abstract void execute();
}
public class AddRequirementCommand extends Command {
//执行增加一项需求的命令
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//给出计划
super.rg.plan();
}
}
public class DeletePageCommand extends Command {
//执行删除一个页面的命令
public void execute() {
//找到页面组
super.pg.find();
//删除一个页面
super.rg.delete();
//给出计划
super.rg.plan();
}
}
public class Invoker {
//什么命令
private Command command;
//客户发出命令
public void setCommand(Command command){
this.command = command;
}
//执行客户的命令
public void action(){
this.command.execute();
}
}
public class Client {
public static void main(String[] args) {
//定义我们的接头人
Invoker xiaoSan = new Invoker(); //接头人就是小三
//客户要求增加一项需求
System.out.println("------------客户要求增加一项需求---------------");
//客户给我们下命令来
Command command = new AddRequirementCommand();
//接头人接收到命令
xiaoSan.setCommand(command);
//接头人执行命令
xiaoSan.action();
}
}