-
设计原则
-
开闭原则
- 一个软件实体应当对扩展开放,对修改闭合
-
里氏代换原则
- 任何能使用父类的地方,一定能使用子类
-
依赖倒转原则
- 要依赖于抽象,不要依赖于实现
- 抽象不应该依赖于细节,细节应该依赖于抽象
-
合成聚合复用原则
- 尽量使用合成聚合而不是继承去实现复用
-
迪米特法则
- 一个软件实体应该尽可能少的和其他实体发生相互作用
-
接口隔离原则
- 应该为客户提供尽可能小的单独的接口,而不应该提供大的综合性的接口
-
创建模式
-
7.建造者模式
-
概念
-
内容
- 生成器模式(Builder Pattern)是一种设计模式,又名:建造模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),是这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。(维基百科)
-
解析
- 建造者模式也叫作生成器模式。建造者模式是一种创建型模式。
概念中说这种设计模式可以将复杂对象的构建过程抽象出来。也就是说共同特点的一类产品,我们将他们的构造过程提出来,然后让不同的子类去实现这个构造过程。产生不同的产品。这就是建造者模式,它类似于一个对象工厂,通过不同的构造过程产生不同的对象。这个和工厂模式比较类似,但也有区别。
-
应用场景
- 当一个产品的创建过程非常复杂,比如过程中所需要的一些对象不容易获取到,或者不同的创建过程会产生不同的结果的时候,可以考虑使用建造者模式。建造者模式使用的频率不是很高。
-
想象一个场景
- java君所在的公司没有一个汉堡店,只卖鸡排堡和鸡腿堡。但是他们的原料很神奇,在汉堡中放的顺序不同,就会产生不同的口味。所以深受大家的喜欢。
汉堡店店长希望可以造出来一个系统,这个系统可以生产出各种各样的鸡排堡和鸡腿堡。这种情况就属于产品的生产过程比较复杂,需要再生产的时候调用原料生产的方法,并且不同的调用顺序还会产生不同的产品。
- 解决方案
- 建造者模式示例代码
- 我们可以看到,使用建造者模式,很完美地生产出了四种汉堡。
我们先定义了一个抽象产品类,在这个抽象产品类中有具体的建造产品的方法,在这个方法中有固定的步骤,然后这些固定的步骤在不同的子类中是不同的。这里用到了模板方法模式,然后具体的产品类实现了自己特定的放入食材的方法,比如鸡腿堡放鸡腿,鸡排堡放鸡排。
然后我们定义了一个抽象的建造者,其中声明了构建的方法也就是设置生产说明书,因为我们创造一个汉堡是对照生产说明书来的。
还有抽象的得到汉堡的方法,在具体的两类汉堡中我们实现各自的生产汉堡的方法。其实就是调用了各个汉堡实例的setSequence方法进行说明书传入。
最后有一个指挥类,在指挥类中,我们实现了四种汉堡的实现方法。每种获取方法都是先构造说明书,然后再向具体的构建者传入说明书。然后调用获取该汉堡的方法。调用该汉堡店的make方法进行制作。
-
总结
-
角色
- Product产品类
- 这个类是具体的产品,一般产品类实现了模板方法模式。这个类就是具体需要被创建的类,其中有自己的业务逻辑。
- 比如例子中的ChickenChop和Drumstick类
- Builder建造者类
- 该类是产品的抽象建造者。其中一般声明有两个方法,产品类的内部逻辑处理和组件产品。这是一个产品构建规范,由子类去实现。
- 比如例子中的HamburgerBuilder类
- ConcreteBuilder类
- 具体的建造者类,该类实现了Builder抽象类,可以具体返回一个组建好的对象。
- 比如例子中的DrumstickBuilder类和ChickenChopBuilder类。
- Director指挥类
- 该类负责安排不同的建造顺序。然后调用Builder的buildProduct方法获取产品。
- 比如例子中的Director类。
- 应用建造者模式进行产品的构建,使得各类的职责明确,符合单一职责原则。
而且比较容易扩展。客户不需要知道产品的内部组成细节。比如例子中我们就不需要去关心一个汉堡的内部生产的具体细节。而只关心各部件最后的组合。不同的建造者之间相互独立。容易扩展新的产品。
-
缺点
- 如果产品的内部过于复杂,会产生需要定义很多具体建造者来适应这些变化的情况,导致系统变的很庞大而不容易管理。
-
和工厂模式很相似
- 区别
- 建造者模式比较注重不同之间的零件之间的装配,前提是这些零件的生产过程已经有了。
- 装配
- 而工厂模式注重怎么去创建这些不同的零件。
- 创建
- 关于建造者模式的扩展,我们举了和模板模式结合使用的例子,在产品类之间使用模板方法的模式,在生产不同的产品的时候,零件的组合方式十分重要。而引入建造者模式就很好地实现了这个效果。
-
8.工厂模式
-
概念
-
内容
- 工厂方法模式(Factory method Pattern)是一种实现了’工厂‘概念的设计模式。就像其它创建型模式一样,她也是在处理不确定对象具体类型的情况下,创建对象的问题。工厂方法的实质:定义一个创建对象的接口,但让实现这个接口的类来决定实例哪个类。工厂方法让类的实例化推迟到子类中进行。(维基百科)
-
解析
- 工厂方法模式是一种创建型模式。这个模式提供了一个接口,让实现这个接口的类决定实例化哪一个类,也就是说工厂方法模式将对象的调用者和对象的创建进行分离。
调用者不用考虑对象是怎么被创建的。
-
应用场景
- 在所有我们需要得到一个对象实例的地方。都可以考虑使用工厂方法模式,作为new的代替品,如果需要获得不同种类的实例,可以用该模式提供一个统一的接口,调用者通过该接口获取实例,而不用关心对象的实例化过程。
-
想象一个场景
- java君所在的餐厅最近推出了几款披萨,分别是培根披萨,鲜虾披萨,火腿披萨。在客人点披萨的时候,服务员会通知厨师去做一个披萨,然后服务员上菜。
服务员不用去管比萨是怎么做出来的。他只知道将客人的需求递给厨师,剩下的由厨师来完成就可以。
- 解决方案
- 代码实现
- 从上面的例子中,我们定义了产品接口,然后三种产品类去具体实现该接口,还定义了一个工厂接口,然后一个具体的工厂去实现了该接口。
我们在场景类中实例化这个具体的工厂。然后如果想获取某个实例,就去调用了createPizza方法并传入一个我们需要的类型的class类即可。这样就相当于服务员向厨师,也就是工厂类请求了一个pizza,传入pizza的要求,然后厨师返回给服务员,服务员上菜。
服务员不用了解pizza具体是怎么做出来的,只需要命令厨师,做出来什么什么样的比萨即可。
-
典型应用
- 我们在用jdbc进行数据库连接的时候,我们获取连接的时候用getConnection方法直接获取,而不用了解具体的获取过程.当数据库从oracle变成mysql的时候,我们写数据库的操作语言根本不用变化.因为底层的数据库连接获取被jdbc屏蔽了.
-
总结
-
角色
- Product抽象产品
- 可以是接口或者抽象类,定义了产品的业务逻辑。
- 比如例子中的Pizza类
- ConreteProduct具体产品
- 实现了具体的产品业务逻辑。可以有多个。
- 比如上例中三种具体的披萨
- Factory工厂
- 声明了构建类的方法.
- 比如上例中AbstractPizzaFactory
- Concrete具体工厂
- 接受一个具体参数,表示需要返回哪一种产品,然后根据该参数返回具体的产品.
-
工厂方法模式很简单,但是使用频率很高.
- 子主题 1
-
优点
- 封装良好,代码结构清晰.
- 获取一个对象只需要知道这个类的约束条件即可.
- 比如上例中只需要知道该类的Class对象即可,而不知道创建对象的过程.
- 降低了不同模块之间的耦合
- 工厂方法模式扩展性很高.如果有了新的产品.只需要添加一个抽象产品的抽象类,然后稍微修改一下工厂类中的代码,甚至不需要修改.
- 例子中的工厂代码根本就不需要修改.
- 符合面向抽象编程的原则,符合迪米特法则,也符合里氏替换原则.
- 通过使用工厂类,产品类被屏蔽,高层模块中只知道产品类的接口,而不需要对产品有过多的了解.
-
简单工厂模式
- 当只有一个工厂的时候,我们没有必要将其实例化,也没有必要再增加一个工厂接口.
我们可以直接将工厂接口删除掉,然后把工厂中创建类的方法声明成静态方法.
直接通过静态方法进行创建对象调用就可以.这是对一般工厂方法模式的缩小.
-
在某个项目比较复杂的时候,如果实例化一个产品很复杂,如果将所有的实例化过程都写到同一个工厂中,那么该工厂的实例化部分将会十分臃肿.
- 如此,我们可以声明多个工厂类,每个工厂类对应着不同的产品的实例化
- 工厂方法模式也可以直接代替单例模式进行使用.
-
概要
-
9.抽象工厂模式
-
概念
-
内容
- 抽象工厂的实质是"提供接口,创建一些列相关或者独立的对象,而不指定这些对象的具体类"(维基百科)
-
解析
- 抽象工厂模式其实是工厂方法模式中最一般的,抽象程度最高的一种类型,本质上也是工厂方法模式.
- 工厂方法模式是为了创建一种产品,而在我们需要产生一族产品的时候,就需要一个更加强大的工具,抽象工厂模式了.
-
应用场景
-
想象一个场景
- java君餐厅的披萨套餐都是一样的规格,这引起了大家的不满,成年人可以吃完,小孩子吃不完了.所以顾客强烈要求增加小尺寸披萨.
- 设计方案F4
-
总结
-
产品等级
- 产品的继承结构
- 比如例子中的披萨接口是最顶级的,直接实现该接口的有三种披萨,他们实现了自己独特的order()方法,而把size()方法留给了下一级,大披萨和小披萨进行实现.
- 这样不同层次的产品类,实现本层次需要实现的方法,最后的一个层次就是最具体的产品.
-
产品族
- 就是在同一个工厂中生产的一族产品
-
使用场景
- 在系统的工厂所提供的具体产品不是某一对象,而是一个产品族的时候,就要考虑抽象工厂模式了.
- 在上个例子中,我们实现了两个工厂,分别处理大披萨族和小披萨族产品.
-
一个工厂生产的是一族产品,工厂模式中针对的是一个产品等级结构进行产品生产,而抽象工厂模式要针对多个产品等级结构进行生产.
- 也就是说,一个对象族,甚至是没有任何关系的一组对象,有共同的约束条件,就可以使用该模式.
- 所谓大约束条件类似于上例中的大披萨和小披萨,如果新添加了大份米饭和小份米饭,也可以将大份米饭和大份披萨放到同一个工厂中创建,因为两个有共同的约束条件(大份).
- 抽象工厂模式和工厂方法模式都拥有良好的封装性,他们只关心接口,而不用去关心具体实现,各模块之间的耦合性很低.
- 产品族内部的约束条件不用被调用之知道,比如他们通过调查得知,关于大披萨的生产,鲜虾披萨和培根披萨的消费比例是1:2.他们就需要在生产披萨时注意这个比例.而这个比例的约束,在工厂内部实现,对调用他们的高层模块完全是透明的.
-
缺点
- 不利于产品族的扩展,如果想要添加一个新的产品,需要对接口进行改动,对接口的改动就意味着要改动所有实现该接口的类.这是很可怕的事情
- 注意
- 是不容易添加产品族,而不是产品等级,因为添加产品等级是不需要对现有接口进行改造的.
-
10.单例模式
-
概念
-
内容
- 单例模式也叫做单子模式,是常见的一种设计模式,在应用这个设计模式时,单例对象的类必须保证只有一个实例存在.许多时候,整个系统中只需要拥有一个全局对象,这样有利于我们协调系统整体的行为.
-
解析
- 单例模式,顾名思义,就是只有一个实例.
-
应用场景
- 某些情况下,我们需要某个类有且仅有一个对象,比如一个计数器,如果有多个计数器,就会产生统计错误.这时候,就满足单例模式的使用条件了.
-
想象一个场景
- java君睡觉的时候,有认床的习惯.就是说,如果不是他的床,他是睡不着的.java君每次想睡觉的时候,就会请求一个床的实例.所以java君打算用单例来获取床.
- 代码实现
- 我们在Bed类中定义了一个静态的私有的Bed实例.然后定义了一个静态的getBed()方法.
调用该方法就会获得之前的实例.
- 同时我们把无参构造器声明成了private.以免在外部实例化bed,让java无法入睡.
- 子主题 3
- 代码实现
- 这种形式在并发量低的情况下不会有问题,但是一旦有高并发,就可能会导致产生多个实例,因为判断是否为空,然后实例化,这个过程是非原子性的.有可能在线程一准备实例化的时候,线程二请求该实例,这时候判断Bed还是等于null.所以这个时候就会产生多个实例.
- 解决办法,使用线程锁进行同步.不过有可能产生性能问题
- 代码实现
- 在此段代码中,我们把同步的粒度从整个方法缩小在了实例化的语句中.这样既不影响性能,又实现了懒汉式单例.但是懒汉式繁琐,不推荐使用
-
具体场景
- Spring的javabean默认是单例的
- servlet也是单例的
-
总结
-
很像简单工厂模式,每次从工厂中获取一个实例.
- 不过区别就是单例模式要求每次获取的实例都是一样的,而简单工厂模式就没有这种限制.
- 虽然我们将构造方法设置为private,可是如果该类实现了Cloneable接口,还是有可能通过clone的方法来复制实例.所以不推荐单例类实现Cloneable接口.
-
优点
- 内存中只有一个实例,减少了内存的开销.
- 当一个对象需要被频繁调用,频繁销毁的时候,用单例模式会减少创建实例和销毁实例带来的性能开支.
- 单例模式还可以设置全局的访问点,优化资源访问.
-
缺点
- 扩展困难
- 由于单例模式不是面向接口编程(整个模式中没有一个抽象),所以如果想去扩展,只能去修改代码.
- 单例模式对测试不友好
- 因为如果没有实现好单例模式,是不可以进行测试的.
- 单例模式将单例的获取和该类具体业务逻辑写在一个类中,不符合单一职责原则,
- 原型模式
-
结构模式
-
5.装饰模式
-
概念
-
内容
- 装饰者模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。
就功能而言,装饰者模式相比生成的子类更加灵活,这样可以给某个对象而不是整个类添加一些功能。
-
解析
- 顾名思义,是用来做装饰的。既然是装饰,就要有被装饰的组件。
我们平常的一些东西,被装饰后,通常会在保留之前功能的基础上,有了新功能,
有了新功能,就是装饰的意义。
在原来的基础上扩展,这种看起来像是继承可以完成的工作。
可是概念中说到,装饰者模式比生成子类更加灵活,可以给某个对象而不是整个类添加一些新的功能。
可见,使用装饰者模式和直接使用继承是不一样的。
-
应用场景
- 当需要扩展一个类的功能,或者给一个类增加附加职责的时候,或者需要支持功能的动态撤销和添加的时候可以使用该场景
-
想象一个场景
- java君喜欢吃东西,而且餐厅老板允许他自己定做,可是java君很讲卫生,还讲究食物色香味俱全。有一次他想吃甜点了,他就想去定做一个蛋糕,餐厅已经有一套做蛋糕的流程了。但是他想要在吃cake之前,用紫外线灯照射厨房3个小时。餐厅的工作人员说这个好办。我们再写一个类,然后继承之前的流程,加上一个照射紫外线的新功能就可以。这个类刚写好,java君又想在做好cake之后在cake上面放一些花瓣。他们就继承了原来的加了紫外线照射的流程后加上放花瓣的新功能,这样就产生了两层继承关系。这个以后如果java君还想再添加新功能,又需要继承,这一层层的继承,维护起来很麻烦。牵一发而动全身,耦合性很强。而且这一次如果java君不想照射紫外线,而是想用消毒水,这下子又需要再写一个继承了,然后放花瓣的类又需要重新继承消毒水。我猜想谁都不想去维护这些类,这样显然是不行的。
其实,当你的继承超过两层的时候,就要考虑下是不是设计有问题了。
-
解决之道
- 我们可以用装饰者模式。在一个功能的基础上添加新功能,除了继承,我们还可以使用组合。即把A类当做B类的一个属性,在B类中使用A类的方法。从而实现B类对A类功能的继承。这个就是装饰者模式的基础。
- 代码实现
- 最后的结果中,先对厨房进行了三个小时的紫外线照射,然后进行cake的制作,然后在使用花瓣进行装饰,最后开动。
- 在此例当中,我们没有使用多重继承来添加多重的行为装饰,而是使用了组合的方式,把类A作为类B的一个属性,然后在B中使用A的方法和属性,这就相当于让B拥有了A的能力。这种组合的方式相比继承更加灵活。这也是装饰者模式的核心。
-
经典场景
- IO流
- BufferReader bf =new BufferReader(new InputStreamReader(new FileInputStream(new File(path))))
- 在path下的File中读出数据封装成一个FileputStream流,然后被InputStreamReader装饰成一个Reader流,然后被BufferReader装饰成一个带缓冲的Reader然后处理最后装饰成的这个对象。每一次封装都是对原始流的一次装饰,动态的向原始的文件流中添加职责。最后成为一种我们很方便处理的流。
-
总结
-
角色
- Component接口
- 抽象组件,是原始的最核心的接口。
- 例如上面的Meal类,这个就是被装饰者的抽象。
- 其中声明了核心的Operation方法
- ConcreteComponent类
- 这个类实现了Component接口,是具体的被装饰组件。这个是最核心,最原始,最基本的组件。是具体的被装饰的组件。它实现了Component接口中的核心Operation方法。
- 例如上面的cake类
- Decorator类
- 抽象类,是所有装饰者的父类,他实现了Component接口。
- 例如例子中的抽象类Decorator
- 它里面是一个私有的Component类型的属性。在构造方法中初始化这个属性。
- 在实现Component的Operation方法的时候调用的是他的Component属性的Operation方法。
- ConcreteDecorator类
- 这个是具体的装饰者类。继承自Decorator类。
- 并且在每一个ConcreteDecorator里有一个私有的新功能的方法。
- 然后在重写父类的Operation方法的时候。可以根据新添加的方法的执行位置,在super.Operation()之前或者之后调用本类的修饰方法。灵活地向被装饰类中添加新功能。就像上面的PetalDecorate和UItravioletRayDecorate
- 装饰者模式放弃了多重继承,而选择用组合的方式进行职责扩充。
- 这样降低了耦合,提高了系统的可维护性。
- 如果再想进行扩展。只需要在写一个新的继承自Decorate的装饰者类即可。
- 而不同的装饰者之间,没有像用继承那样的强耦合性。灵活性比较强
-
优点
- 可以让装饰类和被装饰类独立发展而不会相互影响,降低了他们之间的耦合性。
- 装饰者模式可以很方便的动态扩展一个实现类的功能。
-
缺点
- 多层的装饰复杂度提高,不利于debug。
- 我们在使用装饰者模式的时候应尽量减少装饰类的数量,降低系统复杂度。
-
类图
- 门面模式
- 合成模式
- 代理模式
- 适配器模式
- 桥梁模式
- 共享元类模式
-
行为模式
-
1.策略模式
-
概念
-
内容
- 指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法
-
解析
- 基于设计原则
- 分离变化的部分和不变的部分
- 不变的地方是这个行为,变化的地方是行为所需要的算法(策略)
-
应用场景
-
举例
- java君、Pascal君、Scala君三个人要去餐厅吃饭,可是呢,他们三个的口味不一样,java君想吃面向对象大餐,Pascal君想吃面向过程大餐,而Scala君想吃函数式大餐,这时候假设你是餐厅服务员,你给每个人点餐的时候,你就这样写了
- 以上做法是比较糟糕的,如果再来一个spring先生想要吃aop大餐,就要去改源码了
- 不符合“对修改关闭,对扩展开放”的原则
- 子主题 4
- 解决之道
- 分离变化和不变的东西
- 变化就是对不同的人要上不同的菜,从代码中抽离,进行分离。
- 利用多态,给每一个类人一个点某中菜的能力。
- 我们可以让所有人实现一个Order接口,这个接口中定义了点菜的这个方法,让每个人都去实现这个方法,然后在点菜的时候直接去调用每个人的order方法。
- 这样在新来人的话,我们不用去修改原来的代码,只需要让新人实现自己的order方法,这样就符合设计原则了
- 代码实现
- 如此,无论来汇编先生,还是python女士,都只需要实现Order接口就行了
- 使代码更具有扩展性,降低了耦合性
-
框架
- Struts
- 你url一大堆,然后由mapping映射获取到指定的action
- springmvc
-
jdk官方排序算法
- 排序的时候,由于不同的需要排序的类的排序规则不一样,也就是策略不一样,
- 所以我们把策略封装到客户部分,也就是具体需要排序的类中,也就是让具体需要排序的类实现Comparable接口,
- 然后在传入到排序的算法中的时候,排序的算法在比较两个元素的先后顺序的时候直接调用其compareTo方法,
- 根据多态,他调用的其实是具体传入的compareTo方法。也就是封装到子类中的具体的比较策略,这样就能实现了具体的排序
-
提炼总结
- 我们需要对若干个都可以实现我们所要的功能中选择一个最合适的
-
策略模式包含的角色
- Context
- 上下文类
- 上下文中有一个策略属性,还有一个设置策略的方法,一个工作方法
- 工作方法中实现的就是策略的工作方法
- Strategy
- 抽象策略类
- ConcreteStrategy
- 具体策略类
- 关系
- 具体的策略A和具体的策略B实现了策略接口,实现了work方法
- 这样在调用上下文的work方法,就是调用了实际设置的具体策略的work方法
- 从而实现了将执行策略与具体的策略解耦
- 有新的策略的时候,只需要再重新声明一个策略类就可以了
-
策略模式是一种非常简单的设计模式,策略模式是对不同算法的封装,把算法的责任与算法本身分离开来,将算法委派给不同的对象进行管理
- 实现一族算法,并将每一个算法都封装起来,使其相互交换
-
策略模式中,使用哪一种策略由用户来决定,提高了系统的灵活性,但在一定程度上,也增加了客户端的使用难度
- 因为客户端需要了解不同算之间的区别,选择合适的算法
-
设计原则
- 针对接口编程,而不是针对实现编程
- 在策略模式中,我们用一个接口代表行为,根据多态,上下文在工作时执行的行为,是这个接口的具体实现
-
JDK官方的排序方法
- Collections.sort (List<T> ,list)
- 传入的比较器是null
- 元素T也实现了Comparator接口
- Collections.sort(List<T> list ,Comparator<? super T> c )
- 传入了具体的比较器
- 概要
- 实现都是直接调用了List的sort方法
-
关系图
-
2.模板模式
-
概念
-
内容
- 模板方法模式定义了一个算法的步骤,并允许子类别为一个或者多个步骤提供其实践方式。在子类别在不改变算法架构的情况下,重新定义算法中的某些步骤(维基百科)
-
理解
- 在这个模式中,算法的步骤是确定的。(对第一句的理解)
相当于父类中有算法步骤,但是把具体的算法步骤延迟到子类中去实现。(对第二句的理解)
如此:就实现了算法步骤和算法步骤的具体实现分离。
如果再有一份新的算法的实现,就再写一个子类就可以了。
- 既然是步骤,就是有顺序
-
应用场景
- 若干个类中有步骤相同的逻辑,但是步骤的实现方法不同。
复杂的算法,可以把核心的算法设计为模板方法,具体相关的细节由子类去实现。
-
举例
- Pascal君和Java君点完餐之后,想要吃饭。
他们的吃饭步骤如下:
准备餐具,吃饭,收拾餐具。
但是pascal君和java君准备的餐具不同,吃饭的方式当然也不一样,并且pascal君不喜欢收拾餐具。而java君每次吃饭后都要自己收拾餐具。
- 活用场景
-
解决之道
- 分析异同:
同:吃饭步骤三步。
异:三个步骤的具体实现不同
将相同的步骤抽离出来,就属于模板方法。
如此:我们将其封装到父类中,这个父类可以是一个抽象类,然后让子类去继承父类,然后子类去具体实现具体的被模板调用的方法,这个就把变化的部分和不变的部分分离开了。
- 代码实现
- 注意点
- 一般将父类的模板方法加上final关键字,防止被恶意覆盖。
- 父类的抽象方法一般用protected修饰符修饰,让其对子类和同包下的类可见
-
Servlet
- servlet中对一个请求进行具体的业务逻辑处理。
是调用了HttpServlet中的service方法,然后service方法根据不同的请求分发到具体的方法中,而具体的业务逻辑则由用户去进程HttpServlet类去实现
-
总结
-
模板方法模式一般分为两类
- 模板类
- 定义了算法的实现步骤
- 抽象的具体步骤的实现
- 具体实现类
- 子类去继承父类,然后去具体实现步骤
-
模板方法模式封装了不变的部分,扩展了可变得部分。如果scala君也想要吃饭,只需要去继承父类,然后去实现具体的步骤即可,不需要修改已经存在的代码。
- 符合开闭原则
-
当父类有N多步骤,而子类需要实现其中一两步时。
- 问题
- 模板方法无法对子类进行约束
- 解决方案
- 钩子方法
- 在父类中增加is***的方法,然后自己具体实现,返回true或者false表明是否需要调用这个方法。
- 在父类的模板方法中先判断是否可以执行,然后在执行步骤,这些is***方法就是钩子方法
- 顾名思义:钩子可以钩取这些基本方法,控制其是否执行。
- 活用场景
- 但是还有一个问题 就是小黄姐姐孩子上学快迟到了 刚洗完的头发还没有来的及吹就出门了 送完孩子之后 她想再去和帅哥约会 就想着去理发店把头发吹干 这个时候 剪和洗都不需要了 那么怎么解决这个问题呢
- 模板方法模式给出了钩子方法 就是在理发的抽象方法中加上一个is****()的抽象方法 具体再子类中实现 进而判断哪些步骤是否需要执行 就解决了小黄姐姐只需要吹干头发的问题了
-
3.命令模式
-
概念
-
内容
- 命令模式将请求封装成一个对象,从而使用不同的请求对客户端进行参数化,使用求情队列,或者记录请求日志,并且支持撤销操作。
-
解析
- 从定义上来看,他是把命令封装成了对象,命令就是指令,是上级发给下级的,既然有命令,就会有上下级的层次关系,命令是下发给下层人员去执行的。
所以命令模式中有决策者和执行者。
- 所谓的使用请求将客户端参数化,就是将执行者的命令参数化,也就是将命令封装成了对象。
- 至于队列和记录日志,就是在基础的命令模式上的扩展
-
应用场景
-
我们经常要向某些对象发请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。
- 例如:使用遥控控制电视机
- 我们使用遥控器的时候,做的只是按下某个按钮,然而这个按钮按下之后,遥控器发送的是什么样的红外线信号,我们不得而知,信号传到电视中之后被谁获取了我们也不知道。获取之后有什么样的操作,我们更不得而知。
- 我们只知道,我们按下这个按钮,电视做出了一些变化,也就是说,我们发出某个指令,获得了一些反馈信息
-
举例
- java君比较勤奋,而且有收拾盘子的好习惯。所以他被餐厅老板奖励,可以让java君去定做一些菜品,这下java君高兴坏了。但不久之后他就发现问题了。
比如:java君想吃焗盐鸡。然后他走到餐厅后,找到采购员,然后命令她去采购新鲜的鸡和粗盐。采购回来之后,他又找到负责宰鸡的师傅,帮忙杀掉黄毛鸡,然后找到厨师师傅帮忙给抹上细盐,包上吸油纸,锡纸,埋到粗盐中。最后找到烘烤师傅,帮忙烤熟。这样他才得到一只香喷喷的盐焗鸡。
不过这样太麻烦了呀。java君只是一个食客,结果他想吃一只鸡就需要前前后后联系那么多的人。并且java君有时候还想去吃黄焖鸡,这样就要去联系其他的人了。所以,他很厌烦。
- 解决之道
- java君找到老板说,你这样让我定做虽然好,但整个制作过程都要我去联系,我也受不了啊。你就告诉我一个接头的负责人,我告诉他怎么做。
- 你们内部怎么处理的我就不管了。最后给我一个完成的菜品就好了。增加一个接头人。java君每次想吃东西的时候就告诉这个接头人。然后等他的实物就可以了。
- 代码实现
- 其 实现 就是将参数命令化,封装成了一个对象。然后传给调用者,让调用者去执行这个命令。这样一来,java君想要什么菜品,只需要实例化一个命令,把他传给调用者就行了。
- 在该例子中,java君并不知道接受者是怎么去做的,也就是Receiver角色并没有暴露给Client,但是Client却依赖了Recevier。
- 在实际应用命令模式的时候,Receiver层一般都会被封装
- 在项目中,我们一般秉承“约定优于配置”这条原则。
因为每一个命令都是对一个或者多个Receiver的封装。
所以我们可以在项目中通过有意义的类名或者命令处理命令角色和接受角色之间的耦合关系,从而减少高层模块Client类,和底层模块Receiver类的依赖关系,从而提高系统的稳定性
-
概要
- 使得请求的发送者和接收者彼此间消除耦合,让对象之间的调用更加灵活
-
总结
-
角色
- Invokeer
- 调用者接收客户(客户端)的命令,并且执行该命令
- Invoker类
- 很简单,就是接受命令,执行命令。
- Command
- 声明需要执行的命令
- 抽象命令类
- 执行做饭命令
- 具体命令类
- 执行做盐焗鸡
- 执行做黄焖鸡
- 命令模式的核心。根据具体的需求,Command也可以有多个。
所以在类图中,用了抽象Command来表示命令。
- Receiver
- 就是具体进行工作的角色
- 命令传到接收者处,接受者执行命令
- 员工类
- 一般作为抽象类
- 因为接收者可能有多个,有多个就要定义为抽象接收者。
- 类图
-
本质
- 对命令的封装,将发出命令和执行命令两个责任解耦。
每个命令都是一个操作,请求的一方发出命令,然后执行者收到命令,执行命令。
-
优点
- 使用命令模式,调用者和接受者之间没有任何依赖关系。
调用者进行调用的时候,只需要调用execute()方法即可,而不用关心具体是哪一个接受者
- Command子类容易扩展
- 在上面的栗子中,java君如果想要预定其他的菜品,只需要扩展一个子类就可以了。
- 命名模式支持撤销操作。
- 一是结合备忘录模式使其恢复到最后状态
- 只适合接受者是状态的变化,而不适合事件处理。
- 可以增加一个新的命令rollback,实现事务的回滚。
- 在每次执行命令行的时候,把执行的过程写到日志中,撤销的时候通过读取日志,执行相反的命令。
- 子主题 2
- 命令模式可和其他模式进行联合
- 和组合模式进行联合
- 可以使用宏命令,我们在现在的命令模式中执行命令的时候,只是对命令的直接执行。
- 如果使用宏命令,我们可以在执行命令的时候,传入一个具体的对象,在调用execute方法的时候,会递归调用他所包含的每个成员命令的execute方法。
这个对象可以是一个简单的命令,也可以是一个宏命令,这样就会实现对命令的批量处理。
- 和模板方法进行联合’
- 可以减少Command子类膨胀问题
-
缺点
- 在Command命令复杂的时候,会造成Command子类膨胀
-
命令模式的扩展
- 请求队列
- 就是把若干个请求封装起来,放到一个队列中。
- 将请求封装起来后,就可以让请求进行传递,这样不会影响请求的完整性
- 并且可以让命令在不同的线程中被调用
- 想象一个场景
- 有一个工作队列,我们在一段添加命令,线程可以在另一端进行不断取出命令来执行。
线程和命令之间是完全解耦的。线程不用关心每个命令是干什么用的。
他们只知道取出该命令对象,然后调用其execute()方法。
- 请求日志
- 有时候我们需要将某些应用的动作记录到日志中。在宕机后,我们可以通过日志,重新调用这些动作,恢复到之前的状态。然后继续执行接下来的动作。
- 通常我们会增加两个方法,load()和store(),在我们执行某个请求的时候,会调用store()方法记相关日志,我们可以用对象的序列化将这个命令持久化到硬盘上,死机之后,我们可以在下一次启动的时候调用load()方法载入对象,然后执行该命令对象,恢复到宕机之前的状态。
-
4.责任链模式
-
概念
-
内容
- 是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。
每一个处理对象决定它能出处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。
该模式还描述了往该处理链的末尾添加新的处理对象的方法。
-
解析
- 责任链模式包含了命令对象和处理对象,是该模式中的两个角色。
既然是责任链,那么责任链是怎么表现出来的呢?概念中提到,每一个处理对象决定它能处理的那些对象,这就是责任的体现。
我如果能处理这个对象,那么我就对这个对象负责,如果我不能处理,我就把它叫给其他的对象。如此,就形成了一条处理链。
-
应用场景
- 当一个请求可能被多个处理者进行处理时,或者我们不知道会被哪一个处理者进行处理时,就可以使用该模式。
-
想象一个场景
- java君太厉害了,以至于java君所在的公司想给他报销餐费,这个公司有个奇怪的规定,项目经理只能报销<=500的餐费,部门经理只能报销<=1000且>=500的餐费,总经理只能报销1000以上的餐费,这一次,java君在享用了自己制定的菜品之后,希望各位领导大大们给他报销,那么大家可能会这样写
- 这样大量的if-else语句嵌套,使代码的可读性大大降低了,不美观,体现不出来代码的艺术感,并且如果公司规定变化了。这个if-else结构就得跟着变化。不符合开闭原则。
- 试想
- 如果java君的公司又来了一个新的大爷,也会报销某一个区间内的餐费,这个结构就被破坏了。还需要重新写。
- 设计模式就是为了应对需求变更的。并且力求代码的可读性
-
解决之道
- 需求分析
- java君提出一个请求,然后希望得到一个答复。
并且是唯一的一个答复,不能是项目经理报销一次后,部门经理再给他报销一次。
无论是可不可以被报销,或者被谁报销,java君不会去理会,java君只在意最后的结果。
- 设计方案
- 先把java君的请求传给项目经理,能处理则处理,不能处理则交给下一个经理大爷。最后反馈给java君,这样即使又来一个经理大爷,我们直接把他安排到这个处理链的末端就可以了。
- 代码实现
- 我们定义了一个抽象的经理类,里面定义了抽象的 该经理是否可以处理该请求,并且定义了具体的处理请求的方法,先判断是否可以被当前经理处理,如果可以,就调用当前经理的处理结果,如果不能处理,就把请求交给他的下一个继任者进行处理。
- 每个具体的经理类实现自己的判断方法和处理结果
- 我们的请求类java君有一个获取当前请求的金额方法,具体设置请求 请求金额在构造方法中。
- 此处有点类似模板方法
-
总结
-
角色
- Client
- 请求的发出者,希望得到回复
- 例子中的java君
- 有一个获取请求的方法
- 有一个设定请求的方法
- 设定请求一般在构造方法中进行
- Handler
- 抽象处理者,负责核心逻辑
- 例子中的抽象经理
- 职责
- 定义一个请求的处理方法
- 定义链的编排
- 即设置下一个处理者
- 定义了抽象的由子类实现的两个方法
- 判断能否处理当前请求
- 具体的处理任务
- 包括具体处理者的更换
- Concretehandler
- 具体实现者,实现具体的处理方法,判断是否可以处理该请求
- 例子中的具体经理
- 继承自抽象Handler实现了具体的处理任务和判断能否处理当前的请求
-
核心
- 核心是链
- 在这条链上,处理器像链表一样,每个处理器都拥有下一个处理器的引用。
- 一个请求在这个链中传递,被每个处理器处理或者不处理
-
注意
- 我们在实际项目中用到的大多数是不纯的责任链
- 一个请求在一个处理器上被处理,这里是被该处理器处理,然后再交给下一个处理器,被下一个处理器处理。当然,他也可以选择不处理。
那么这种一个请求可以被多个处理器进行处理的模式,就叫做不纯的责任链模式。
- 一个请求之被一个处理器处理
- 设计模式是为项目服务的,项目中怎么用着方便就怎么用,不要被设计模式所限制,造成思维僵化。
-
优点
- 将请求和处理分开了,请求者可以不用知道是谁处理的。
处理者也可以不知道请求者的全部。
两者解耦,提高了 系统的灵活性。
-
缺点
- 每次请求都是从链头遍历到链尾,造成了性能问题和调试的不方便
- 在实际应用中,我们经常回去控制链中节点的数量。避免出现超长的链。
- 一般做法是,在Handler中设置一个最大的节点数量,在setNext中先判断是否超过了这个最大的数量,如果超过了,就不允许建立该节点。
- 该模式融合了模板方法,Handler中,抽象父类实现了请求的传递,子类实现了请求的具体处理。
- 通过模板方法的融合,每个类只需要关注自己的业务逻辑。而不用去考虑其他类的情况,大大增强了系统的可扩展性。
- 如果想在添加一个处理器,只需要让该处理器实现Handler接口。然后实现自己的处理和判断即可,而不用改动原有的代码。
-
和命令模式结合使用
- Client封装了一个Command,传给Invoker,Invoker调用Command的执行方法,然后交给Receiver具体执行。如果结合了责任链模式,就可以让这个命令在链中进行传递,让对该命令负责的处理者对其进行处理。从而大大增加了系统的灵活性。
-
概要
-
6.状态模式
-
概念
-
内容
- 让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能获得的状态创立一个状态类的子类。当系统的状态变化后时,系统便改变所有的子类。(维基百科)
-
解析
- 状态模式,可见,有状态的概念。状态就是事物的实时属性,是可以变化的。在程序设计上,状态的变化,意味着行为可能发生变化,这样的话,我们可能会遇到某个行为在不同的状态下会有不同的表现(这个听起来可以用策略模式或者模板方法模式)。在某个状态下进行某个动作之后,还有可能会变化该状态。这样的话,我们可能会需要若干个if-else语句,或者switch语句。这样写就会发生状态的耦合,不利于修改。所以要用状态模式。
-
应用场景
- 当一个对象取决于该对象的状态,状态的改变需要与外部事件进行交互,并且其状态可能被改变的时候,我们就可以使用状态模式。
-
想象一个场景
- java君太厉害了,被餐厅聘请为架构师。java君任职第一天,老板就有了新任务,他们准备添加一台自动售货机,贩卖java咖啡和面向对象速食套餐。而且要插入会员卡进行消费。如此,java君开始设计了。
如果这个人没有插卡,是不能买东西的。插卡之后,可以买东西,可以退卡。买完东西可以接着卖可以退卡。退卡之后可以插卡接着买。
- 于是他设计了插卡方法。代码实现F4
- 只不过这个丑的代码肯定不是出自于作为架构师的java君之手。
这个代码,大量的switch-case,不利于扩展。万一领导要添加一个新的需求,添加一个新的状态。比如高级会员卡,这样的话,又需要改。而且有多少个动作就需要改多少个地方。不符合开闭原则,兼职辣眼睛。
- 解决方案
- 状态模式代码实现
- 该方案我们放弃了大量的if-else或者switch这样的写法,而是采用了一种更为灵活的方式。
我们先抽象了一个状态类,这个状态类中定义了插卡,购买,拔卡的这三种方法。然后声明具体的有卡或者无卡状态,让这两个具体状态实现自己状态下的具体方法。其中包括在进行某些动作之后的状态的改变。然后实现自动售货机类,状态类作为他的一个属性,代表他当前的状态,当前状态的set方法中,在设置为传入状态的同时,将当前的自动售货机传给该状态类。以便于状态类可以在适当的时候改变自动售货机类的当前状态。
-
常见场景
- TCP监听,TCP连接有三个状态。
这三个状态按照顺序循环切换,在不同的状态下会有不同的行为。
- 等待状态
- 连接状态
- 断开状态
- 不过,在具体的项目中,通常不是和TCP的状态这样只能切换到单一的状态,我们经常会遇到的是某个状态可以切换到很多其他的状态
- 比如qq用户的状态,有普通用户,会员,超级会员,普通用户可以切换到会员,也可以切换到超级会员。
-
总结
-
角色
- Status
- 抽象状态,这个是接口或者抽象状态。封装了具体的状态拥有者,以便完成状态的转换。其中声明了在每个状态下执行的动作方法。
- 如上status类
- ConcreteStatus
- 具体的状态类,这个是状态持有者运行的时候会产生的具体的状态。每一种对象都要去实现相对应的该状态下的具体的动作,如果需要状态转换,则调用父类的状态类拥有者的设置属性的方法。
- 如上的WithCard类和NoCard类。
- Context
- 状态拥有者类。也成为上下文或者环境。这个类时状态的具体拥有者。状态是其一个属性。这个类有自己的请求方法,在用到某状态下的动作的时候,调用当前的状态即可。如果改变状态,调用状态的set方法。
- 如上VendingMachine类。
- 在状态模式中,允许一个对象在其内部状态改变的时候改变他的行为, 看起来像是改变了她的类,其实是动态改变了状态,而在一些状态决定的动作中调用了当前状态的具体实现。这种方式很容易进行扩展,如果有了新的状态,或者代码中包含了大量的和当前状态有关的switch语句或者if-else语句的时候,就可以使用该模式。
-
优点
- 避免了大量的switch语句和if-else语句,使得结构更加清晰,避免了程序的复杂,提高了整个系统的可维护性。
-
缺点
- 如果一个事物有多个状态,就会产生类爆炸。
大量的子类使得系统不是那么容易管理。
-
状态模式和策略模式很相似
- 两者共同点
- 在不同场景下,策略可以灵活改变。
- 在策略模式中,我们称之为场景的东西在状态模式中就变成了状态,而所谓的策略,就是不同状态下的动作。
- 我们可以认为,状态模式就是策略模式的孪生兄弟。
- 两者不同点
- 在策略模式中,不同的场景对应不同的策略。
而不同场景之间,通常是不可以相互切换的,他们相对独立。
- 也就是说,在策略模式中,Context上下文类不会被改变。
- 而在状态模式下,不同状态之间是可以相互切换的,我们可以调用Context的setStatus方法进行状态的切换。从而方便在不同的动作之间进行切换。
-
类图
-
11.备忘录模式
- 子主题 1
- 观察模式
- 解释器模式
- 迭代模式
- 中介模式
- 访问者模式
-
概要
- 五指记忆法