配套使用效果更佳: 本博客配套开源源码github点击跳转
设计模式主要研究的是“变”与“不变”,以及如何将它们分离、解耦、组装,将其中“不变”的部分沉淀下来,避免“重复造轮子”,而对于“变”的部分则可以用抽象化、多态化等方式,增强软件的兼容性、可扩展性。
作为一个开发人员,在进行一个项目的设计与实现的过程中,应当具备软件架构的全局观,对项目进行模块化的设计,并充分考虑代码的可复用性,用最少的代码实现最完备的功能,使代码简洁、优雅。
优秀的系统应兼备功能强大、模块清晰、高扩展性,这离不开对各种设计模式的灵活运用。
单一职责原则:不是自己分内之事绝不负责。
开闭原则:对扩展开放,对修改关闭。
里式替换原则:能使用父类的地方,一定也可以替换为子类。
接口隔离原则:定义接口时应尽量拆分成较小的粒度,往往一个接口只对应一个智能。
依赖倒置原则:高层只依赖于上层抽象,不依赖于具体的底层实现。
迪米特法则:模块之间应该尽量意识不到彼此的存在。
本文第一章介绍面向对象的三大特性——封装、继承、多态,后续章节将展开介绍创建型、结构型、行为型三大类下的21种设计模式。
以下为本文的思维导图:
在我们的生活中,对象无处不在,从我们周围的每一个人,到使用的电脑、桌椅,你的家,再到工作的公司、就读的学校,每一个都是一个对象。我们可以通过对这些对象进行模型建立,将现实世界“投射”到计算机的世界中去。
但是模型往往并不是孤立存在的,它们彼此之间存在着千丝万缕的联系,因此,我们应当充分利用面向对象的三大特性——也就是封装、继承、多态去建模,从而减少代码冗余、模块耦合。面向对象的三大特性是学习设计模式不可或缺的预备知识。
1.封装
其实封装在我们的生活中也是随处可见的,就以我喜欢喝的烧仙草为例。在早期,许多奶茶店的烧仙草其实是采用塑封的方式进行包装的,这体现的就是一种封装的思想。封装隐藏了烧仙草内部的珍珠、芋圆、椰果、奶茶等,仅留给外界一根吸管用于访问,这根吸管相当于一个“接口”。
这种封装又能带来什么样的好处呢?作为一个喜欢喝烧仙草的人,我不必关注这个饮料是怎么做的以及它在瓶中的状态将如何变化,我仅仅只需要通过吸管这个“接口”去访问它的内部内容,这样做非常方便省事。其次,封装带来的好处就是,我既不用担心瓶中的饮料会倾洒出来,因为零散的数据被集中管理起来了;也不必担心内部会受到来自外界的污染,封装使得数据的安全性得到保障。
在Java编程语言中,一对大括号“{}”就是类的外壳、边界,它能很好地把类的各种属性及行为包裹起来,将它们封装在类内部并固化成一个整体。封装好的类如同一个黑匣子,外部无法看到内部的构造及运转机制,而只能访问其暴露出来的属性或方法。
下面仍以实现一个烧仙草类为例,展示Java语言中的封装。
为了简化代码,而回归其原理本身,假使烧仙草GrassJelly中的组成为奶茶MilkTea、配料Material,则对于一个烧仙草类可以有以下实现。
package com.wang.design.chapter0;
/**
* @author Tracy
*/
public class GrassJelly {
//属性
private String milkTea;
private String material;
//实例化对象
public GrassJelly(String milkTea, String material) {
this.milkTea = milkTea;
this.material = material;
}
public GrassJelly(){
}
//访问属性
public String getMilkTea() {
return milkTea;
}
public String getMaterial() {
return material;
}
public void show(){
System.out.println("this is a cup of grass jelly.");
}
//修改属性
public void setMilkTea(String milkTea) {
this.milkTea = milkTea;
}
public void setMaterial(String material) {
this.material = material;
}
}
现实生活中的对象是不计其数的,我们如果为每一个对象都单独创建一个类,不仅代码量会非常巨大,且其中的代码冗余会导致代码难以修改和维护,总之这样的做法实在是称不上优雅。
继承可以使父类的属性和方法延续到子类中,这样子类就不需要重复定义,并且子类可以通过重写来修改继承而来的方法实现,或者通过追加达到属性与功能扩展的目的。
下面我以交通工具体系为例,来展示如何通过Java中的继承来灵活扩展类。
首先,Vehicle为顶级抽象父类,因此将四种具体子类的公共特征与行为提取到其中,这样就不必在子类中重新定义,从一定程度上保证了代码的可复用性,降低了代码的冗余性。由于show()方法会因具体实现类的不同而不同,因此定义为抽象方法,为子类留有余地。
以下为代码:
package com.wang.design.chapter0;
/**
* @author Tracy
*
* 0-2 继承
*/
public abstract class Vehicle {
/**
* 以下为所有交通工具共有的属性
*/
protected String category;//"交通工具"
protected String size;//"大","中","小"
protected String name;//具体的名字"飞机"、"轮船"等
protected String load;//载重
/**
* 构造函数
*/
public Vehicle(String size, String load,String name) {
this.category = "交通工具";
this.size = size;
this.name = name;
this.load=load;
}
public Vehicle() {
}
/**
* setter & getter
*
*/
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 以下为所有交通工具共有的方法
*/
public abstract void show();
}
定义一个实现类Airplane子类继承自Vehicle父类,新增航班属性flightNumber。
以下为代码:
package com.wang.design.chapter0;
/**
* @author Tracy
*
* 0-2 继承
*
* 继承自父类Vehicle
*/
public class Airplane extends Vehicle{
protected String flightNumber;//专属属性:航班号
public Airplane(String size,String load,String flightNumber) {
super(size,load,"飞机");//调用父类构造方法
this.flightNumber = flightNumber;
}
public String getFlightNumber() {
return flightNumber;
}
public void setFlightNumber(String flightNumber) {
this.flightNumber = flightNumber;
}
@Override
public void show() {
System.out.println(category+":"+size+""+name+",航班号为"+flightNumber+"。");
}
public static void main(String[] args) {
new Airplane("大型","250t","12345").show();
}
}
调用show():
package com.wang.design.chapter0;
/**
* @author Tracy
*
* 0-2 继承
*
* 继承自父类Vehicle
*/
public class Bus extends Vehicle{
protected String route;//线路
public Bus(String size,String load,String route) {
super(size,load,"公交车");
this.route = route;
}
public String getRoute() {
return route;
}
public void setRoute(String route) {
this.route = route;
}
@Override
public void show() {
System.out.println(category+":"+size+""+name+",线路为"+route+"路。");
}
public static void main(String[] args) {
new Bus("中型","2t","220").show();
}
}
调用show():
对于父类定义的引用只能指向本类或者其子类实例化而来的对象,这就是一种多态。除此之外,还有其他形式的多态,例如抽象类引用指向子类对象,接口引用指向实现类的对象,其本质上都别无二致。
简单来说,多态就是指一个引用指向不同的类的对象,但这种指向只能是自己->自己,自己->子类,不能是子类->父类。如下所示:
package com.wang.design.chapter0;
/**
* @author Tracy
*
* 0-3 多态
*/
public class Polymorphic {
public static void main(String[] args) {
/**
* [交通工具]是[交通工具]
*/
Vehicle vehicle = new Vehicle() {
@Override
public void show() {
System.out.println("交通工具");
}
};
vehicle.show();
/**
* [飞机]是[交通工具]
*/
vehicle = new Airplane("大型","250t","12345");
vehicle.show();
/**
* [公交车]是[交通工具]
*/
vehicle = new Bus("中型","2t","220");
vehicle.show();
/**
* 以下为错误用法
* [交通工具]是[公交车] 错误
*/
//Bus bus=new Vehicle();
}
}
面向对象及其三大特性至此已介绍完毕,预备工作已完成,下面就真正进入到设计模式的学习中去。
单例模式指在某个系统中一个类只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。
饿汉模式,即在初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用。
下面我以上帝God为例,看看如何对世间唯一(假设唯一)存在的God进行饿汉模式的实现。
private
关键字确保God实例的私有性。
static
关键字确保God的静态性,在类加载的时候就实例化了,在内存中永生且唯一,即使是内存垃圾收集器也不会对其进行回收。
final
关键字则确保这个God是常量、恒量,引用一旦被赋值就不能再修改;
new
关键字初始化God类的静态实例,并赋予静态常量god。最后,
getInstance()
方法用来提供给外界唯一获取这个god单例的接口。
package com.wang.design.chapter1;
/**
* @author Tracy
*
* 1-1 创建型——单例模式
*/
public class God {
private static final God god=new God();
private God(){}
private static God getInstance(){
return god;
}
}
虽说在一开始就对god单例进行饿汉模式的加载具有一定的方便性,但如果加载之后很长一段时间都没有人访问god,那对上帝或者内存来说这恐怕是一种没有必要的消耗。基于这种考虑,于是就有了懒汉模式,也就是在需要的时候再对单例进行实例化。
- 去掉final关键字:由于god不是在声明时进行初始化的,因此将声明语句中的final关键字去掉了,否则后续真正需要初始化时就无法对其进行初始化(属于一种修改操作)。
- 好处与坏处:好处是在一定程度上节省了内存,坏处是临时初始化实例会消耗CPU资源使得程序有些许的延迟(尽管看上去也许不太明显)。
synchronized关键字
保证线程同步。
/**
* @author Tracy
*
* 1-1 创建型——单例模式——懒汉模式
*/
public class God {
private static God god;
private God(){}
private static synchronized God getInstance(){
if(god==null)god=new God();
return god;
}
}
上面的代码其实有一些不足,当有多个方法访问getInstance()方法时,在还没有进入这个方法时就开始了排队,会造成线程提前阻塞,其实这是没有必要的。下面进行了优化:首先,进入方法不排队,而是当god判空才开始排队;在同步块中进行二重检测(双检锁),是为了排除在进入getInstance的同时已经有线程获取到了god。
懒加载模式的“双检锁”:外层放宽入口,保证线程并发的高效性;内层加锁同步,保证实例化的单次运行。如此里应外合,不仅达到了单例模式的效果,还完美地保证了构建过程的运行效率,一举两得。
/**
* @author Tracy
*
* 1-1 创建型——单例模式——懒汉模式
*/
public class God {
private volatile static God god;
private God(){}
private static God getInstance(){//进入方法不排队
if(god==null){
synchronized(Sun.class){//判空才开始排队
if(god==null)god=new God();//二重检测是为了排除在进入getInstance的同时已经有线程获取到了god
}
}
return god;
}
}
相比“懒汉模式”,其实在大多数情况下我们通常会更多地使用“饿汉模式”,原因在于这个单例迟早是要被实例化占用内存的,延迟懒加载的意义并不大,加锁解锁反而是一种资源浪费,同步更是会降低CPU的利用率,使用不当的话反而会带来不必要的风险。越简单的包容性越强,而越复杂的反而越容易出错。
我们可以关注一下实例化与克隆之间的区别,二者都是在造对象,原型模式的目的是从原型实例克隆出新的实例,应对那些有非常复杂的初始化过程的对象或者是需要耗费大量资源的情况时具有一定的性能优势。
究其本质,克隆操作时Java虚拟机会进行内存操作,直接拷贝原型对象数据流生成新的副本对象,绝不会拖泥带水地触发一些多余的复杂操作(如类加载、实例化、初始化等),所以其效率远远高于“new”关键字所触发的实例化操作。
我以路人甲类Passerby为例,通过实现java.lang包中的克隆接口Cloneable,并在实现的clone()方法中调用了父类Object的克隆方法,如此一来外部就能够对本类的实例进行克隆操作了,省去了由类而生的再造过程。注意,对于属性中的引用类型需要深拷贝才能完全彻底地克隆这个实例的所有。
package com.wang.design.chapter2;
/**
* @author Tracy
*
* 2-1 创建型——原型模式
*/
public class Passerby implements Cloneable{
// x坐标和y坐标
private int x;
private int y;
private Equipment equipment;
public Passerby(int x, int y) {
this.x = x;
this.y = y;
}
public void setX(int x) {
this.x = x;
}
public void setEquipment(Equipment equipment) {
this.equipment = equipment;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
//行走
public void walk(){
++y;
}
//奔跑
public void run(){
y+=5;
}
//重写克隆方法
@Override
protected Passerby clone() throws CloneNotSupportedException {
Passerby passerby=(Passerby)super.clone();
passerby.setEquipment(this.equipment.clone());//深拷贝
return passerby;
}
}
class Equipment implements Cloneable{
@Override
protected Equipment clone() throws CloneNotSupportedException {
return (Equipment)super.clone();
}
}
定义克隆工厂类可以更方便地生产对象。
package com.wang.design.chapter2;
/**
* @author Tracy
*
* 2-2 创建型——原型模式——克隆工厂
*/
public class PasserbyFactory {
//单例饿汉模式先创建一个Passerby原型
private static Passerby passerby=new Passerby(0,0);
//获取克隆实例
public static Passerby getInstance(){
try{
Passerby one=passerby.clone();
return one;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
程序设计中的工厂类往往是对对象构造、实例化、初始化过程的封装,而工厂方法(FactoryMethod)则可以升华为一种设计模式,它对工厂制造方法进行接口规范化,以允许子类工厂决定具体制造哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升。
我们都知道,传统的实例构造方式就是使用new关键字,但这样会导致客户端与实例化对象这个过程产生耦合,但如果我们把生产过程交给一个工厂类,由工厂类来完成实例化、初始化等过程,那么我们就能摆脱传统生产方式带给我们的束缚。
实现工厂模式并不是简单地把生产对象这个过程换一个位置,如果这样做,当工厂类需要扩展的时候必然会需要不断地重写它,这样做事实上也并没有太多优越性。相反,我们力求创建一个便于管理的、可扩展的工厂体系。
package com.wang.design.chapter3;
import java.util.Random;
/**
* @author tracy
* 3——工厂模式——乘客实体类
*/
public class Passenger {
private int x,y;
private int gender;//0表示女性 1表示男性
public Passenger(int x,int y){
this.x=x;
this.y=y;
this.gender=new Random().nextInt(2);
}
public void show(){
System.out.println("乘客 坐标["+x+","+y+"]");
}
}
下面的Factory接口是工厂模式的核心,对于每个实体类都可以创建一个专门的工厂类来生产它,这样就能使代码具有低耦合、可扩展性。
package com.wang.design.chapter3;
/**
* @author tracy
* 3——工厂模式——工厂类
*/
public interface Factory {
Passenger create(int x,int y);
}
class PassengerFactory implements Factory{
@Override
public Passenger create(int x,int y) {
return new Passenger(x,y);
}
}
package com.wang.design.chapter3;
/**
* 3-工厂模式-客户端
*/
public class Client {
public static void main(String[] args) {
System.out.println("begin");
//批量生产10个乘客
Factory factory=new PassengerFactory();
for(int i=0;i<10;++i){
factory.create(0,0).show();
}
System.out.println("end");
}
}
//执行结果
//begin
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//乘客 坐标[0,0]
//end
在工厂方法模式中,不仅产品需要分类,工厂同样需要分类,与其把所有生产方式堆积在一个简单工厂类中,不如把生产方式放在具体的子类工厂中去实现,这样做对工厂的抽象化与多态化有诸多好处,避免了由于新加入产品类而反复修改同一个工厂类所带来的困扰,使后期的代码维护以及扩展更加直观、方便。
建造者模式的主要目的在于把烦琐的构建过程从不同对象中抽离出来,使其脱离并独立于产品类与工厂类,最终实现用同一套标准的制造工序能够产出不同的产品。
这一章的代码参考自《秒懂设计模式》。
我们以房屋施工为例。开发商找了施工方来为一块规划地修建别墅和公寓,由施工方提供两个施工队来对不同的建筑进行施工。由于开发商不是完全放心将任务全权交给施工方,因此又聘请了一位工程总监来对施工队的施工工作进行指导和监督。角色关系如下所示。
我们将建筑物简化为三个模块:地基、墙体、屋顶,施工队需要分别对三个模块进行施工和组装。
package com.wang.design.chapter4;
import java.util.ArrayList;
import java.util.List;
/**
* @author tracy
* 4-建造者模式-建筑物类
*/
public class Building {
private List<String> buildingComponents=new ArrayList<>();//组装组件
public void setBasement(String basement){//修建地基
buildingComponents.add(basement);
}
public void setWall(String wall){//修建墙体
buildingComponents.add(wall);
}
public void setRoof(String roof){//修建屋顶
buildingComponents.add(roof);
}
@Override
public String toString() {
String str="";
for(int i = buildingComponents.size()-1;i>=0;--i){
str += buildingComponents.get(i);
}
return str;
}
}
package com.wang.design.chapter4;
/**
* @author tracy
* 3-建造者模式-施工队
*/
public interface Builder {
void buildBasement();
void buildWall();
void buildRoof();
Building getBuilding();
}
/**
* 别墅施工队
*/
class HouseBuilder implements Builder{
private Building house;
public HouseBuilder(){
house=new Building();
}
/**
* 施工三步曲
*/
@Override
public void buildBasement() {
System.out.println("建造地基");
house.setBasement("╬╬╬╬╬╬╬╬╬╬\n");
}
@Override
public void buildWall() {
System.out.println("建造墙体");
house.setWall("|田|田 田|\n");
}
@Override
public void buildRoof() {
System.out.println("建造屋顶");
house.setRoof("╱◥███████◣\n");
}
@Override
public Building getBuilding() {
return house;
}
}
/**
* 公寓施工队
*/
class ApartmentBuilder implements Builder{
private Building apartment;
public ApartmentBuilder(){
apartment=new Building();
}
/**
* 施工三步曲
*/
@Override
public void buildBasement() {
System.out.println("建造地基");
apartment.setBasement("╚═════════╝\n");
}
@Override
public void buildWall() {
System.out.println("建造墙体");
for (int i = 0; i < 8; i++) {// 8层
apartment.setWall("║ □ □ □ □ ║\n");
}
}
@Override
public void buildRoof() {
System.out.println("建造屋顶");
apartment.setRoof("╔═════════╗\n");
}
@Override
public Building getBuilding() {
return apartment;
}
}
package com.wang.design.chapter4;
/**
* 4-建造者模式-工程监理
*/
public class Director {
private Builder builder;
public void setBuilder(Builder builder) {
this.builder = builder;
}
/**
* 控制施工流程
* @return
*/
public Building direct(){
System.out.println("=====工程项目启动=====");
builder.buildBasement();
builder.buildWall();
builder.buildRoof();
System.out.println("=====工程项目竣工=====");
return builder.getBuilding();
}
}
package com.wang.design.chapter4;
/**
* @author tracy
* 4-建造者模式-实际施工
*/
public class Client {
public static void main(String[] args) {
Director director=new Director();
//监理别墅施工队
director.setBuilder(new HouseBuilder());
System.out.println(director.direct());
//监理公寓施工队
director.setBuilder(new ApartmentBuilder());
System.out.println(director.direct());
}
}
这个模式的精髓就在于“封装”二字,把暴露在客户端的细节封装成一个大的系统,仅留给外部一个接口。只看文字描述可能不够直观,下面直接上代码。
下面我以【上一门课】为例。我们都知道,一门课需要经过上课考勤、提问、作业、考试几个步骤,如果不使用门面模式,代码如下:
package com.wang.design.chapter5;
/**
* 5-门面模式-不使用门面模式
*/
public class Client1 {
public static void main(String[] args) {
System.out.println("=====开始一门课程=====");
new Check().handle();//考勤
new Question().handle();//提问
new Task().handle();//作业
new Exam().handle();//考试
System.out.println("=====结束一门课程=====");
}
}
class Check{
public void handle(){
System.out.println("考勤......");
}
}
class Question{
public void handle(){
System.out.println("提问......");
}
}
class Task{
public void handle(){
System.out.println("交作业......");
}
}
class Exam{
public void handle(){
System.out.println("期末考试......");
}
}
执行过程
=====开始一门课程=====
考勤......
提问......
交作业......
期末考试......
=====结束一门课程=====
把大量代码都写在客户端的main()方法中看着非常的臃肿,很不优雅对吧?
我们来把【上一门课】这个过程封装一下:
package com.wang.design.chapter5;
/**
* @author tracy
* 5-门面模式
*/
public class Facade {
public void course(){
new Check().handle();//考勤
new Question().handle();//提问
new Task().handle();//作业
new Exam().handle();//考试
}
}
然后再重写一下我们的客户端:
package com.wang.design.chapter5;
/**
* @author tracy
*
* 5-门面模式
*/
public class Client2 {
public static void main(String[] args) {
System.out.println("=====开始一门课程=====");
new Facade().course();
System.out.println("=====结束一门课程=====");
}
}
现在,我们通过门面模式将一些没必要暴露在外部的细节封装了起来,仅留给外界访问接口,代码相比之前更加简洁优雅,各部分之间的耦合也进一步降低了。这就是门面模式的魅力,你学废了吗?
不知道你有没有观察过一棵大树的结构,它是典型的的层次结构。首先,从整体来看它是一棵树,这棵树由许多一级树枝组成;然后,每一条大的树枝又由规模更小的树枝组成…最小的树枝由一些绿叶组成。
不管我们从哪个层次观看这一棵树,都不难发现,树、树枝、树叶这些元素之间具有一定的从属关系;且无论是处于哪一个级别的元素,它们之间都具备了一定的共同特征,除了处于最低层级的树叶,其他的元素都包含了多个次级子对象的结构特征。
设想一下,如果我们为一棵树的每一个结点(树、树枝、树叶)都创建一个类,会造成什么样的结果——拥有成千上万个类,对于程序来说,这无疑是一种灾难。
为了写出优雅简洁的代码,我们完全可以将所有的树、树枝、树叶抽象为一种类,树相当于一种超级树枝,而树叶相当于一种没有子元素的树枝,通过组合不同的实例,就可以通过只设计一个类实现一整棵树。
下面请看我的代码示例。假设我们要打印这样一棵树。
package com.wang.design.chapter6;
import java.util.*;
/**
* @author tracy
*
* 6-组合模式
*/
public class Node {
private final List<Node> children=new ArrayList<>();
private int level=0;
private String name;
public Node(String name){
this.name=name;
}
public void setLevel(int level){
this.level=level;
}
public void addChild(Node child){
child.setLevel(this.level+1);
this.children.add(child);
}
//将层次可视化,并递归调用children的show()方法
public void show(){
System.out.println(this.name);
if(this.children.size()==0)return;
for(Node node:children){
for(int i=0;i<=this.level;++i) System.out.print(" ");
System.out.print("|_");
for(int i=0;i<this.level;++i) System.out.print("_");
node.show();
}
}
}
package com.wang.design.chapter6;
/**
* @author tracy
*
* 6-组合模式
*/
public class Client {
public static void main(String[] args) {
Node root=new Node("root");
Node branch1=new Node("branch1");
Node branch2=new Node("branch2");
Node leaf1=new Node("leaf1");
Node leaf2=new Node("leaf2");
Node leaf3=new Node("leaf3");
Node leaf4=new Node("leaf4");
root.addChild(branch1);
root.addChild(branch2);
root.addChild(leaf4);
branch1.addChild(leaf1);
branch1.addChild(leaf2);
branch2.addChild(leaf3);
root.show();
}
}
看一下运行效果,输出层次结构:
装饰器模式能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,区别在于方式的不同,后者是在编译时静态地通过对原始类的继承完成,而前者则是在程序运行时通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。
我以包装礼物为例,进行代码演示。
package com.wang.design.chapter7;
public interface Showable {
void show();//展示礼物的方法
}
class Gift implements Showable{
@Override
public void show() {
System.out.print("礼物");
}
}
package com.wang.design.chapter7;
/**
* Decorator为包装器父类
*/
public abstract class Decorator implements Showable{
protected Showable show_obj;
public Decorator(Showable show_obj){
this.show_obj=show_obj;//注入被装饰对象
}
@Override
public void show() {
show_obj.show();//调用默认展示方法
}
}
/**
* 包装盒
*/
class BoxDecorator extends Decorator{
public BoxDecorator(Showable show_obj){
super(show_obj);
}
@Override
public void show() {
System.out.print("包装盒【");
show_obj.show();
System.out.print("】包装盒");
}
}
/**
* 蝴蝶结
*/
class BowknotDecorator extends Decorator{
public BowknotDecorator(Showable show_obj){
super(show_obj);
}
@Override
public void show() {
System.out.print("蝴蝶结【");
show_obj.show();
System.out.print("】蝴蝶结");
}
}
package com.wang.design.chapter7;
public class Client {
public static void main(String[] args) {
Showable gift=new BowknotDecorator(new BoxDecorator(new Gift()));
gift.show();
}
}
执行结果:
蝴蝶结【包装盒【礼物】包装盒】蝴蝶结
到这里,我们通过低耦合、高扩展的设计模式,成功实现了对一个类的包装。
众所周知,人不能与牲口进行交流,原因就是人和牲口的语言系统并不能兼容。但如果我们非要和牲口交流呢,把人变成牲口或者把牲口变成人都是不现实的,更好的解决办法就是创造一个人畜交流大师(假设此人存在的话),这样一来,人还是原来的人,牲口也还是原来的牲口,不需要修改两个实体就能实现新的需求。即,通过低耦合的方式扩展了程序的功能。
下面,我们就来动手实现一下这个人畜交流大师。
这个类的对象只能与同类对象人进行交流。
package com.wang.design.chapter8;
public class Person {
private String name;
public Person(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public void talk(Person anothor){
System.out.println(this.name+"(人)正在与"+anothor.getName()+"(人)交流...");
}
}
这个类的对象只能与同类对象牛进行交流。
package com.wang.design.chapter8;
public class Cow {
private String name;
public Cow(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public void talk(Cow anothor){
System.out.println(this.name+"(牛)正在与"+anothor.getName()+"(牛)交流...");
}
}
首先,这个大师肯定得是个人,因此他继承自Person类,并且也默认继承了Person类的talk()方法,具备与其他人类交流的能力;其次,他具有一个特别的方法talkWithCow(),这使得他也具备了与牛交流的能力。
package com.wang.design.chapter8;
public class PersonWithCowTranslator extends Person{
public PersonWithCowTranslator(String name){
super(name);
}
public void talkWithCow(Cow cow){
System.out.println(this.getName()+"(人)正在与"+cow.getName()+"(牛)交流...");
}
}
package com.wang.design.chapter8;
public class Client {
public static void main(String[] args) {
Person person=new Person("小明");
Cow cow=new Cow("牛魔王");
PersonWithCowTranslator translator=new PersonWithCowTranslator("动物语言学大师");
person.talk(translator);//小明把想对牛魔王说的话告诉给大师
translator.talkWithCow(cow);//大师把小明想说的话转告给牛魔王,并获得牛魔王对小明的回复
translator.talk(person);//大师把牛魔王的回复转达给小明
}
}
小明(人)正在与动物语言学大师(人)交流...
动物语言学大师(人)正在与牛魔王(牛)交流...
动物语言学大师(人)正在与小明(人)交流...
至此,我们便在完全不修改Person类和Cow类的前提下扩展了我们需要的功能,这就是适配器模式的魅力。虽然我举的这个例子并不是那么实用,但其中体现的设计模式的思想完全可以应用到各种各样的实际情况中去。
享元模式的宗旨就是共享细粒度对象,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗,享元模式属于结构型模式。
适用场景:当系统中多处需要用到一些公共信息时,可以把这些信息封装到一个对象实现享元模式,避免重复创建对象带来系统的开销。比如我们熟悉的线程池就是基于的这种设计模式的思想。
优点:减少对象的创建,降低了系统中对象的数量,故而可以降低系统的使用内存,提高效率。
我以买电影票为例,展示这种设计模式的代码实现。
我们首先将所有买票实体类的共同行为抽取出来,形成一个Ticket接口,其中有两个方法需要实现类实现。setSeat()方法用于选择座位等级,info()方法用于获取电影票的信息。
public interface Ticket {
void info();//获取电影票信息
void setSeat(String seatType);//设置电影票座位等级:普通座、舒适座、豪华座
}
然后,我们定义一个Ticket接口的实现类CinemaTicket。每个实例都应该有一个一一对应的name属性和seatType属性,表明电影名称和座位等级。并定义了4个需要的方法。
class CinemaTicket implements Ticket{
private String name;//电影名称
private String seatType="普通座";//座位等级,默认为普通座
// 1 实例化
public CinemaTicket(String name){
this.name=name;
}
// 2 设置座位等级
@Override
public void setSeat(String seatType) {
this.seatType=seatType;
}
// 3 获得票价
public double getPrice(){
switch (this.seatType){
case "普通座":return 30.0;
case "舒适座":return 40.0;
default:return 50.0;//豪华座
}
}
// 4 获得所有电影票相关信息
@Override
public void info() {
System.out.println("当前影厅正在放映:"+this.name+",您购买的是:"+this.seatType+",票价为:"+this.getPrice()+"元。");
}
}
没学过享元模式的朋友可能会这么实现需求,买一张电影票new一个对象,买下一张再new一个…当你买了很多张电影票,不知不觉就new了很多个对象出来。当问题规模扩大到一定程度的时候,程序中会充斥着大量冗余的代码,性能下降,且难以维护。
享元模式的思想其实和单例模式有那么一点关系,单例模式是一个类只有一个对象,而享元模式是一个类只实例化需要的n(往往大于等于1)个对象。
这里又不得不提到两个词——内部状态、外部状态。前者是指存储在享元对象内部并且不会随环境的改变而改变的信息,一般对象之间可以共享;后者是指无法被所有对象共享的信息,这部分信息往往会随着环境的变化而改变。
在我们的示例中,CinemaTicket类中的name属性可以被视为是内部状态,同一个电影的所有CinemaTicket对象可以共享这部分信息;seatType属性被视为是外部状态,不同的对象可以设置不同的座位等级。
我们创建一个工厂类用来获取CinemaTicket对象。
public class CinemaTicketFactory {
private static final Map<String,Ticket> cinema_pool = new HashMap<>();//缓冲池
public static Ticket buyAndGetInfo(String name){
//当缓冲池中不存在name电影对应的对象时,创建一个并放入池子里
if(cinema_pool.get(name)==null){
System.out.println("新建对象");
cinema_pool.put(name,new CinemaTicket(name));
}
//获取需要的对象,而不必每次都重复new对象
System.out.println("获取到了对象");
return cinema_pool.get(name);
}
}
public class Client {
public static void main(String[] args) {
//买一张票
System.out.println("-----买票-----");
Ticket ticket=CinemaTicketFactory.buyAndGetInfo("战狼");
ticket.setSeat("普通座");
ticket.info();
//再买一张票
System.out.println("-----买票-----");
ticket=CinemaTicketFactory.buyAndGetInfo("战狼");
ticket.setSeat("舒适座");
ticket.info();
//再买一张票
System.out.println("-----买票-----");
ticket=CinemaTicketFactory.buyAndGetInfo("四大名捕");
ticket.setSeat("豪华座");
ticket.info();
}
}
执行结果
-----买票-----
新建对象
获取到了对象
当前影厅正在放映:战狼,您购买的是:普通座,票价为:30.0元。
-----买票-----
获取到了对象
当前影厅正在放映:战狼,您购买的是:舒适座,票价为:40.0元。
-----买票-----
新建对象
获取到了对象
当前影厅正在放映:四大名捕,您购买的是:豪华座,票价为:50.0元。
代理模式一般是指一个对象为另一个对象提供一种代理,从而通过代理对象来控制非代理对象的访问,代理对象在客户端和目标对象之间起到中介作用。静态代理是最基本的代理模式,更特殊的一种代理模式是动态代理模式。
下面实现一个标准的静态代理模式,实际上就是一个代理对象操作被代理对象进行某种动作并在运行时添加一些额外的动作。还是以上一章的买电影票为例。
public interface Ticket {
void buyTicket(String name,String time,String seat);
}
class CinemaTicket implements Ticket{
@Override
public void buyTicket(String name,String time,String seat){
System.out.println(name+" 时间:"+time+" 座位:"+seat);
}
}
class CinemaTicketAgent implements Ticket{
private CinemaTicket cinemaTicket;
public CinemaTicketAgent(CinemaTicket cinemaTicket){
this.cinemaTicket=cinemaTicket;
}
@Override
public void buyTicket(String name,String time,String seat){
before();
this.cinemaTicket.buyTicket(name,time,seat);
after();
}
public void before(){
System.out.println("-----选座位-----");
}
public void after(){
System.out.println("-----付款-----");
}
}
public class Client {
public static void main(String[] args) {
CinemaTicketAgent agent=new CinemaTicketAgent(new CinemaTicket());
agent.buyTicket("战狼","13:00","0712");
}
}
运行结果
-----选座位-----
战狼 时间:13:00 座位:0712
-----付款-----
虽然静态代理使用起来还是比较简单、直接,但实际上代理对象与被代理对象的耦合度仍旧较高,当需求发生变更时会涉及到较多源码上的修改。
在动态代理模式中,实例化过程是动态完成的,而不是由我们手动实现的。我以JDK动态代理为例,将这一章的静态代理实现修改成动态代理实现。
实现InvocationHandler接口 -> 根据注入的被代理对象获取代理对象 -> 调用代理对象方法
class CinemaTicketAgent implements InvocationHandler{
private Object target;//被代理对象
//根据注入的被代理对象获取代理对象
public Object getAgent(Object target){
this.target=target;
Class<?> targetClass=target.getClass();
//创建并返回代理对象
return Proxy.newProxyInstance(targetClass.getClassLoader(),targetClass.getInterfaces(),this);
}
//调用代理对象方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object obj=method.invoke(this.target,args);
after();
return obj;
}
public void before(){
System.out.println("-----选座位-----");
}
public void after(){
System.out.println("-----付款-----");
}
}
public class Client {
public static void main(String[] args) {
//静态代理
//CinemaTicketAgent agent=new CinemaTicketAgent(new CinemaTicket());
//agent.buyTicket("战狼","13:00","0712");
//动态代理
Ticket ticket=(Ticket) new CinemaTicketAgent().getAgent(new CinemaTicket());
ticket.buyTicket("爸爸去哪儿","14:20","0806");
}
}
运行结果
-----选座位-----
爸爸去哪儿 时间:14:20 座位:0806
-----付款-----
至此,我们实现了耦合度更低的动态代理模式。
桥接模式和前面的组合模式是有一些类似的,但各有侧重点。
桥接模式属于结构型模式,主要目的是通过聚合(组合)的方式建立两个类之间的关系,而不是通过继承来实现,从而达到解耦抽象和实现的目的。
设想一下,现在我们需要实现一个画画功能。首先画笔的颜色有三种,分别为红、绿、紫;其次,画笔的材质也有三种,分别为水彩、素描、油画;最后,画笔能画的形状也有三种,分别为三角形、矩形、圆形。如果是基于继承来实现这个需求,那么需要的具体的实现类为3x3x3=27种。对于这种小规模的问题或许还能勉强应付,但如果面对的是那种规模特别大、功能特别复杂的系统,那这种方法无疑是非常笨拙且效率低下的。
不如换一个思路,比如,我们可以设置一支画笔类Pen,然后在其中定义color属性、texture属性、shape形状,然后通过传参的方式设置具体的属性值,这样,就可以仅仅定义一支画笔而实现出27种不同的笔实例。而这个Pen类,就相当于一个桥梁,将三种颜色、三种材质、三种形状桥接起来。
package com.wang.design.chapter11;
/**
* @author tracy
*
* 11-桥接模式
*/
public interface Color {
void showColor();
}
class Red implements Color{
@Override
public void showColor() {
System.out.print("[红]");
}
}
class Green implements Color{
@Override
public void showColor() {
System.out.print("[绿]");
}
}
class Purple implements Color{
@Override
public void showColor() {
System.out.print("[紫]");
}
}
package com.wang.design.chapter11;
/**
* @author tracy
*
* 11-桥接模式
*/
public interface Texture {
void showTexture();
}
class Water implements Texture{
@Override
public void showTexture() {
System.out.print("[水彩]");
}
}
class Sketch implements Texture{
@Override
public void showTexture() {
System.out.print("[素描]");
}
}
class Oil implements Texture{
@Override
public void showTexture() {
System.out.print("[油画]");
}
}
package com.wang.design.chapter11;
/**
* @author tracy
*
* 11-桥接模式
*/
public interface Shape {
void shape();
}
class Tri implements Shape{
@Override
public void shape() {
System.out.print("△");
}
}
class Rect implements Shape{
@Override
public void shape() {
System.out.print("□");
}
}
class Round implements Shape{
@Override
public void shape() {
System.out.print("○");
}
}
package com.wang.design.chapter11;
/**
* @author tracy
*
* 11-桥接模式
*/
public class Pen {
private Color color;
private Texture texture;
private Shape shape;
public Pen(Color color, Texture texture, Shape shape) {
this.color = color;
this.texture = texture;
this.shape = shape;
}
public void print(){
this.color.showColor();
this.texture.showTexture();
this.shape.shape();
System.out.println();
}
}
package com.wang.design.chapter11;
/**
* @author tracy
*
* 11-桥接模式
*/
public class Client {
public static void main(String[] args) {
Pen pen=new Pen(new Red(),new Sketch(),new Rect());
pen.print();
pen=new Pen(new Green(),new Water(),new Tri());
pen.print();
}
}
运行效果
[红][素描]□
[绿][水彩]△
至此,本博客的前二分之一内容已经更新完毕,后半部分我将介绍剩下的若干种行为类设计模式。
模板方法模式非常类似于定制表格,设计者先将所有需要填写的信息头(字段名)抽取出来,再将它们整合在一起成为一种既定格式的表格,最后让填表人按照这个标准化模板去填写自己特有的信息,而不必为书写内容、先后顺序、格式而感到困扰。
具体地说,就是把所有子类通用的信息和行为抽象出来放在父类中,建立抽象方法或非抽象的通用方法,然后由子类去继承和实现。下面我以[上数学课]和[上英语课]为例,展示模板方法模式的代码实现。
package com.wang.design.chapter12;
public abstract class Course {
abstract void register();//选课
abstract void homework();//做作业
abstract void exam();//考试
public void show(){//通用方法
this.register();
this.homework();
this.exam();
}
}
class Math extends Course{
@Override
void register() {
System.out.println("=====数学课开课了=====");
}
@Override
void homework() {
System.out.println("=====完成数学课平时=====");
}
@Override
void exam() {
System.out.println("=====进行数学期末考试=====");
}
}
class English extends Course{
@Override
void register() {
System.out.println("=====英语课开课了=====");
}
@Override
void homework() {
System.out.println("=====完成英语课平时=====");
}
@Override
void exam() {
System.out.println("=====进行英语期末考试=====");
}
}
package com.wang.design.chapter12;
public class Client {
public static void main(String[] args) {
Course course=new Math();
course.show();
course=new English();
course.show();
}
}
执行结果
=====数学课开课了=====
=====完成数学课平时=====
=====进行数学期末考试=====
=====英语课开课了=====
=====完成英语课平时=====
=====进行英语期末考试=====
迭代器满足了对集合迭代的需求,并向外部提供了一种统一的迭代方式,而不必暴露集合的内部数据结构。
相信很多写过代码的朋友对迭代器并不会陌生,像我们平时常见的一些集合基本上都实现了迭代器,用来对集合中的元素进行遍历。相比于普通的循环,迭代器提供的遍历可以让我们不必关注集合的总长度以及当前的索引位置。
下面我以实现一个[行车记录仪]为例。这个行车记录仪中以先进先出原则存放10条视频记录,当遍历它的时候,将按照最新记录的顺序遍历。(本章代码参考自《秒懂设计模式》)
package com.wang.design.chapter12;
import java.util.Iterator;
public class DriverRecorder implements Iterable<String>{
private String[] records=new String[10];//最多存储10条记录
private int index=-1;//记录最新一条记录存放的位置
//插入一条记录
public void append(String record){
if(index==9){//records满了,覆盖第一条
index=0;
}else{
++index;
}
records[index]=record;
}
//获取迭代器
@Override
public Iterator<String> iterator(){
return new Itr();
}
private class Itr implements Iterator<String>{
int loopCount=0;//用来遍历计数
int cursor=index;//用来记录遍历位置
@Override
public boolean hasNext() {
return loopCount<10;
}
//按最新插入的顺序遍历元素
@Override
public String next() {
int i=cursor;//用来记录需要返回的元素的位置
if(cursor==0)cursor=9;
else --cursor;
++loopCount;
return records[i];
}
}
}
package com.wang.design.chapter12;
import java.util.Iterator;
public class Client {
public static void main(String[] args) {
DriverRecorder recorder=new DriverRecorder();
//插入13条视频
for(int i=0;i<13;++i){
recorder.append("--视频"+i+"--");
}
//遍历recorder中记录的视频
Iterator<String> it= recorder.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
执行代码
--视频12--
--视频11--
--视频10--
--视频9--
--视频8--
--视频7--
--视频6--
--视频5--
--视频4--
--视频3--
在软件系统中,当一个业务需要经历一系列业务对象去处理时,我们可以把这些业务对象串联起来成为一条业务责任链,请求者可以直接通过访问业务责任链来完成业务的处理,最终实现请求者与响应者的解耦。
这一章以[报账审批]为例。假设审批流程中有三个级别的职位,分别是Staff、Manager、CFO,当金额小于等于1000元时交给Staff审批,当金额小于等于5000元时交给Manager审批,当金额小于等于10000元时交给CFO审批。当低权限的员工面临更高金额的审批时需要将审批单提交给更高级别的工作人员。
package com.wang.design.chapter14;
public abstract class Approver {
protected String name;
protected Approver higherApprover;
public Approver(String name){
this.name=name;
}
public void setHigherApprover(Approver higherApprover) {
this.higherApprover = higherApprover;
}
public abstract void approve(int amount);//审批方法
}
class Staff extends Approver{
public Staff(String name){
super(name);
}
@Override
public void approve(int amount) {
System.out.println("-----开始审批-----");
if(amount<=1000){
System.out.println("staff"+this.name+"审批通过");
}else if(this.higherApprover==null){
System.out.println("staff"+this.name+"审批不通过");
}else{
System.out.println("staff"+this.name+"将流程提交给更高权限的工作人员");
this.higherApprover.approve(amount);
}
}
}
class Manager extends Approver{
public Manager(String name){
super(name);
}
@Override
public void approve(int amount) {
if(amount<=5000){
System.out.println("manager"+this.name+"审批通过");
}else if(this.higherApprover==null){
System.out.println("manager"+this.name+"审批不通过");
}else{
System.out.println("manager"+this.name+"将流程提交给更高权限的工作人员");
this.higherApprover.approve(amount);
}
}
}
class CFO extends Approver{
public CFO(String name){
super(name);
}
@Override
public void approve(int amount) {
if(amount<=10000){
System.out.println("cfo"+this.name+"审批通过");
}else{
System.out.println("cfo"+this.name+"审批不通过");
}
}
}
package com.wang.design.chapter14;
public class Client {
public static void main(String[] args) {
Staff staff=new Staff("小王");
Manager manager=new Manager("张冬良");
CFO cfo=new CFO("程哥");
staff.setHigherApprover(manager);
manager.setHigherApprover(cfo);
staff.approve(600);
staff.approve(1200);
staff.approve(6700);
staff.approve(10020);
}
}
测试结果
-----开始审批-----
staff小王审批通过
-----开始审批-----
staff小王将流程提交给更高权限的工作人员
manager张冬良审批通过
-----开始审批-----
staff小王将流程提交给更高权限的工作人员
manager张冬良将流程提交给更高权限的工作人员
cfo程哥审批通过
-----开始审批-----
staff小王将流程提交给更高权限的工作人员
manager张冬良将流程提交给更高权限的工作人员
cfo程哥审批不通过
策略模式强调的是行为的灵活切换,比如一个类的多个方法有着类似的行为接口,可以将它们抽离出来作为一系列策略类,在运行时灵活对接,变更其算法策略,以适应不同的场景。
本章以[玻璃杯]为例。一个玻璃杯,可以装啤酒、咖啡、纯净水、牛奶等等,如果每装一种饮料就创建一个新的杯子,长此以往,系统会变得臃肿不堪。比起不断地创建不同的杯子,不如创建一套灵活的策略,用一个杯子通过策略的运用装入不同的饮料,实现可插拔的功能。
package com.wang.design.chapter15;
public class Glass {
private Drinks drinks;
public void setDrinks(Drinks drinks) {
this.drinks = drinks;
}
public void drinking(){
System.out.println("玻璃杯中装入了"+drinks.name);
}
}
package com.wang.design.chapter15;
public abstract class Drinks {
protected String name;
public Drinks(String name){
this.name=name;
}
}
class Milk extends Drinks{
public Milk(String name) {
super(name);
}
}
class Tea extends Drinks{
public Tea(String name) {
super(name);
}
}
package com.wang.design.chapter15;
public class Client {
public static void main(String[] args) {
Glass glass=new Glass();
glass.setDrinks(new Milk("纯牛奶"));
glass.drinking();
glass.setDrinks(new Tea("乌龙茶"));
glass.drinking();
}
}
测试结果
玻璃杯中装入了纯牛奶
玻璃杯中装入了乌龙茶
状态指事物基于所处的状况、形态表现出的不同的行为特性。状态模式构架出一套完备的事物内部状态转换机制,并将内部状态包裹起来且对外部不可见,使其行为能随其状态的改变而改变,同时简化了事物的复杂的状态变化逻辑。
这一章以[红灯、黄灯、绿灯的切换]为例。首先为我们的信号灯切换制定出规则,红灯亮60s,接着黄灯亮10s,然后绿灯亮30s,绿灯亮完红灯亮,开始新一轮的循环。
package com.wang.design.chapter16;
public abstract class Light {
protected static int counter=0;//用来读秒
protected abstract void on();
}
class Red extends Light{
@Override
public void on() {
System.out.println(counter+"s 红灯...");
}
}
class Yellow extends Light{
@Override
public void on() {
System.out.println(counter+"s 黄灯...");
}
}
class Green extends Light{
@Override
public void on() {
System.out.println(counter+"s 绿灯...");
}
}
package com.wang.design.chapter16;
import java.util.Timer;
import java.util.TimerTask;
public class Switcher {
private Light light;
public void lightSwitch(){
TimerTask timerTask=new TimerTask() {
@Override
public void run() {
if(Light.counter<60){
light=new Red();
light.on();
Light.counter+=5;
}else if(Light.counter<70){
light=new Yellow();
light.on();
Light.counter+=5;
}else{
light=new Green();
light.on();
Light.counter+=5;
if(Light.counter==100) Light.counter=0;
}
}
};
new Timer().schedule(timerTask,0,5000);//设置定时任务,延迟0s,每过5s执行一次
}
}
package com.wang.design.chapter16;
public class Client {
public static void main(String[] args) {
new Switcher().lightSwitch();
}
}
执行结果
0s 红灯...
5s 红灯...
10s 红灯...
15s 红灯...
20s 红灯...
25s 红灯...
30s 红灯...
35s 红灯...
40s 红灯...
45s 红灯...
50s 红灯...
55s 红灯...
60s 黄灯...
65s 黄灯...
70s 绿灯...
75s 绿灯...
80s 绿灯...
85s 绿灯...
90s 绿灯...
95s 绿灯...
0s 红灯...
5s 红灯...
......
备忘录用来记录曾经发生过的事情,使回溯历史变得切实可行。备忘录模式则可以在不破坏元对象封装性的前提下捕获其在某些时刻的内部状态,并像历史快照一样将它们保留在元对象之外,以备恢复之用。
这一章以实现一个[备忘录类]为例。这个类有几个重要功能:1以行为单位插入内容;2以行为单位删除内容;3一键清空内容;4备份功能。下面就来实现它。
package com.wang.design.chapter17;
import java.util.LinkedList;
public class Remember {
private String title;
private LinkedList<String> content;
private LinkedList<LinkedList<String>> historyList;//最多备份5条
public Remember(String title){
this.title=title;
this.content=new LinkedList<>();
this.historyList=new LinkedList<>();
}
// 1以行为单位插入内容,每插入一行自动备份
public void append(String row){
System.out.println("添加:"+row);
this.content.addLast(row);
this.backup();
this.show();
}
// 2以行为单位删除内容
public void delete(){
if(this.content.size()==0){
System.out.println("内容为空,无法删除!");
return;
}
System.out.println("删除:"+this.content.removeLast());
this.show();
}
// 3一键清空内容
public void clear(){
this.content=new LinkedList<>();
System.out.println("已清空内容");
this.show();
}
// 4展示最新备份内容
public void latestHistory(){
System.out.println("正在获取最新备份内容:");
if(this.historyList.size()==0){
System.out.println("没有备份!");
return;
}
for(String row:this.historyList.getLast()){
System.out.println(row);
}
System.out.println();
}
// 5退回到最新备份内容
public void recovery(){
System.out.println("正在恢复...");
this.content=this.historyList.getLast();
System.out.println("备忘录恢复成功:");
this.show();
}
// 私有方法:展示所有已输入内容
private void show(){
System.out.println("------<"+this.title+">------");
if(this.content.size()==0) {
System.out.println("null");
System.out.println();
return;
}
for(String row:this.content){
System.out.println(row);
}
System.out.println();
}
// 私有方法:备份功能
private void backup(){
this.historyList.addLast(new LinkedList<>(this.content));
if(this.historyList.size()==6)this.historyList.removeFirst();
}
}
package com.wang.design.chapter17;
public class Client {
public static void main(String[] args) {
// 新建备忘录
Remember remember=new Remember("待办事项");
// 插入文本
remember.append("算法刷题");
remember.append("买菜");
remember.append("背单词");
// 删除一行
remember.delete();
// 获取最新备份记录
remember.latestHistory();
// 恢复
remember.recovery();
// 情况
remember.clear();
// 恢复
remember.recovery();
}
}
测试结果
添加:算法刷题
------<待办事项>------
算法刷题
添加:买菜
------<待办事项>------
算法刷题
买菜
添加:背单词
------<待办事项>------
算法刷题
买菜
背单词
删除:背单词
------<待办事项>------
算法刷题
买菜
正在获取最新备份内容:
算法刷题
买菜
背单词
正在恢复...
备忘录恢复成功:
------<待办事项>------
算法刷题
买菜
背单词
已清空内容
------<待办事项>------
null
正在恢复...
备忘录恢复成功:
------<待办事项>------
算法刷题
买菜
背单词
中介是在事物之间传播信息的中间媒介。中介模式为对象构架出一个互动平台,通过减少对象间的依赖程度以达到解耦的目的。
本章以实现一个[简易的聊天室]为例,通过聊天室这个平台,实现用户与用户之间的解耦。
package com.wang.design.chapter18;
import java.util.ArrayList;
import java.util.Objects;
public class User {
private String name;
private ChatRoom chatRoom;
private ArrayList<String> msgBox;//接收消息的容器
public User(String name){
this.name=name;
this.msgBox=new ArrayList<>();
}
public String getName() {
return name;
}
//加入聊天室
public void setChatRoom(ChatRoom chatRoom) {
this.chatRoom = chatRoom;
chatRoom.register(this);//在聊天室中登记注册用户信息
}
//发言
public void talk(String words,User to){
System.out.println(this.name+"正在发言...");
//不指定用户,则默认为广播
if(to==null){
chatRoom.broadcast(this,words);
}
//指定向特定用户发送消息
else chatRoom.toSomeone(this,to,words);
System.out.println();
}
//接收消息
public void receive(String msg){
this.msgBox.add(msg);
}
//获取消息
public void listen(){
System.out.println("用户["+this.name+"]正在收听消息:");
for (String str:this.msgBox){
System.out.println(str);
}
System.out.println();
}
//判断两个用户是否为同一个
@Override
public boolean equals(Object obj) {
if(obj==null||this.getClass()!=obj.getClass())return false;
User u=(User)obj;
return Objects.equals(this.name,u.getName());
}
}
package com.wang.design.chapter18;
import java.util.HashMap;
public class ChatRoom {
private String name;//群聊名称
private HashMap<String,User> members;//群聊成员
public ChatRoom(String name){
this.name=name;
members=new HashMap<>();
System.out.println("聊天群["+this.name+"]已创建成功");
System.out.println();
}
//新成员加入
public void register(User user){
members.put(user.getName(),user);
System.out.println("用户["+user.getName()+"]加入聊天群");
System.out.println();
}
//广播消息
public void broadcast(User from,String words){
if(this.members.size()!=0){
for(User user:this.members.values()){
user.receive(from.getName()+":"+words);
}
}
}
//给特定用户发消息
public void toSomeone(User from,User to,String words){
if(to.equals(this.members.getOrDefault(to.getName(),null))){
to.receive(from.getName()+"(私):"+words);
}
}
}
package com.wang.design.chapter18;
public class Client {
public static void main(String[] args) {
//创建三个用户
User user1=new User("小明");
User user2=new User("小花");
User user3=new User("团团");
//创建聊天室
ChatRoom chatRoom=new ChatRoom("卷王交流群");
//用户加入聊天室
user1.setChatRoom(chatRoom);
user2.setChatRoom(chatRoom);
user3.setChatRoom(chatRoom);
//用户发言
user1.talk("你们做作业了吗",null);
user1.talk("帮我签一下到",user2);//私聊
user3.talk("后天考试",null);
//用户收听消息
user1.listen();
user2.listen();
user3.listen();
}
}
运行结果
聊天群[卷王交流群]已创建成功
用户[小明]加入聊天群
用户[小花]加入聊天群
用户[团团]加入聊天群
小明正在发言...
小明正在发言...
团团正在发言...
用户[小明]正在收听消息:
小明:你们做作业了吗
团团:后天考试
用户[小花]正在收听消息:
小明:你们做作业了吗
小明(私):帮我签一下到
团团:后天考试
用户[团团]正在收听消息:
小明:你们做作业了吗
团团:后天考试
Process finished with exit code 0
命令是一个对象向另一个或多个对象发送的指令信息。命令的发送方负责下达指令,接收方则根据命令触发相应的行为。命令模式能够将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,以使命令的请求方与执行方解耦,双方只通过传递各种命令过象来完成任务。
这个设计模式简单来说就是,我有一个类A,现在我想通过类B操控A的一些行为,但是我又不想直接在类B中出现A中的实例化过程之类的(不想耦合),所以我就再创建一个Commond类C来控制A,这样B和A不就解耦了吗。
下面我以[开/关空调]为例来实现这种设计模式。
package com.wang.design.chapter19;
/**
* @author tracy
*
* 19-命令模式
*/
public class AirConditioner {
private String name;
private boolean on=false;
public AirConditioner(String name) {
this.name = name;
}
//开
public void turnOn(){
System.out.println(this.name+"打开了");
if(!on)this.on=true;
}
//关
public void turnOff(){
System.out.println(this.name+"关闭了");
if(on)this.on=false;
}
}
package com.wang.design.chapter19;
public class AirCommand {
private AirConditioner airConditioner;
public AirCommand(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
public void onExecute(){
this.airConditioner.turnOn();
}
public void offExecute(){
this.airConditioner.turnOff();
}
}
package com.wang.design.chapter19;
public class Switcher {
private AirCommand airCommand;
public Switcher(AirCommand airCommand) {
this.airCommand = airCommand;
}
public void buttonPush(){
System.out.println("按下开关");
this.airCommand.onExecute();
System.out.println();
}
public void buttonPop(){
System.out.println("放下开关");
this.airCommand.offExecute();
System.out.println();
}
}
package com.wang.design.chapter19;
public class Client {
public static void main(String[] args) {
AirCommand airCommand=new AirCommand(new AirConditioner("美的空调"));
Switcher switcher=new Switcher(airCommand);
switcher.buttonPush();
switcher.buttonPop();
}
}
执行结果
按下开关
美的空调打开了
放下开关
美的空调关闭了
访问者模式主要解决的是数据与算法的耦合问题,尤其是在数据结构比较稳定,而算法多变的情况下。为了不「污染」数据本身,访问者模式会将多种算法独立归类,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并且确保算法的自由扩展。
简单来说,访问者模式就是,将数据本身与数据的访问过程分开实现,使二者解耦,从而保证数据不受污染。
本章以[超市商品计价为例]。我们都知道,超市有着琳琅满目的商品,每件商品都有着不同的计价方式和优惠方式,但是无论计价方式多么复杂,最后都交给收银员(访问者)统一处理。
当我们仅考虑一件商品的结算时是很容易实现的。
商品类定义
package com.wang.design.chapter20;
import java.time.LocalDate;
/**
* @author tracy
* 20-访问者模式
*/
// 商品实体类继承体系
public abstract class Product {
private String name;//商品名称
private LocalDate productedDate;//生产日期,后面打折会用到
private float price;//单价
public Product(String name, LocalDate productedDate, float price) {
this.name = name;
this.productedDate = productedDate;
this.price = price;
}
public String getName() {
return name;
}
public LocalDate getProductedDate() {
return productedDate;
}
public float getPrice() {
return price;
}
}
//第一种商品:袋装果干
class DriedFruit extends Product{
public DriedFruit(String name, LocalDate productedDate, float price) {
super(name, productedDate, price);
}
}
//第二种商品:散装水果
class Fruit extends Product{
private float weight;//散装商品需要称重,定义一个新的属性
public float getWeight() {
return weight;
}
public Fruit(String name, LocalDate productedDate, float price,float weight) {
super(name, productedDate, price);
this.weight=weight;
}
}
//第三种商品:瓶装饮料
class Drink extends Product{
public Drink(String name, LocalDate productedDate, float price) {
super(name, productedDate, price);
}
}
计价类(访问者)
package com.wang.design.chapter20;
import java.time.LocalDate;
/**
* @author tracy
* 20-访问者模式
*/
public interface Visitor {
//为每一种商品实现一种计价方式
void visit(DriedFruit driedFruit);
void visit(Fruit fruit);
void visit(Drink drink);
}
//打折计价类的实现
class DiscountVisitor implements Visitor{
private LocalDate checkDate;//定义一个结账日期
private float total;//定义一个结账总金额
public DiscountVisitor(LocalDate checkDate) {
this.checkDate = checkDate;
this.total=0f;
System.out.println("结账日期:"+checkDate);
}
@Override
public void visit(DriedFruit driedFruit) {
System.out.println("=====果干【"+driedFruit.getName()+"】=====");
float rate=1;//打折率
long days=checkDate.toEpochDay()-driedFruit.getProductedDate().toEpochDay();
if(days>180){
rate=0;
System.out.println("果干已过期,请勿食用!");
}else if(days>60){//超过两个月,8折
rate=0.8f;
}else{//默认9折
rate=0.9f;
}
float bill=driedFruit.getPrice()*rate;
System.out.println(driedFruit.getName()+"1件共计"+bill+"元");
this.total+=bill;
System.out.println("当前消费总金额"+this.total+"元");
}
@Override
public void visit(Fruit fruit) {
System.out.println("=====水果【"+fruit.getName()+"】=====");
float rate=1;//打折率
long days=checkDate.toEpochDay()-fruit.getProductedDate().toEpochDay();
if(days>7){
rate=0;
System.out.println("水果已过期,请勿食用!");
}else if(days>3){//超过3天,5折
rate=0.5f;
}else{//默认9折
rate=0.9f;
}
float bill=fruit.getPrice()*rate;
System.out.println(fruit.getName()+fruit.getWeight()+"斤共计"+bill+"元");
this.total+=bill;
System.out.println("当前消费总金额"+this.total+"元");
}
@Override
public void visit(Drink drink) {
System.out.println("=====饮料【"+drink.getName()+"】=====");
System.out.println(drink.getName()+"1件共计"+drink.getPrice()+"元");
this.total+=drink.getPrice();//不打折
System.out.println("当前消费总金额"+this.total+"元");
}
}
客户端测试一下
package com.wang.design.chapter20;
import java.time.LocalDate;
/**
* @author tracy
* 20-访问者模式
*/
public class Client {
public static void main(String[] args) {
//一袋芒果干,单价15元
DriedFruit driedFruit=new DriedFruit("芒果干", LocalDate.of(2022,3,1),15.0f);
//两斤苹果,单价5.2元
Fruit apple=new Fruit("苹果",LocalDate.of(2022,6,3),5.2f,2.0f);
//一瓶红酒,单价125元
Drink wine=new Drink("红酒",LocalDate.of(2020,4,3),125.0f);
Visitor discountVisitor=new DiscountVisitor(LocalDate.now());
discountVisitor.visit(driedFruit);//计价
discountVisitor.visit(wine);//计价
discountVisitor.visit(apple);//计价
}
}
执行结果
结账日期:2022-06-05
=====果干【芒果干】=====
芒果干1件共计12.0元
当前消费总金额12.0元
=====饮料【红酒】=====
红酒1件共计125.0元
当前消费总金额137.0元
=====水果【苹果】=====
苹果2.0斤共计4.68元
当前消费总金额141.68元
Process finished with exit code 0
单价商品的计价实现还是比较简单的,现在我们考虑实现批量计价。
一种很容易想到的实现方式是将商品类型处理为泛型:
package com.wang.design.chapter20;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author tracy
* 20-访问者模式
*/
public class Client {
public static void main(String[] args) {
//一袋芒果干,单价15元
//两斤苹果,单价5.2元
//一瓶红酒,单价125元
List<Product> products= Arrays.asList(
new DriedFruit("芒果干", LocalDate.of(2022,3,1),15.0f),
new Fruit("苹果",LocalDate.of(2022,6,3),5.2f,2.0f),
new Drink("红酒",LocalDate.of(2020,4,3),125.0f)
);
//批量结算
Visitor discountVisitor=new DiscountVisitor(LocalDate.now());
for(Product product:products){
discountVisitor.visit(product);//这里会报错
}
}
}
但由于传入的类型统一被视为是Product类型,系统并不知道具体是哪一种商品类,因此这样的修改并不能达到批量计价的目的。
为了实现批量计价的功能,这里引入一种双派发机制。
增加Acceptable接口,让每个商品类实现此接口,主动接待访问者(相当于将商品类型放入购物车),向visitor派发自己。
public interface Acceptable {
void accept(Visitor visitor);
}
package com.wang.design.chapter20;
import java.time.LocalDate;
/**
* @author tracy
* 20-访问者模式
*/
// 商品实体类继承体系
public abstract class Product{
private String name;//商品名称
private LocalDate productedDate;//生产日期,后面打折会用到
private float price;//单价
public Product(String name, LocalDate productedDate, float price) {
this.name = name;
this.productedDate = productedDate;
this.price = price;
}
public String getName() {
return name;
}
public LocalDate getProductedDate() {
return productedDate;
}
public float getPrice() {
return price;
}
}
//第一种商品:袋装果干
class DriedFruit extends Product implements Acceptable{
public DriedFruit(String name, LocalDate productedDate, float price) {
super(name, productedDate, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
//第二种商品:散装水果
class Fruit extends Product implements Acceptable{
private float weight;//散装商品需要称重,定义一个新的属性
public float getWeight() {
return weight;
}
public Fruit(String name, LocalDate productedDate, float price,float weight) {
super(name, productedDate, price);
this.weight=weight;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
//第三种商品:瓶装饮料
class Drink extends Product implements Acceptable{
public Drink(String name, LocalDate productedDate, float price) {
super(name, productedDate, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
客户端测试
package com.wang.design.chapter20;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author tracy
* 20-访问者模式
*/
public class Client {
public static void main(String[] args) {
//一袋芒果干,单价15元
//两斤苹果,单价5.2元
//一瓶红酒,单价125元
List<Acceptable> products= Arrays.asList(
new DriedFruit("芒果干", LocalDate.of(2022,3,1),15.0f),
new Fruit("苹果",LocalDate.of(2022,6,3),5.2f,2.0f),
new Drink("红酒",LocalDate.of(2020,4,3),125.0f)
);
//批量结算
Visitor discountVisitor=new DiscountVisitor(LocalDate.now());
for(Acceptable product:products){
product.accept(discountVisitor);
}
}
}
执行过程
结账日期:2022-06-05
=====果干【芒果干】=====
芒果干1件共计12.0元
当前消费总金额12.0元
=====水果【苹果】=====
苹果2.0斤共计4.68元
当前消费总金额16.68元
=====饮料【红酒】=====
红酒1件共计125.0元
当前消费总金额141.68元
至此,访问者模式已完全实现。
观察者模式可以针对被观察对象与观察者对象之间一对多的依赖关系建立起一种行为自动触发机制,当被观察对象状态发生变化时主动对外发起广播,以通知所有观察者做出响应。
在一般情况下,观察者总是处于非常忙碌的状态,因为它总是在一遍又一遍地轮询被观察者,直到被观察者的状态发生变化。这样做无疑会给系统带来很多额外的负担。观察者模式则反其道而行之,与其让观察者无休止地询问,不如让被观察者在状态发生变化之后,主动通知观察者前来访问。
本章以[商店卖货]为例。
package com.wang.design.chapter21;
import java.util.ArrayList;
import java.util.List;
/**
* @author tracy
* 21-观察者模式
*/
public class Shop {
private String name;//商店名
private List<String> handicraft,stationary,phone;//售卖种类:手工艺品、文具、手机
private List<Buyer> buyers;//买家预定
public Shop(String name){
this.name=name;
this.handicraft=new ArrayList<>();
this.stationary=new ArrayList<>();
this.phone=new ArrayList<>();
buyers=new ArrayList<>();
System.out.println(this.name+"开张了!");
}
public List<String> getHandicraft() {
return handicraft;
}
public List<String> getStationary() {
return stationary;
}
public List<String> getPhone() {
return phone;
}
//商家登记
public void register(Buyer buyer){
this.buyers.add(buyer);
}
//进货
public void purchaseHandicraft(String product){
this.handicraft.add(product);
notifyBuyers();
}
public void purchaseStationary(String product){
this.stationary.add(product);
notifyBuyers();
}
public void purchasePhone(String product){
this.phone.add(product);
notifyBuyers();
}
//通知买家
public void notifyBuyers(){
this.buyers.stream().forEach(b->b.inform(this));
}
}
package com.wang.design.chapter21;
import java.util.List;
/**
* @author tracy
* 21-观察者模式
*/
public abstract class Buyer {
protected String name;
public Buyer(String name) {
this.name = name;
}
//来自商家的到货通知
public abstract void inform(Shop shop);
}
class HandicraftBuyer extends Buyer{
public HandicraftBuyer(String name) {
super(name);
}
@Override
public void inform(Shop shop) {
List<String> products=shop.getHandicraft();
if(products.size()!=0){
System.out.println("====="+this.name+"买入手工艺品=====");
for (String str:products){
System.out.println(str);
}
products.clear();
}
}
}
class StatonaryBuyer extends Buyer{
public StatonaryBuyer(String name) {
super(name);
}
@Override
public void inform(Shop shop) {
List<String> products=shop.getStationary();
if(products.size()!=0){
System.out.println("====="+this.name+"买入文具=====");
for (String str:products){
System.out.println(str);
}
products.clear();
}
}
}
class PhoneBuyer extends Buyer{
public PhoneBuyer(String name) {
super(name);
}
@Override
public void inform(Shop shop) {
List<String> products=shop.getPhone();
if(products.size()!=0){
System.out.println("====="+this.name+"买入手机=====");
for (String str:products){
System.out.println(str);
}
products.clear();
}
}
}
package com.wang.design.chapter21;
/**
* @author tracy
* 21-观察者模式
*/
public class Client {
public static void main(String[] args) {
Shop shop=new Shop("解忧杂货铺");
Buyer handyBuyer=new HandicraftBuyer("手工艺品买手");
Buyer phoneBuyer=new PhoneBuyer("手机买手");
Buyer stationaryBuyer=new StatonaryBuyer("文具买手");
//买家登记
shop.register(handyBuyer);
shop.register(phoneBuyer);
shop.register(stationaryBuyer);
//到货了
shop.purchaseHandicraft("兵马俑摆件");
shop.purchaseHandicraft("海螺捕梦网");
shop.purchaseHandicraft("古风沙漏");
shop.purchaseStationary("直尺");
shop.purchaseStationary("书立");
shop.purchaseStationary("订书机");
shop.purchasePhone("华为荣耀20");
shop.purchasePhone("华为荣耀20s");
}
}
执行结果
解忧杂货铺开张了!
=====手工艺品买手买入手工艺品=====
兵马俑摆件
=====手工艺品买手买入手工艺品=====
海螺捕梦网
=====手工艺品买手买入手工艺品=====
古风沙漏
=====文具买手买入文具=====
直尺
=====文具买手买入文具=====
书立
=====文具买手买入文具=====
订书机
=====手机买手买入手机=====
华为荣耀20
=====手机买手买入手机=====
华为荣耀20s
Process finished with exit code 0