设计模式草图
2021-07-26 19:10:27 8 举报
浅谈Java设计模式笔记
作者其他创作
大纲/内容
public class BinaryObserver extends Observer { // 在构造方法中进行订阅主题 public BinaryObserver(Subject subject) { this.subject = subject; // 通常在构造方法中将 this 发布出去的操作一定要小心 this.subject.attach(this); } // 该方法由主题类在数据变更的时候进行调用 @Override public void update() { String result = Integer.toBinaryString(subject.getState()); System.out.println(\"订阅的数据发生变化,新的数据处理为二进制值为:\" + result); }}
观察者模式: 观察者模式对于我们来说,真是再简单不过了。无外乎两个操作,观察者订阅自己关心的主题和主题有数据变化后通知观察者们。 1. 首先,需要定义主题,每个主题需要持有观察者列表的引用,用于在数据变更的时候通知各个观察者: 2.定义观察者接口 其实如果只有一个观察者类的话,接口都不用定义了,不过,通常场景下,既然用到了观察者模式,我们就是希望一个事件出来了,会有多个不同的类需要处理相应的信息。比如,订单修改成功事件,我们希望发短信的类得到通知、发邮件的类得到通知、处理物流信息的类得到通知等。 当然,JDK 也提供了相似的支持,具体的大家可以参考 java.util.Observable 和 java.util.Observer 这两个类。 实际生产过程中,观察者模式往往用消息中间件来实现,如果要实现单机观察者模式,笔者建议读者使用 Guava 中的 EventBus,它有同步实现也有异步实现,本文主要介绍设计模式,就不展开说了。 还有,即使是上面的这个代码,也会有很多变种,大家只要记住核心的部分,那就是一定有一个地方存放了所有的观察者,然后在事件发生的时候,遍历观察者,调用它们的回调函数。
public class NewUserRuleHandler extends RuleHandler { public void apply(Context context) { if (context.isNewUser()) { // 如果有后继节点的话,传递下去 if (this.getSuccessor() != null) { this.getSuccessor().apply(context); } } else { throw new RuntimeException(\"该活动仅限新用户参与\"); } }}
class ServiceProxy implement Service{ private Service service= new ServiceImpl(); void method(){ ... } ...}
public class LocationRuleHandler extends RuleHandler { public void apply(Context context) { boolean allowed = activityService.isSupportedLocation(context.getLocation); if (allowed) { if (this.getSuccessor() != null) { this.getSuccessor().apply(context); } } else { throw new RuntimeException(\"非常抱歉,您所在的地区无法参与本次活动\"); } }}
BufferedInputStream
组合模式用于表示具有层次结构的数据,使得我们对单个对象和组合对象的访问具有一致性.直接看一个例子吧,每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。通常,这种类需要定义 add(node)、remove(node)、getChildren() 这些方法。这说的其实就是组合模式
适配器模式和代理模式的异同:比较这两种模式,其实是比较对象适配器模式和代理模式,在代码结构上,它们很相似,都需要一个具体的实现类的实例。但是它们的目的不一样,代理模式做的是增强原方法的活;适配器做的是适配的活,为的是提供“把鸡包装成鸭,然后当做鸭来使用”,而鸡和鸭它们之间原本没有继承关系。
ByteArrayInputStream
interface Target{ void method(); ....}
思路:因为电脑是由许多的构件组成的,我们将 CPU 和主板进行抽象,然后 CPU 由 CPUFactory 生产,主板由 MainBoardFactory 生产,然后,我们再将 CPU 和主板搭配起来组合在一起.
FilterInputStream extends InputStream
public abstract class Observer { protected Subject subject; public abstract void update();}
public class ConcreteTemplate extends AbstractTemplate { public void apply() { System.out.println(\"子类实现抽象方法 apply\"); } public void end() { System.out.println(\"我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了\"); }}
产品族
行为型模式总结: 行为型模式部分介绍了策略模式、观察者模式、责任链模式、模板方法模式和状态模式,其实,经典的行为型模式还包括备忘录模式、命令模式等,但是它们的使用场景比较有限.
class ServiceProxy implement Service{ private Service service=new ServiceImpl(); void method(); ...}
从名字来简单解释下装饰器。既然说是装饰,那么往往就是添加小功能这种,而且,我们要满足可以添加多个小功能。最简单的,代理模式就可以实现功能的增强,但是代理不容易实现多个功能的增强,当然你可以说用代理包装代理的多层包装方式,但是那样的话代码就复杂了。 首先明白一些简单的概念,从图中我们看到,所有的具体装饰者们 ConcreteDecorator*** 都可以作为 Component 来使用,因为它们都实现了 Component 中的所有接口。它们和 Component 实现类 ConcreteComponent* 的区别是,它们只是装饰者,起装饰作用,也就是即使它们看上去牛逼轰轰,但是它们都只是在具体的实现中加了层皮来装饰**而已。
public class BlackTea extends Beverage { private static double COST_NUM_10 = 10.00; @Override public String getDescription() { return \"black tea\"; } @Override public double cost() { return COST_NUM_10; }}
class SomeAdapter implement Target{ // 构造方法中注入 private SomeThing s; SomeAdapter(SomeThing s){ this.s=s; } void method(){......} ....}
适配器模式-对象适配
模板设计模式: 在含有继承结构的代码中,模板方法模式是非常常用的。 1. 通常会有一个抽象类 模板方法中调用了 3 个方法,其中 apply() 是抽象方法,子类必须实现它,其实模板方法中有几个抽象方法完全是自由的,我们也可以将三个方法都设置为抽象方法,让子类来实现。也就是说,模板方法只负责定义第一步应该要做什么,第二步应该做什么,第三步应该做什么,至于怎么做,由子类来实现。 2. 一个实现类
行为模式: 行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。策略模式: 下面设计的场景是,我们需要画一个图形,可选的策略就是用红色笔来画,还是绿色笔来画,或者蓝色笔来画。
class SomeAdapter implement Target{ private SomeThing s; public SomeAdapter( SomeThing s){ this.s=s; } void method(); ....}
Decorator menthodA(); menthodB();
public class IntelMainBoardFactory extends MainBoardFactory { @Override public MainBoard makeBoard() { return new IntelMainBoard(); }}
使用桥梁
class ServiceImpl implement Service{ void method(); ...}
public interface Cpu {}
public class AmdFactory extends ComputerFactory { @Override public Cpu makeCpu() { return new AmdCpu(); } @Override public MainBoard makeMainBoard() { return new AmdMainBoard(); } @Override public HardDisk makeHardDisk() { return new AmdHardDisk(); }}
public class RevertState implements State { public void doAction(Context context) { System.out.println(\"给此商品补库存\"); context.setState(this); //... 执行加库存的具体操作 } public String toString() { return \"Revert State\"; }}
代理模式 : 用一个代理来隐藏具体实现类的实现细节,通常还用于在真实的实现的前后添加一部分逻辑. 既然说是代理,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已,但是对于客户端来说,它必须表现得就是客户端需要的真实实现。
当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。
class ServiceImpl implement Service{ void method(){ ... } ...}
public class DeductState implements State { public void doAction(Context context) { System.out.println(\"商品卖出,准备减库存\"); context.setState(this); //... 执行减库存的具体操作 } public String toString() { return \"Deduct State\"; }}
InputStream
ConcreteDecoratorC menthodA(); menthodB();
public abstract class AbstractTemplate { // 这就是模板方法 public void templateMethod() { init(); apply(); // 这个是重点 end(); // 可以作为钩子方法 } protected void init() { System.out.println(\"init 抽象层已经实现,子类也可以选择覆写\"); } // 留给子类实现 protected abstract void apply(); protected void end() { }}
/** * @author Kevin * Intel品牌的cpu */public class IntelCpuFactory extends CpuFactory { @Override public Cpu makeCpu() { return new IntelCpu(); }}
装饰模式的出发点,从此图中可以看到,接口 Component 其实已经有了 ConcreteComponentA 和 ConcreteComponentB 两个实现类了,但是,如果我们要增强这两个实现类的话,我们就可以采用装饰模式,用具体的装饰器来装饰实现类,以达到增强的目的。
public abstract class Beverage { /** * 描述 * @return 具体的描述 */ public abstract String getDescription(); /** * 返回价格 * @return 价格 */ public abstract double cost();}
抽象工厂模式
抽象工厂模式(一个经典的例子是造一台电脑)
ConcreteDecoratorB menthodA(); menthodB();
状态模式: 举一个简单的例子。商品库存中心有个最基本的需求是减库存和补库存,我们看看怎么用状态模式来写。 核心在于,我们的关注点不再是 Context 是该进行哪种操作,而是关注在这个 Context 会有哪些操作. 1 .定义状态接口; 2 .定义减库存的状态; 3 .定义补库存状态; 在上面这个例子中,如果我们不关心当前 context 处于什么状态,那么 Context 就可以不用维护 state 属性了,那样代码会简单很多。不过,商品库存这个例子毕竟只是个例,我们还有很多实例是需要知道当前 context 处于什么状态的。
代理模式
public class Context { private State state; private String name; public Context(String name) { this.name = name; } public void setState(State state) { this.state = state; } public void getState() { return this.state; }}
public class GreenTea extends Beverage { private static double COST_NUM_11=11.00; @Override public String getDescription() { return \"green tea\"; } @Override public double cost() { return COST_NUM_11; }}
public class AmdMainBoardFactory extends MainBoardFactory { @Override public MainBoard makeBoard() { return new AmdMainBoard(); }}
具体装饰类
public class ChineseFoodFactory implements FoodFactory { private static String A = \"A\"; private static String B = \"B\"; public Food makeFood(String name) { if (A.equals(name)) { return new ChineseFoodA(); } else if (B.equals(name)) { return new ChineseFoodB(); } else { return null; } }}
public interface MainBoard {}
组合模式:每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合
装饰模式
cpu
策略模式
class SomeThing { void method(){......} ....}
interface Service{ void methof(); ...}
责任链模式: 责任链通常需要先建立一个单向链表,然后调用方只需要调用头部节点就可以了,后面会自动流转下去。比如流程审批就是一个很好的例子,只要终端用户提交申请,根据申请的内容信息,自动建立一条责任链,然后就可以开始流转了。 有这么一个场景,用户参加一个活动可以领取奖品,但是活动需要进行很多的规则校验然后才能放行,比如首先需要校验用户是否是新用户、今日参与人数是否有限额、全场参与人数是否有限额等等。设定的规则都通过后,才能让用户领走奖品。 如果产品给你这个需求的话,我想大部分人一开始肯定想的就是,用一个 List 来存放所有的规则,然后 foreach 执行一下每个规则就好了。不过,读者也先别急,看看责任链模式和我们说的这个有什么不一样? 1.首先,我们要定义流程上节点的基类; 2.接下来,我们需要定义具体的每个节点了总结:先定义好一个链表,然后在通过任意一节点后,如果此节点有后继节点,那么传递下去。
public class LimitRuleHandler extends RuleHandler { public void apply(Context context) { int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品 if (remainedTimes > 0) { if (this.getSuccessor() != null) { this.getSuccessor().apply(userInfo); } } else { throw new RuntimeException(\"您来得太晚了,奖品被领完了\"); } }}
工厂模式
ConcreteComponentB menthodA(); menthodB();
结构模式总结: 代理模式、适配器模式、桥梁模式、装饰模式、门面模式、组合模式和享元模式。读者是否可以分别把这几个模式说清楚了呢?在说到这些模式的时候,心中是否有一个清晰的图或处理流程在脑海里呢?代理模式是做方法增强的,适配器模式是把鸡包装成鸭这种用来适配接口的,桥梁模式做到了很好的解耦,装饰模式从名字上就看得出来,适合于装饰类或者说 是增强类的场景,门面模式的优点是客户端不需要关心实例化过程,只要调用需要的方法即可,组合模式用于描述具有层次结构的数据,享元模式是为了在特定的场景中缓存已经创建的对象,用于提高性能。
class SomeThing{ void method(); ....}
public class AmericanFoodFactory implements FoodFactory { private static String A = \"A\"; private static String B = \"B\"; private String name; public Food makeFood(String name) { Food food=null; if (A.equals(name)) { food = new AmericanFoodA(A); } else if (B.equals(name)) { food = new AmericanFoodB(B); } return food; }}
public class Circle implements Shape { @Override public void draw() { System.out.println(\"Circle::draw()\"); }}
装饰器
类适配器模式:适配器模式 - 类继承
public abstract class Condiment extends Beverage {}
装饰模式:Java IO 中的几个类是典型的装饰模式的应用
我们知道 InputStream 代表了输入流,具体的输入来源可以是文件(FileInputStream)、管道(PipedInputStream)、数组(ByteArrayInputStream)等,这些就像前面奶茶的例子中的红茶、绿茶,属于基础输入流。FilterInputStream 承接了装饰模式的关键节点,它的实现类是一系列装饰器,比如 BufferedInputStream 代表用缓冲来装饰,也就使得输入流具有了缓冲的功能,LineNumberInputStream 代表用行号来装饰,在操作的时候就可以取得行号了,DataInputStream 的装饰,使得我们可以从输入流转换为 Java 中的基本类型值。
public abstract class Shape{ protect DrawApi drawApi; protect Shape(DrawApi drawApi){ this.drawApi=drawApi; } public abstract void draw();}
门面模式(也叫外观模式,Facade Pattern)在许多源码中有使用,比如 SLF4J 就可以理解为是门面模式的应用.门面模式的优点显而易见,客户端不再需要关注实例化时应该使用哪个实现类,直接调用门面提供的方法就可以了,因为门面类提供的方法的方法名对于客户端来说已经很友好了.
门面
桥梁模式
观察者模式
适配器模式总结:类适配和对象适配的异同:一个采用继承,一个采用组合;类适配属于静态实现,对象适配属于组合的动态实现,对象适配需要多实例化一个对象;总体来说,对象适配用得比较多。
使用
public class Mango extends Condiment{ private Beverage beverage; public Mango(Beverage beverage) { this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription() +\
Java IO 中的装饰模式
public class Rectangle implements Shape { @Override public void draw() { System.out.println(\"Rectangle::draw()\"); }}
ConcreteComponentA menthodA(); menthodB();
状态模式
public class ShapeMaker { private Shape circle; private Shape rectangle; private Shape square; public ShapeMaker() { circle = new Circle(); rectangle = new Rectangle(); square = new Square(); } /** * 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定 */ public void drawCircle(){ circle.draw(); } public void drawRectangle(){ rectangle.draw(); } public void drawSquare(){ square.draw(); }}
责任链模式
模板方法模式
DataInputStream
public abstract class MainBoardFactory { /** * 制造主板 * @return 主板对象 */ public abstract MainBoard makeBoard();}
public interface Shape { void draw();}
public class Subject { private List<Observer> observers = new ArrayList<Observer>(); private int state; public int getState() { return state; } public void setState(int state) { this.state = state; // 数据已变更,通知观察者们 notifyAllObservers(); } // 注册观察者 public void attach(Observer observer) { observers.add(observer); } // 通知观察者们 public void notifyAllObservers() { for (Observer observer : observers) { observer.update(); } }}
PipeInputStream
使用策略
class SomeThing{ void method(); ....}
public interface FoodFactory { /** * 制造食物 * @param name 食物名字 * @return 食物对象 */ Food makeFood(String name);}
享元模式: 英文是 Flyweight Pattern,不知道是谁最先翻译的这个词,感觉这翻译真的不好理解,我们试着强行关联起来吧。Flyweight 是轻量级的意思,享元分开来说就是 共享 元器件,也就是复用已经生成的对象,这种做法当然也就是轻量级的了。 复用对象最简单的方式是,用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候,先到 HashMap 中看看有没有,如果没有,再生成新的对象,然后将这个对象放入 HashMap 中.
/** * @author Kevin * cpu抽象工厂类 */public abstract class CpuFactory { /** * 制造cpu * @return CPU对象 */ public abstract Cpu makeCpu();}
LineNumberInputStream
电脑
Component menthodA(); menthodB();
桥梁
代理模式说白了就是做 “方法包装” 或做 “方法增强”。在面向切面编程中,其实就是动态代理的过程。比如 Spring 中,我们自己不定义代理类,但是 Spring 会帮我们动态来定义代理,然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。Spring 中实现动态代理有两种,一种是如果我们的类定义了接口,如 UserService 接口和 UserServiceImpl 实现,那么采用 JDK 的动态代理,感兴趣的读者可以去看看 java.lang.reflect.Proxy 类的源码;另一种是我们自己没有定义接口的,Spring 会采用 CGLIB 进行动态代理,它是一个 jar 包,性能还不错
interface Service{ void method(); ....}
/** * @author Kevin * 电脑制造抽象工厂 */public abstract class ComputerFactory { /** * 制造cpu * @return cpu */ public abstract Cpu makeCpu(); /** * 制造主板 * @return MainBoard */ public abstract MainBoard makeMainBoard(); /** * 制造硬盘 * @return HardDisk */ public abstract HardDisk makeHardDisk();}
public class IntelFactory extends ComputerFactory { @Override public Cpu makeCpu() { return new IntelCpu(); } @Override public MainBoard makeMainBoard() { return new IntelMainBoard(); } @Override public HardDisk makeHardDisk() { return new IntelHardDisk(); }}
通过继承的方法,适配器自动获得了所需要的大部分方法。这个时候,客户端使用更加简单,直接 Target t = new SomeAdapter(); 就可以了。
interface Target{ void method(); ...}
硬盘
适配器模式: 适配器模式做的就是,有一个接口需要实现,但是我们现成的对象都不满足,需要加一层适配器来进行适配。适配器模式总体来说分三种:默认适配器模式、对象适配器模式、类适配器模式.
public abstract class RuleHandler { // 后继节点 protected RuleHandler successor; public abstract void apply(Context context); public void setSuccessor(RuleHandler successor) { this.successor = successor; } public RuleHandler getSuccessor() { return successor; }}
策略模式与结构型模式中的桥梁模式而言,它们其实非常相似,我把桥梁模式的图拿过来大家对比下: 它们非常相似,桥梁模式在左侧加了一层抽象而已。桥梁模式的耦合更低,结构更复杂一些.
主板
ConcreteDecoratorA menthodA(); menthodB();
public abstract class Shape{ protect DrawApi drawApi; protect Shape(DrawApi drawApi){ this.drawApi=drawApi; } public abstract void draw();}
FileInputStream
public interface State { public void doAction(Context context);}
ConcreteDecoratorD menthodA(); menthodB();
public class HexaObserver extends Observer { public HexaObserver(Subject subject) { this.subject = subject; this.subject.attach(this); } @Override public void update() { String result = Integer.toHexString(subject.getState()).toUpperCase(); System.out.println(\"订阅的数据发生变化,新的数据处理为十六进制值为:\" + result); }}
interface Target{ void method(); ....}
Java IO 中的装饰模式。看下图 InputStream 派生出来的部分类:
门面模式
/** * @author Kevin */public class AmdCpuFactory extends CpuFactory { @Override public Cpu makeCpu() { return new AmdCpu(); }}
0 条评论
回复 删除
下一页