所有代码已经上传到GitHub
https://github.com/massfeng/Design-Patterns
文章目录
个人理解整理,欢迎批评指正
设计模式就是教你面向对象应该怎么玩,设计模式一般不会单独使用,多种设计模式组合使用。
所谓设计模式,就是遇到问题后的一种解决思路;将这些解决方法整合起来就形成了设计模式
设计模式讲究的是思想,有些设计模式实现上看可能类似,但是其表达的意思跟要解决的问题不一样
设计模式应遵循的七大原则
开闭原则(OCP)
开闭原则的含义是:当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
里氏替换原则(LSP)
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
依赖倒置原则(DIP)
依赖倒置原则的原始定义为:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想是:要面向接口编程,不要面向实现编程
单一职责原则(SRP)
对于一个类而言,有且只有一个引起它变化的原因
接口隔离原则(ISP)
要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
迪米特法则(Lod)又名最少知识原则(LKP)
只和熟人说话,不和陌生人说话。例如明星和经纪人和歌迷,明星只和熟悉经纪人讨论事情,而经纪人决定是否让明星和歌迷开见面会。
合成复用原则(CRP)
它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
创建型模式
用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
1.单例模式
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
单例模式有两种实现方式
1)懒汉式单例
特点:在类加载时没有产生单例,只有在调用getInstance时才产生单例
package singleton;
/**
* @Description TODO 懒汉式单例模式
* @Author xtf
* @Date 2019/8/5 11:35
*/
public class LazySington {
public static void main(String[] args) {
President instance1 = President.getInstance();
System.out.println(instance1);
President instance2 = President.getInstance();
System.out.println(instance2);
if(instance1 == instance2) {
System.out.println("我们是同一个人!");
} else {
System.out.println("我们不是同一个人!");
}
}
}
/**
* 美国总统类,因为世界上只能有一个,所以适合用单例模式
*/
class President{
/**
* 类初始化的时候单例为null,只有在getInstance的时候才初始化
* 使用volatile关键字保证线程安全
*/
private static volatile President instance = null;
/**
* 构造函数,防止外界调用,使用private
*/
private President(){
System.out.println("产生了一个总统!");
}
/**
* 如果是第一次创建,即instance为null,此时创建一个总统
* 如果不是第一次创建,直接返回instance
* 使用synchronized来保证线程安全
*/
public static synchronized President getInstance() {
if(instance == null) {
instance = new President();
}
return instance;
}
@Override
public String toString() {
return "我是美国总统特朗普";
}
}
运行结果:
产生了一个总统!
我是美国总统特朗普
我是美国总统特朗普
我们是同一个人!
2)饿汉式单例
特点:在类加载时就已经产生单例**
package singleton;
/**
* @Description TODO 饿汉式单例模式
* @Author xtf
* @Date 2019/8/5 11:51
*/
public class HungrySington {
public static void main(String[] args){
BaJie instance1 = BaJie.getInstance();
System.out.println(instance1);
BaJie instance2 = BaJie.getInstance();
System.out.println(instance2);
if(instance1 == instance2){
System.out.println("我们是同一个八戒哦!");
} else {
System.out.println("我们是不同的八戒哦!");
}
}
}
/**
* @Description TODO 八戒只有一个,所以可以用单例模式
* @Author xtf
* @Date 2019/8/5 11:54
*/
class BaJie {
/**
* 饿汉式单例模式,在类初始化的时候就已经创建完成
**/
public static BaJie instance = new BaJie();
/**
* 构造函数,防止外界调用,使用private
**/
private BaJie(){
System.out.println("创建了一个八戒");
}
/**
* @Description TODO 直接返回这个单例,因为在类初始化的时候已经构造完成
* @Author xtf
* @Date 2019/8/5 12:07
* @Param []
* @return singleton.BaJie
*/
public static BaJie getInstance() {
return instance;
}
@Override
public String toString() {
return "我是八戒";
}
}
运行结果:
创建了一个八戒
我是八戒
我是八戒
我们是同一个八戒哦!
单例模式的应用场景:
- 在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
- 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
3)有限多例模式
单例模式可拓展成有限多例模式,有限多例模式只能产生特定数量的单例,这里使用饿加载创建多例
例如在读取文件,或者在应用程序启动时,我们可以用有限多例模式产生多个单例,提高响应速度
package singleton;
import java.util.HashMap;
import java.util.Random;
/**
* @Description TODO 有限多例模式
* @Author xtf
* @Date 2019/8/5 16:07
*/
public class Multitcm {
public static void main(String[] args){
for(int i = 0; i < 6; i++) {
Droid droid = Droid.getInstance();
System.out.println(droid);
}
}
}
/**
* @Description TODO 机器人类,使用有限多例模式,这里创建两个
* @Author xtf
* @Date 2019/8/5 16:09
* @Param
* @return
*/
class Droid {
/**
* 使用一个hashMap来保存有限的多例,每次取出的时候,随机取一个
**/
private static HashMap<String, Droid> droids = new HashMap<String, Droid>();
/**
* 每个机器人都要有自己的名字
**/
private String name;
/**
* 使用一个String来存放两个机器人的名字
**/
private static final String[] names = {"阿尔法狗", "骂死他"};
// 直接构造出两个机器人
static {
// 第一个机器人是AlphaGo
Droid alphaGo = new Droid(names[0]);
droids.put(names[0], alphaGo);
// 第二个机器人是master
Droid master = new Droid(names[1]);
droids.put(names[1], master);
}
/**
* @Description TODO 构造函数私有,防止外部调用,在构造时必须填入名字
* @Author xtf
* @Date 2019/8/5 16:14
* @Param [name]
* @return
*/
private Droid(String name) {
this.name = name;
}
/**
* @Description TODO 随机返回一个机器人
* @Author xtf
* @Date 2019/8/5 16:21
* @Param []
* @return singleton.Droid
*/
public static Droid getInstance(){
Random random = new Random();
int index = random.nextInt(2);
return droids.get(names[index]);
}
@Override
public String toString() {
return "我是:" + this.name;
}
}
运行结果:
我是:阿尔法狗
我是:骂死他
我是:阿尔法狗
我是:骂死他
我是:阿尔法狗
我是:阿尔法狗
2.原型模式
1)原型模式
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。
Java的Object类提供了clone()方法,使得Java完成原型模式非常简单,只需要在类中实现Cloneable接口就可以使用clone方法。
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
奖状因为只是名字不一样,其他的内容都一样,所以很适合使用原型模式,下面是代码实现
package prototype;
/**
* @Description TODO 原型模式,使用奖状来模拟
* @Author xtf
* @Date 2019/8/5 16:39
*/
public class ProtoTypeCitation {
public static void main(String[] args) throws CloneNotSupportedException{
// 原型创建
Citation citation1 = new Citation("张三", "同学:在学校表现良好,被评为三好学生!", "江西农业大学");
System.out.println(citation1);
// 克隆出李四
Citation citation2 = (Citation)citation1.clone();
citation2.setName("李四");
System.out.println(citation2);
// 克隆出王五
Citation citation3 = (Citation)citation1.clone();
citation3.setName("王五");
System.out.println(citation3);
}
}
/**
* @Description TODO 奖状类,奖状除了名字以外,其他都一样,所以适合使用原型模式
* @Author xtf
* @Date 2019/8/5 16:40
*/
class Citation implements Cloneable {
private String name;
private String info;
private String college;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
Citation(String name, String info, String college) {
this.name = name;
this.info = info;
this.college = college;
System.out.println("奖状创建成功!");
}
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("奖状克隆成功");
return super.clone();
}
@Override
public String toString() {
return this.name + this.info + this.college;
}
}
运行结果:
奖状创建成功!
张三同学:在学校表现良好,被评为三好学生!江西农业大学
奖状克隆成功
李四同学:在学校表现良好,被评为三好学生!江西农业大学
奖状克隆成功
王五同学:在学校表现良好,被评为三好学生!江西农业大学
2)带原型管理器的原型模式
在原型比较多时,我们可以使用原型管理器来管理它们,每次从原型管理器中取出一个原型时,我们将里面的一个原型的拷贝返回
圆和正方形都是图形,都可以计算面积,但是计算方法不一样,所以我们创建一个shape接口,再创建圆和正方形的类去继承shape接口,并且用原型管理器管理圆和正方形的原型,每次可以从原型管理器中取出圆和正方形的一个原型的拷贝
下面是代码实现:
package prototype;
import com.sun.corba.se.impl.encoding.CDROutputStream_1_0;
import java.util.HashMap;
/**
* @Description TODO 带原型管理器的原型模式
* @Author xtf
* @Date 2019/8/5 17:06
*/
public class ProtoTypeShape {
public static void main(String[] args){
ProtoTypeManager prm = new ProtoTypeManager();
Circle c1 = (Circle)prm.getShape("Circle");
c1.setR(3);
c1.countArea();
Square s1 = (Square)prm.getShape("Square");
s1.setL(4);
s1.countArea();
}
}
/**
* @Description TODO 图形接口,有两个实现类,圆和正方形
* @Author xtf
* @Date 2019/8/5 17:07
*/
interface Shape extends Cloneable {
public Object clone();
public void countArea();
}
/**
* @Description TODO 圆类,实现了Shape接口
* @Author xtf
* @Date 2019/8/5 17:12
*/
class Circle implements Shape {
/**
* 半径
**/
private float r;
Circle(float r) {
this.r = r;
}
public void setR(float r) {
this.r = r;
}
public float getR() {
return r;
}
@Override
public Object clone() {
Circle c = null;
try {
c = (Circle)super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("拷贝圆形失败");
}
return c;
}
@Override
public void countArea() {
System.out.println("圆的面积是:" + 3.14 * r * r);
}
}
/**
* @Description TODO 正方形
* @Author xtf
* @Date 2019/8/5 17:23
*/
class Square implements Shape {
/**
* 正方形的边长
**/
private float l;
Square(float l) {
this.l = l;
}
public void setL(float l) {
this.l = l;
}
public float getL() {
return l;
}
@Override
public Object clone() {
Square s = null;
try {
s = (Square)super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("拷贝正方形失败");
}
return s;
}
@Override
public void countArea() {
System.out.println("正方形的面积是:" + l * l);
}
}
/**
* @Description TODO 原型模式管理器
* @Author xtf
* @Date 2019/8/5 17:26
*/
class ProtoTypeManager {
private HashMap<String, Shape> map = new HashMap<String, Shape>();
/**
* 在构建出原型模式管理器时,往里面加入圆形和正方形的原型
**/
ProtoTypeManager() {
map.put("Circle", new Circle(2));
map.put("Square", new Square(3));
}
public void addShape(String key, Shape shape) {
map.put(key, shape);
}
/**
* 在取出时,取出原型模式管理器中的克隆
**/
public Shape getShape(String key){
Shape shape = map.get(key);
return (Shape)shape.clone();
}
}
3.工厂方法模式
工厂模式:将同类型对象实例的创建封装成一个统一的对象的去创建;通过统一的工厂创建产品,不同类型的产品使用不同的工厂
工厂方法模式的主要优点有:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
其缺点是:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
使用场景:
- 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌。
1)简单工厂模式
当产品不多并且不会增加时,可以使用简单工厂模式,它不属于23种设计模式,因为它不符合开闭原则,当产品需要增加时,只能修改源代码。
使用较为简单,所以在这里不写源代码
2)工厂方法模式
主要对应角色如下
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
电视有很多种产品,例如TCL,海信。而每种电视对应着每种电视生产线,例如TCL生产线,海信生产线。所以我们定义了一个电视接口和一个电视生产线接口,TCL电视和海信电视分别实现电视接口,TCL电视生产线和海信电视生产线分别实现生产线接口,源码如下:
package factory;
/**
* @Description TODO 工厂模式,我们有很多的电视生产线,例如海信,TCL电视生产线
* 每个生产线生产不同的电视,例如海信电视,TCL电视,所以适合用工厂模式
* @Author xtf
* @Date 2019/8/6 10:26
*/
public class FactoryMethod {
public static void main(String[] args){
TV t;
AbstractTVFactory atf = new HisenseFactory();
t = atf.newTV();
t.watchTV();
}
}
/**
* @Description TODO 电视的接口,具有看电视的功能
* @Author xtf
* @Date 2019/8/6 10:30
*/
interface TV {
public void watchTV();
}
/**
* @Description TODO 海信电视
* @Author xtf
* @Date 2019/8/6 10:34
*/
class Hisense implements TV {
@Override
public void watchTV() {
System.out.println("看海信电视");
}
}
/**
* @Description TODO TCL电视
* @Author xtf
* @Date 2019/8/6 10:34
*/
class TCL implements TV {
@Override
public void watchTV() {
System.out.println("看TCL电视");
}
}
/**
* @Description TODO 电视工厂的接口,具有生产电视的功能
* @Author xtf
* @Date 2019/8/6 10:31
*/
interface AbstractTVFactory {
public TV newTV();
}
/**
* @Description TODO 海信电视工厂,可以生产海信电视
* @Author xtf
* @Date 2019/8/6 10:35
*/
class HisenseFactory implements AbstractTVFactory {
@Override
public TV newTV() {
System.out.println("生产了一个Hisense电视");
return new Hisense();
}
}
/**
* @Description TODO TCL电视工厂,可以生产TCL电视
* @Author xtf
* @Date 2019/8/6 10:36
* @Param
* @return
*/
class TCLFactory implements AbstractTVFactory {
@Override
public TV newTV() {
System.out.println("生产了一个TCL电视");
return new TCL();
}
}
4.抽象工厂模式
上一节的工厂方法模式,只能解决工厂只生产单一产品的情况。但是现在社会的工厂不可能只生产一种产品,例如农场可能会生产牛、羊、动物、植物等。所以此时引入了抽象工厂模式。
抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
其缺点是:
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
抽象工厂模式通常适用于以下场景:
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
- 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
- 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
具体实现:
我们有两个农场,一个是南昌农场,一个是樟树农场,南昌农场生产马和水果,樟树农场生产牛和蔬菜。所以此时我们需要一个动物接口,有两个实现类分别是马和牛;需要一个植物接口,有两个实现类分别是水果和蔬菜;需要一个农场接口,接口中包含生产动物和生产植物,有两个实现类分别是南昌农场和樟树农场。
如果我们要扩展农场,此时满足开闭原则,只需要新建一个具体的农场类实现农场接口即可;但是如果我们要扩展农场的功能,比如说农场不仅要生产动物和植物还需要生产禽类,此时我们只能修改源代码,不满足开闭原则,即具有一定的“开闭原则”倾斜性。
代码如下:
package factory;
/**
* @Description TODO 抽象工厂模式
* @Author xtf
* @Date 2019/8/6 11:43
*/
public class AbstractFactory {
public static void main(String[] args){
Farm f = new NanChangFarm();
Animal animal = f.newAnimal();
animal.eat();
Plant plant = f.newPlant();
plant.eat();
}
}
/**
* @Description TODO 动物接口,动物有吃的功能
* @Author xtf
* @Date 2019/8/6 11:47
*/
interface Animal {
public void eat();
}
/**
* @Description TODO 马的实现类
* @Author xtf
* @Date 2019/8/6 11:49
*/
class Horse implements Animal{
@Override
public void eat() {
System.out.println("吃马");
}
}
/**
* @Description TODO 牛的实现类
* @Author xtf
* @Date 2019/8/6 11:49
*/
class Cattle implements Animal {
@Override
public void eat() {
System.out.println("吃牛");
}
}
/**
* @Description TODO 植物的接口,有吃的功能
* @Author xtf
* @Date 2019/8/6 11:49
*/
interface Plant {
public void eat();
}
/**
* @Description TODO 水果类
* @Author xtf
* @Date 2019/8/6 11:51
*/
class Fruitage implements Plant {
@Override
public void eat() {
System.out.println("吃水果");
}
}
/**
* @Description TODO
* @Author xtf
* @Date 2019/8/6 11:51
*/
class Vegetables implements Plant{
@Override
public void eat() {
System.out.println("吃蔬菜");
}
}
/**
* @Description TODO 农场的工厂类接口
* @Author xtf
* @Date 2019/8/6 11:52
*/
interface Farm {
/**
* @Description TODO 可以生产动物
* @Author xtf
* @Date 2019/8/6 11:53
* @Param []
* @return Animal
*/
public Animal newAnimal();
/**
* @Description TODO 可以生产职务
* @Author xtf
* @Date 2019/8/6 11:53
* @Param []
* @return Plant
*/
public Plant newPlant();
}
/**
* @Description TODO 南昌农场,可以生产马和水果
* @Author xtf
* @Date 2019/8/6 11:55
*/
class NanChangFarm implements Farm {
@Override
public Animal newAnimal() {
return new Horse();
}
@Override
public Plant newPlant() {
return new Fruitage();
}
}
/**
* @Description TODO 樟树农场,可以生产牛和蔬菜
* @Author xtf
* @Date 2019/8/6 11:56
* @Param
* @return
*/
class ZhangShuFarm implements Farm {
@Override
public Animal newAnimal() {
return new Cattle();
}
@Override
public Plant newPlant() {
return new Vegetables();
}
}
5.建造者模式
软件开发有时候需要创建一些比较复杂的对象,例如一台电脑其中包括内存、显卡、硬盘、CPU等等,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
又或者是一封邮件,其中包括寄件人、收件人、内容、标题等
以上产品都是由多个部件组成,并且部件可以自由组合,此时我们就可以采用建造者模式创建对象。
建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
优点:
- 各个具体的建造者相互独立,有利于系统的扩展。
- 客户端不必知道产品内部组成的细节,便于控制细节风险。
缺点:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,该模式会增加很多的建造者类。
何时使用:
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
- 当我们需要特定的装配顺序,即产品先要需要什么部件后需要什么部件,步骤顺序不能乱时
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。可以在使用工厂模式时,具体工厂实现采用建造者模式。
具体例子:
肯德基中有很多单品,例如汉堡,可乐等,单品可以组成套餐,此时可以用建造者模式。
汉堡用纸盒包装,可乐用瓶子包装。汉堡有素汉堡和肉汉堡,可乐有百事可乐和可口可乐。
使用建造者模式,建造套餐类。
代码实现:
package builder;
import java.util.ArrayList;
import java.util.List;
/**
* @Description TODO 建造者模式示例,肯德基中有很多单品,例如汉堡,可乐等,单品可以组成套餐,此时可以用建造者模式
* 汉堡用纸盒包装,可乐用瓶子包装。汉堡有素汉堡和肉汉堡,可乐有百事可乐和可口可乐。
* @Author xtf
* @Date 2019/8/6 15:41
*/
public class BuilderPattern {
public static void main(String[] args){
MealBuilder mb = new MealBuilder();
Meal meal1 = mb.Meal1();
System.out.println(meal1.getCost());
meal1.showItems();
Meal meal2 = mb.Meal2();
System.out.println(meal2.getCost());
meal2.showItems();
}
}
/**
* @Description TODO 使用套餐建造者建造套餐对象
* @Author xtf
* @Date 2019/8/6 16:13
*/
class MealBuilder {
/**
* @Description TODO 套餐一,其中包括蔬菜汉堡和可口可乐
* @Author xtf
* @Date 2019/8/6 16:15
* @Param []
* @return builder.Meal
*/
public Meal Meal1() {
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
/**
* @Description TODO 套餐2,其中包括鸡肉汉堡和百事可乐
* @Author xtf
* @Date 2019/8/6 16:15
* @Param []
* @return builder.Meal
*/
public Meal Meal2() {
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
/**
* @Description TODO 单品类,每个单品都有名字,包装和价格
* @Author xtf
* @Date 2019/8/6 15:43
*/
interface Item {
public String name();
public Packing packing();
public float price();
}
/**
* @Description TODO 包装类
* @Author xtf
* @Date 2019/8/6 15:46
*/
interface Packing {
public String pack();
}
/**
* @Description TODO 纸盒类
* @Author xtf
* @Date 2019/8/6 15:46
*/
class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
/**
* @Description TODO 瓶子类
* @Author xtf
* @Date 2019/8/6 15:47
*/
class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
/**
* @Description TODO 实现了特定功能的Item实现类,汉堡类,汉堡用纸盒装
* @Author xtf
* @Date 2019/8/6 15:49
*/
abstract class Burger implements Item {
@Override
public Packing packing() {
return new Wrapper();
}
}
/**
* @Description TODO 实现了特定功能的Item实现类,可乐类,汉堡用瓶子装
* @Author xtf
* @Date 2019/8/6 15:50
*/
abstract class Cola implements Item {
@Override
public Packing packing() {
return new Bottle();
}
}
/**
* @Description TODO 拓展了Burger类的实体类,素食汉堡类
* @Author xtf
* @Date 2019/8/6 15:51
*/
class VegBurger extends Burger {
@Override
public String name() {
return "Veg Burger";
}
@Override
public float price() {
return 25.0f;
}
}
/**
* @Description TODO 拓展了Burger类的实体类,鸡肉汉堡类
* @Author xtf
* @Date 2019/8/6 15:52
*/
class ChickenBurger extends Burger {
@Override
public String name() {
return "Chicken Burger";
}
@Override
public float price() {
return 45.0f;
}
}
/**
* @Description TODO 可乐类的实体类,可口可乐
* @Author xtf
* @Date 2019/8/6 15:54
*/
class Coke extends Cola {
@Override
public String name() {
return "Coke Cola";
}
@Override
public float price() {
return 10.0f;
}
}
/**
* @Description TODO 可乐类的实体类,百事可乐
* @Author xtf
* @Date 2019/8/6 15:54
*/
class Pepsi extends Cola {
@Override
public String name() {
return "Pepsi Cola";
}
@Override
public float price() {
return 5.0f;
}
}
/**
* @Description TODO 用Meal类表示套餐
* @Author xtf
* @Date 2019/8/6 15:59
*/
class Meal {
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item) {
items.add(item);
}
public float getCost() {
float f = 0.0f;
for (Item item:items) {
f += item.price();
}
return f;
}
public void showItems() {
for(Item item:items) {
System.out.println("name:" + item.name() + ",pack:" + item.packing().pack() + ",price:" + item.price());
}
}
}
结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
1.代理模式
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
其主要缺点是:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
代理模式在Java中实现主要有三种:
- 静态代理
- 动态代理(JDK代理)
- Cglib
具体例子:
有个歌手singer,有一个方法sing(),可以唱一首歌,但是我们想在唱歌的基础上,加入和观众问好和告别观众的功能,怎么办呢?
这个时候我们就要采用代理模式。代理模式简单来说就是在不改变源码的情况下,实现对目标对象的功能扩展。
1)静态代理
代码实现:
package proxy;
/**
* @Description TODO 静态代理
* 有个歌手singer,有一个方法sing(),可以唱一首歌,但是我们想在唱歌的基础上,加入和观众问好和告别观众的功能,怎么办呢?
* 这个时候我们就要采用代理模式。代理模式简单来说就是在不改变源码的情况下,实现对目标对象的功能扩展
* @Author xtf
* @Date 2019/8/6 16:55
*/
public class StaticProxy {
public static void main(String[] args){
SingerProxy sp = new SingerProxy(new Singer());
sp.sing();
}
}
/**
* @Description TODO 唱歌的接口,包含了一个唱歌方法
* @Author xtf
* @Date 2019/8/6 16:58
*/
interface ISinger {
public void sing();
}
/**
* @Description TODO 歌手对象,他的sing()方法只能唱一首歌
* @Author xtf
* @Date 2019/8/6 16:59
*/
class Singer implements ISinger {
@Override
public void sing() {
System.out.println("唱了一首歌");
}
}
/**
* @Description TODO 静态代理对象,在之前的歌手唱歌的基础上加入了向观众问好和向观众告别的功能
* @Author xtf
* @Date 2019/8/6 17:02
*/
class SingerProxy implements ISinger {
private ISinger target;
SingerProxy(ISinger target) {
this.target = target;
}
@Override
public void sing() {
System.out.println("向观众问好");
target.sing();
System.out.println("向观众告别");
}
}
从这里我们可以看出,静态代理做的事情无非就是,创建一个类SingerProxy,继承了ISinger接口并实现其中方法,只不过这种实现包含了Singer的方法,从而看上去扩展了其功能,假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含是关键。
缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
2)动态代理(JDK代理)
package proxy;
import java.lang.reflect.Proxy;
/**
* @Description TODO 动态代理,ISinger和Singer请看同包下的StaticProxy.java
* @Author xtf
* @Date 2019/8/6 17:04
*/
public class DynamicProxy {
public static void main(String[] args){
Singer target = new Singer();
ISinger proxy = (ISinger)Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(o, method, objects) -> {
System.out.println("向观众问好");
Object returnVal = method.invoke(target, objects);
System.out.println("向观众告别");
return returnVal;
}
);
proxy.sing();
}
}
从代码中我们可以看出,我们这里使用了Java自带的Proxy类,所以动态代理又叫JDK代理,这种代理方法非常简单,因为Java底层已经帮我们封装实现好了newProxyInstance这个方法的实现细节,不需要自己造轮子,并且写法格式也十分固定,只需要改拉姆达表达式中的代码就可以。
缺点:我们可以看出静态代理和动态代理都需要目标类去实现一个或多个接口,如果我们的类没有实现接口怎么办呢?这个时候就可以使用Cglib代理,Cglib是一个优秀的开源项目,Spring就使用了它,所以我们直接导入Spring-core.jar的jar包也可以使用Cglib。
3)Cglib
前提条件:
- 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-5.1.8.jar
- 目标类不能为final
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
package proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @Description TODO 使用Cglib代理,因为Spring已经集成了Cglib所以直接用spring-core的jar包
* 使用静态代理或者动态代理时,目标对象必须实现一个或多个接口,而当对象不实现接口时,可以使用Cglib
* @Author xtf
* @Date 2019/8/6 17:24
*/
public class Cglib {
public static void main(String[] args){
singer singer = new singer();
singer proxy = (singer)new ProxyFactory(singer).getProxyInstance();
proxy.sing();
}
}
/**
* @Description TODO 目标对象,可以不实现接口
* @Author xtf
* @Date 2019/8/6 17:27
*/
class singer {
public void sing() {
System.out.println("唱一首歌");
}
}
class ProxyFactory implements MethodInterceptor {
// 维护目标对象
private Object target;
// 传入目标对象
public ProxyFactory(Object target) {
this.target = target;
}
// 给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("向观众问好");
// 执行目标对象的方法
Object returnVal = method.invoke(target, objects);
System.out.println("向观众告别");
return returnVal;
}
}
三种代理模式各有优缺点,主要看目标是否实现了接口。以Spring使用的代理模式举例,在Spring的AOP编程中:
- 如果加入容器的目标对象有实现接口,用JDK代理
- 如果目标对象没有实现接口,用Cglib代理
2.适配器模式
有两个接口A和B,我们只能访问接口A,但是我们需要的方法接口A中没有,而我们在接口B中发现了该方法,此时我们就需要一个适配器来连接A和B,我们在适配器中访问A时,可以用到B中的方法。
例如,我们只能访问ps2接口,但是我们需要一个usb接口的方法,这是我们就可以建立一个适配器来适配ps2和usb
代码实现:
package adapter;
/**
* @Description TODO
* 我们要访问的接口A中不存在我们需要的方法,但是在接口B中我们发现了该方法
* 所以这时我们需要一个适配器p来帮助我们在访问A时同时访问到B中的我们需要的方法
* 以USB口和PS2口为例,我们要访问的接口是PS2口,但是不存在我们需要的方法(在USB接口中)
* 我们又不能直接修改PS2接口的内容,所以我们创建一个适配器来帮助我们适配USB口
* 适配器分为两种,一种是类适配器,一种是对象适配器
* @Author xtf
* @Date 2019/8/8 15:04
*/
public class UsbDemo {
public static void main(String[] args){
Adpter1 adpter1 = new Adpter1();
adpter1.ps2();
Adpter2 adpter2 = new Adpter2(new Usber());
adpter2.ps2();
}
}
/**
* @Description TODO ps2的接口(我们直接访问的接口)
* @Author xtf
* @Date 2019/8/8 15:07
*/
interface Ps2 {
public void ps2();
}
/**
* @Description TODO usb的接口(我们需要该接口中的方法)
* @Author xtf
* @Date 2019/8/8 15:07
*/
interface Usb {
public void usb();
}
/**
* @Description TODO usb口的实现类
* @Author xtf
* @Date 2019/8/8 15:08
*/
class Usber implements Usb {
@Override
public void usb() {
System.out.println("我是usb口中的方法");
}
}
/**
* @Description TODO 类适配器
* @Author xtf
* @Date 2019/8/8 15:11
*/
class Adpter1 extends Usber implements Ps2 {
@Override
public void ps2() {
super.usb();
}
}
/**
* @Description TODO 对象适配器
* @Author xtf
* @Date 2019/8/8 15:12
*/
class Adpter2 implements Ps2 {
private Usb usb;
public Adpter2(Usb usb) {
this.usb = usb;
}
@Override
public void ps2() {
usb.usb();
}
}
从上面的源码,适配器模式分为两种,一种是类适配器,一种是对象适配器。
类适配器需要继承usber类和实现ps2接口
而对象适配器只需要实现ps2接口,但是在对象适配器的构造方法中,我们需要传入实现usb接口实现类的参数
新能源汽车的发动机有电能发动机(Electric Motor)和光能发动机(Optical Motor)等,各种发动机的驱动方法不同,例如,电能发动机的驱动方法 electricDrive() 是用电能驱动,而光能发动机的驱动方法 opticalDrive() 是用光能驱动。
客户端希望用统一的发动机驱动方法 drive() 访问这两种发动机,所以必须定义一个统一的目标接口 Motor,然后再定义电能适配器(Electric Adapter)和光能适配器(Optical Adapter)去适配这两种发动机。
代码实现:
package adapter;
/**
* @Description TODO
* 新能源汽车的发动机有电能发动机(Electric Motor)和光能发动机(Optical Motor)等,
* 各种发动机的驱动方法不同,例如,电能发动机的驱动方法 electricDrive() 是用电能驱动,
* 而光能发动机的驱动方法 opticalDrive() 是用光能驱动
* 客户端希望用统一的发动机驱动方法 drive() 访问这两种发动机,所以必须定义一个统一的目标接口 Motor,
* 然后再定义电能适配器(Electric Adapter)和光能适配器(Optical Adapter)去适配这两种发动机。
* 适配器有两种模式,一种是类适配器,一种是对象适配器
* 类适配器既继承又实现接口
* 对象适配器只实现接口,而具体对象在构造函数时注入
* @Author xtf
* @Date 2019/8/8 14:48
*/
public class MotorAdapter {
public static void main(String[] args){
Moter motor = new OpticalDrive2();
motor.drive();
}
}
/**
* @Description TODO 发动机接口
* @Author xtf
* @Date 2019/8/8 14:50
*/
interface Moter {
public void drive();
}
/**
* @Description TODO 电能发动机实现类
* @Author xtf
* @Date 2019/8/8 14:51
*/
class ElectricMoter {
public void electricDrive() {
System.out.println("电动发动机");
}
}
/**
* @Description TODO 光能发动机实现类
* @Author xtf
* @Date 2019/8/8 14:52
*/
class OpticalMotor {
public void opticalDrive() {
System.out.println("光能发动机");
}
}
/**
* @Description TODO 电能驱动 对象适配器模式
* @Author xtf
* @Date 2019/8/8 14:54
*/
class ElectricDrive1 implements Moter {
private ElectricMoter em;
public ElectricDrive1() {
em = new ElectricMoter();
}
@Override
public void drive() {
em.electricDrive();
}
}
/**
* @Description TODO 电能驱动 类适配器模式
* @Author xtf
* @Date 2019/8/8 14:55
*/
class ElectricDrive2 extends ElectricMoter implements Moter {
@Override
public void drive() {
super.electricDrive();
}
}
/**
* @Description TODO 光能启动 对象适配器模式
* @Author xtf
* @Date 2019/8/8 14:56
*/
class OpticalDrive1 implements Moter {
private OpticalMotor om;
public OpticalDrive1() {
om = new OpticalMotor();
}
@Override
public void drive() {
om.opticalDrive();
}
}
/**
* @Description TODO 光能驱动 类适配器模式
* @Author xtf
* @Date 2019/8/8 14:57
*/
class OpticalDrive2 extends OpticalMotor implements Moter {
@Override
public void drive() {
super.opticalDrive();
}
}
3.桥接模式
在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
包包有很多种,有双肩包,单肩包,也有很多种颜色,红色、蓝色等。这个时候如果单纯用继承,非常麻烦,有很多种组合方式,所以这时候我们可以用桥接模式
颜色类(Color)是一个维度,定义为实现化角色,它有两个具体实现化角色:黄色和红色,通过 getColor() 方法可以选择颜色;包类(Bag)是另一个维度,定义为抽象化角色,它有两个扩展抽象化角色:单肩包和双肩包,它包含了颜色类对象,通过 getName() 方法可以选择相关颜色的挎包和钱包。
代码实现:
package bridge;
/**
* @Description TODO 桥接模式
* 包包有很多种,有双肩包,单肩包,也有很多种颜色,红色、蓝色等
* 这个时候如果单纯用继承,非常麻烦,有很多种组合方式
* 所以这时候我们可以用桥接模式
* @Author xtf
* @Date 2019/8/8 15:39
*/
public class BagBridge {
public static void main(String[] args){
Color color;
Bag bag;
color = new Yellow();
bag = new BackPack();
bag.setColor(color);
System.out.println(bag.getName());
}
}
/**
* @Description TODO 包的抽象类
* @Author xtf
* @Date 2019/8/8 15:44
*/
abstract class Bag {
protected Color color;
public void setColor(Color color) {
this.color = color;
}
public abstract String getName();
}
/**
* @Description TODO 颜色的接口
* @Author xtf
* @Date 2019/8/8 15:42
*/
interface Color {
public String getColor();
}
/**
* @Description TODO 黄色
* @Author xtf
* @Date 2019/8/8 15:45
*/
class Yellow implements Color {
@Override
public String getColor() {
return "yellow";
}
}
/**
* @Description TODO 红色
* @Author xtf
* @Date 2019/8/8 15:48
*/
class Red implements Color {
@Override
public String getColor() {
return "red";
}
}
/**
* @Description TODO 双肩包
* @Author xtf
* @Date 2019/8/8 15:48
*/
class BackPack extends Bag {
@Override
public String getName() {
return color.getColor() + "BackPack";
}
}
/**
* @Description TODO 单肩包
* @Author xtf
* @Date 2019/8/8 15:48
*/
class SingPack extends Bag {
@Override
public String getName() {
return color.getColor() + "SingPack";
}
}
4.装饰模式
装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰(Decorator)模式的主要优点有:
- 采用装饰模式扩展对象的功能比采用继承方式更加灵活。
- 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
其主要缺点是:装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。下面来分析其基本结构和实现方法。
装饰模式主要包含以下角色。
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰模式最出名的例子就是JDK中的IO流了,IO流中广泛的使用了装饰模式。
装饰模式和代理模式的相同和区别:
相同:都是增加被代理或被装饰的区别
不同:
代理模式就像VPN,你不能直接访问Facebook,而是通过了一个你可以访问它且它可以访问facebook的服务器代理你去访问,并把数据给你。
装饰模式就像你买了一个手机套,但是这个手机套不能完全满足你的需要,所以你在手机套上刻上了自己的名字,而这个手机套从本质上来说,还是你之前的手机套
具体案例:
人有吃饭喝水的功能,但是如果我们不止想吃饭喝水,还想吃牛排和喝红酒,怎么办呢?人还是人,但是我们要添加功能,这时我们使用装饰模式。
代码实现:
package decorator;
/**
* @Description TODO
* 人有吃饭的功能,但是我们需要拓展人吃牛排和吃面条的功能的时候怎么办呢,这个时候我们可以使用装饰者模式
* @Author xtf
* @Date 2019/8/18 10:18
*/
public class PeopleDecorator {
public static void main(String[] args){
ExpensiveFood ef1 = new ExpensiveFood(new NormalPerson());
ef1.eat();
ExpensiveFood ef2 = new ExpensiveFood(new NormalPerson2());
ef2.eat();
}
}
/**
* @Description TODO 人定义为抽象类,有一个吃东西的方法
* @Author xtf
* @Date 2019/8/18 10:22
*/
interface Person {
public void eat();
}
/**
* @Description TODO 普通人可以吃饭
* @Author xtf
* @Date 2019/8/18 10:23
*/
class NormalPerson implements Person {
@Override
public void eat() {
System.out.println("吃饭");
}
}
class NormalPerson2 implements Person {
@Override
public void eat() {
System.out.println("喝水");
}
}
/**
* @Description TODO 这里定义一个装饰者的抽象类,保持一个对Person的引用,可以方便调用具体的被装饰者的方法,方便对其进行扩展
* @Author xtf
* @Date 2019/8/18 10:24
*/
class PeopleFood extends NormalPerson {
private Person person;
public PeopleFood(Person person) {
this.person = person;
}
@Override
public void eat() {
person.eat();
}
}
/**
* @Description TODO 具体装饰类
* @Author xtf
* @Date 2019/8/18 10:51
*/
class ExpensiveFood extends PeopleFood {
public ExpensiveFood(Person person) {
super(person);
}
@Override
public void eat() {
super.eat();
eatSteak();
drinkRedWine();
}
public void eatSteak() {
System.out.println("吃牛排");
}
public void drinkRedWine() {
System.out.println("喝拉菲");
}
}
/**
* @Description TODO 具体装饰类
* @Author xtf
* @Date 2019/8/18 10:53
*/
class CheapFood extends PeopleFood {
public CheapFood(Person person) {
super(person);
}
@Override
public void eat() {
super.eat();
eatNoodles();
}
public void eatNoodles() {
System.out.println("吃面条");
}
}
运行结果:
吃饭
吃牛排
喝拉菲
喝水
吃牛排
喝拉菲
5.外观模式
外观(Facade)模式的定义:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
外观(Facade)模式的主要缺点如下。
- 不能很好地限制客户使用子系统类。
- 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观模式比较简单,同时也是非常常见的一种设计模式。
我们使用外观模式的情况有很多,比如经典的MVC三层架构,可以考虑在数据访问层和业务逻辑层、业务逻辑层和表示层的层与层之间简历外观Facade,降低耦合。对于复杂难以维护的老系统,直接去修改或扩展都可能产生很多问题,可以分两个小组,一个开发Facade与老系统的交互,另一个只要了解Facade接口,直接开发新系统调用这些接口即可。
具体案例:
有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
代码实现:
package facade;
/**
* @Description 基金类,基金经理人通过该类作为中间交互者,可以接受投资者的资金,统一对股票、国债、房地产进行购买和赎回操作。
* 有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,
* 刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,
* 其实基金就是个好帮手,支付宝里就有许多的基金,
* 它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,
* 而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
*
* @Author xtf
* @Date 2019/8/18 11:22
*/
public class FundFacade {
public static void main(String[] args){
Fund fund = new Fund();
fund.buy();
fund.sell();
}
}
class Fund {
private Stock1 stock1;
private Stock2 stock2;
private Stock3 stock3;
public Fund() {
stock1 = new Stock1();
stock2 = new Stock2();
stock3 = new Stock3();
}
public void buy() {
stock1.buy();
stock2.buy();
stock3.buy();
}
public void sell() {
stock1.sell();
stock2.sell();
stock3.sell();
}
}
/**
* @Description TODO 股票1
* @Author xtf
* @Date 2019/8/18 11:24
*/
class Stock1 {
public void buy() {
System.out.println("股票1买入");
}
public void sell() {
System.out.println("股票1赎回");
}
}
/**
* @Description TODO 股票2
* @Author xtf
* @Date 2019/8/18 11:24
*/
class Stock2 {
public void buy() {
System.out.println("股票2买入");
}
public void sell() {
System.out.println("股票2赎回");
}
}
/**
* @Description TODO 股票3
* @Author xtf
* @Date 2019/8/18 11:24
*/
class Stock3 {
public void buy() {
System.out.println("股票3买入");
}
public void sell() {
System.out.println("股票3赎回");
}
}
运行结果:
股票1买入
股票2买入
股票3买入
股票1赎回
股票2赎回
股票3赎回
6.享元模式
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式最出名的运用就是各种池,例如Java的字符串常量池,例如我们new了一个新字符串"admin",Java首先会到Java常量池中寻找,如果找到了,直接返回给你常量池中该字符串的引用,如果没找到,则在常量池中创建一个"admin",并返回该引用。
享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
其主要缺点是:
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
- 读取享元模式的外部状态会使得运行时间稍微变长。
具体例子:
我们现在有一个圆类,圆有不同的颜色和坐标,当我们创建新圆时,我们希望如果有同颜色的圆,就不创建,而直接返回原颜色的圆的引用。这个时候我们就可以使用享元模式。
代码实现:
package flyweight;
import java.util.HashMap;
/**
* @Description TODO
* 享元模式
* 享元模式最出名的应用就是池应用,例如Java的String常量池
* 我们将创建一个 Shape 接口和实现了 Shape 接口的实体类 Circle。下一步是定义工厂类 ShapeFactory。
* ShapeFactory 有一个 Circle 的 HashMap,其中键名为 Circle 对象的颜色。
* 无论何时接收到请求,都会创建一个特定颜色的圆。
* ShapeFactory 检查它的 HashMap 中的 circle 对象,
* 如果找到 Circle 对象,则返回该对象,
* 否则将创建一个存储在 hashmap 中以备后续使用的新对象,并把该对象返回到客户端。
* @Author xtf
* @Date 2019/8/18 17:32
*/
public class ShapeFlyweight {
private static final String colors[] =
{ "Red", "Green", "Blue", "White", "Black" };
public static void main(String[] args){
for(int i=0; i < 20; ++i) {
Circle circle =
(Circle)ShapeFactory.getShape(getRandomColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int)(Math.random()*colors.length)];
}
private static int getRandomX() {
return (int)(Math.random()*100 );
}
private static int getRandomY() {
return (int)(Math.random()*100);
}
}
/**
* @Description TODO 定义图形的接口
* @Author xtf
* @Date 2019/8/18 17:41
*/
interface Shape {
public void draw();
}
/**
* @Description TODO 圆类,图形的实体类
* @Author xtf
* @Date 2019/8/18 17:42
*/
class Circle implements Shape {
private int x;
private int y;
private int radius;
private String color;
@Override
public void draw() {
System.out.println("Circle{" +
"x=" + x +
", y=" + y +
", redius=" + radius +
", color='" + color + '\'' +
'}');
}
Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
}
/**
* @Description TODO 工厂类,生产基于颜色的对象
* @Author xtf
* @Date 2019/8/18 17:43
*/
class ShapeFactory {
// 工厂维护了一个hashmap,能够通过颜色获取对象
public static final HashMap<String, Shape> circleMap = new HashMap<String, Shape>();
/**
* @Description TODO 通过颜色从hashmap中拿对象,如果该颜色已经存在(或者说已经被拿过),则返回hashmap中的对象
* 如果该颜色不存在,则创建并返回一个该颜色的圆,并把这个圆放到hashmap中去
* @Author xtf
* @Date 2019/8/18 17:48
* @Param [color]
* @return flyweight.Shape
*/
public static Shape getShape(String color) {
Circle circle = (Circle)circleMap.get(color);
if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("创建了一个新圆:" + color);
}
return circle;
}
}
运行结果:
创建了一个新圆:White
Circle{x=53, y=70, redius=100, color='White'}
创建了一个新圆:Black
Circle{x=21, y=69, redius=100, color='Black'}
Circle{x=10, y=7, redius=100, color='Black'}
创建了一个新圆:Blue
Circle{x=97, y=18, redius=100, color='Blue'}
Circle{x=61, y=56, redius=100, color='Black'}
创建了一个新圆:Red
Circle{x=90, y=13, redius=100, color='Red'}
创建了一个新圆:Green
Circle{x=86, y=8, redius=100, color='Green'}
Circle{x=61, y=2, redius=100, color='Green'}
Circle{x=31, y=35, redius=100, color='Black'}
Circle{x=54, y=78, redius=100, color='Red'}
Circle{x=9, y=91, redius=100, color='White'}
Circle{x=91, y=29, redius=100, color='White'}
Circle{x=86, y=71, redius=100, color='White'}
Circle{x=22, y=75, redius=100, color='Blue'}
Circle{x=54, y=68, redius=100, color='Red'}
Circle{x=99, y=73, redius=100, color='Green'}
Circle{x=2, y=98, redius=100, color='Red'}
Circle{x=28, y=63, redius=100, color='Blue'}
Circle{x=67, y=16, redius=100, color='Red'}
Circle{x=85, y=61, redius=100, color='White'}
7.组合模式
组合(Composite)模式的定义:有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
组合模式的主要优点有:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
其主要缺点是:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
从定义中可以看出,组合模式用来表示部分与整体的层次结构(类似于树结构),而且也可以使用户对单个对象(叶子节点)以及组合对象(非叶子节点)的使用具有一致性,一致性的意思就是说,这些对象都拥有相同的接口。
很多书中包括文章都喜欢使用公司、子公司以及部门的例子,这其实就是一个典型的树结构。其实生活当中的树结构还有很多,比如书的目录、文件系统、网站的菜单等等,有很多很多。
我们随便挑一个树结构的例子,比如文件系统,我们来分析一下,在文件系统中,如果使用组合模式,各个部分的类都应该是什么样子的。
首先,文件系统中,叶子节点是文件,非叶子节点是文件夹,所以Leaf和Composite实现类就是文件和文件夹。对于Component接口,其实也很简单,就是提取文件和文件夹的共性就可以了。
很显然,二者的共性有很多,比如都可以进行复制、剪切、删除、重命名等操作。但是不同的是,对于文件和文件夹的这些操作是有细微的区别的,最明显的就是删除操作,如果是文件,那么我们只需要删除当前文件即可,而如果是文件夹,则需要删除文件夹下的所有文件以及文件夹,然后再删除该文件夹。
那么定义当中的一致性就体现在,我们的客户端不需要知道当前操作的是文件还是文件夹,它只知道它要进行删除操作,而我们去针对文件类别的不同去进行相应的处理。
下面我们来模拟一下组合模式,采用文件系统
具体案例:
我们使用文件系统来模拟组合模式,我们都知道,文件系统是由文件和文件夹组成,首先定义一个公共的接口IFile,其中包括获取名字,删除,创建文件等等。然后分别实现接口,最后测试
代码实现:
package composite;
import java.util.ArrayList;
import java.util.List;
/**
* @Description TODO 组合模式
* 采用文件系统模拟组合模式
* @Author xtf
* @Date 2019/8/19 10:58
*/
public class FileSystem {
public static void main(String[] args){
IFile root = new Folder("我的电脑");
root.createNewFile("C盘");
root.createNewFile("D盘");
root.createNewFile("E盘");
IFile D = root.getIFile(1);
D.createNewFile("project");
D.createNewFile("电影");
IFile project = D.getIFile(0);
project.createNewFile("test1.java");
project.createNewFile("test2.java");
project.createNewFile("test3.java");
IFile movie = D.getIFile(1);
movie.createNewFile("致青春.avi");
movie.createNewFile("速度与激情6.avi");
/* 以上为当前文件系统的情况,下面我们尝试删除文件和文件夹 */
display(null, root);
System.out.println();
project.delete();
movie.getIFile(0).delete();
System.out.println();
display(null, root);
}
//打印文件系统
public static void display(String prefix,IFile iFile){
if (prefix == null) {
prefix = "";
}
System.out.println(prefix + iFile.getName());
if(iFile instanceof Folder){
for (int i = 0; ; i++) {
try {
if (iFile.getIFile(i) != null) {
display(prefix + "--", iFile.getIFile(i));
}
} catch (Exception e) {
break;
}
}
}
}
}
/**
* @Description TODO
* 文件和文件夹所要实现的接口
* @Author xtf
* @Date 2019/8/19 11:09
*/
interface IFile {
/**
* 删除整个文件
**/
void delete();
/**
* 获取文件名
**/
String getName();
// 下面的三个方法是对于文件夹来说的,如果是文件则不可以使用这三个方法,将会抛出异常
/**
* 创建新文件
**/
void createNewFile(String name);
/**
* 删除文件夹下的某个文件
**/
void deleteFile(String name);
/**
* 获取文件夹下的某个文件
**/
IFile getIFile(int index);
}
/**
* @Description TODO 文件夹
* @Author xtf
* @Date 2019/8/19 11:13
*/
class Folder implements IFile {
/**
* 文件夹名
**/
private String name;
/**
* 父文件夹的引用
**/
private IFile folder;
/**
* 文件夹下的文件(夹)列表
**/
private List<IFile> files;
public Folder(String name) {
this(name, null);
}
public Folder(String name, IFile folder) {
super();
this.name = name;
this.folder = folder;
files = new ArrayList<IFile>();
}
@Override
/**
* @Description TODO 先删除下面的文件夹然后删除自己
* @Author xtf
* @Date 2019/8/19 11:41
* @Param []
* @return void
*/
public void delete() {
List<IFile> copy = new ArrayList<>(this.files);
System.out.println("-----------删除子文件-----------");
for (IFile file :
copy) {
file.delete();
}
System.out.println("-----------删除子文件结束-----------");
if(folder != null) {
folder.deleteFile(this.name);
}
System.out.println("---删除 [" + name +"] ---");
}
@Override
public String getName() {
return name;
}
@Override
public void createNewFile(String name) {
// 如果添加的文件名中有".",说明是文件,添加一个文件
// 否则是文件夹,添加一个文件夹
if(name.contains(".")) {
files.add(new File(name, this));
} else {
files.add(new Folder(name, this));
}
}
@Override
public void deleteFile(String name) {
// 如果找到同名文件,则删除。
for (IFile file:
files) {
if(file.getName().equals(name)) {
files.remove(file);
}
break;
}
}
@Override
public IFile getIFile(int index) {
return files.get(index);
}
}
/**
* @Description TODO 文件类
* @Author xtf
* @Date 2019/8/19 11:28
*/
class File implements IFile {
/**
* 文件名
**/
private String name;
/**
* 父文件夹
**/
private IFile folder;
public File(String name, IFile folder) {
super();
this.name = name;
this.folder = folder;
}
@Override
public void delete() {
folder.deleteFile(name);
System.out.println("---删除 [" + name + "] ---");
}
@Override
public String getName() {
return name;
}
@Override
public void createNewFile(String name) {
throw new UnsupportedOperationException();
}
@Override
public void deleteFile(String name) {
throw new UnsupportedOperationException();
}
@Override
public IFile getIFile(int index) {
throw new UnsupportedOperationException();
}
}
运行结果:
我的电脑
--C盘
--D盘
----project
------test1.java
------test2.java
------test3.java
----电影
------致青春.avi
------速度与激情6.avi
--E盘
-----------删除子文件-----------
---删除 [test1.java] ---
---删除 [test2.java] ---
---删除 [test3.java] ---
-----------删除子文件结束-----------
---删除 [project] ---
---删除 [致青春.avi] ---
我的电脑
--C盘
--D盘
----电影
------速度与激情6.avi
--E盘
行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是 GoF 设计模式中最为庞大的一类,它包含以下 11 种模式。
- 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
1.模板方法模式
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。
以下介绍的模板方法模式将解决以上类似的问题。
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下。
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
具体案例:
我们现在需要实现调试饮料的功能
冲泡咖啡:
1.把水煮沸
2.用沸水冲泡咖啡
3.把咖啡倒入杯子
4.加糖和牛奶
冲泡茶:
1.把水煮沸
2.用沸水冲泡茶叶
3.把茶倒入被子
4.加柠檬
我们发现,1和3是很像的,4的步骤(加糖和牛奶/加柠檬)本质上都是加调料。
所以在这里,如果我们每个类都一步一步重复写一遍,我们会发现有很多重复代码
所以我们可以使用模板方法模式,找出共性进行封装,提取相同的代码,使系统达到维护和扩展成本很低的状态。
代码实现:
package templateMethod;
/**
* @Description TODO 模板方法模式
* 实现调试饮料的功能
*
* 冲泡咖啡:
* 1.把水煮沸
* 2.用沸水冲泡咖啡
* 3.把咖啡倒入杯子
* 4.加糖和牛奶
*
* 冲泡茶:
* 1.把水煮沸
* 2.用沸水冲泡茶叶
* 3.把茶倒入被子
* 4.加柠檬
*
* 我们发现,1和3是很像的,4的步骤(加糖和牛奶/加柠檬)本质上都是加调料。
* 所以在这里,如果我们每个类都一步一步重复写一遍,我们会发现有很多重复代码
* 所以我们可以使用模板方法模式,找出共性进行封装,提取相同的代码,使系统达到维护和扩展成本很低的状态。
* @Author xtf
* @Date 2019/8/19 14:56
*/
public class Drink {
public static void main(String[] args){
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
}
}
/**
* @Description TODO 模板方法
* @Author xtf
* @Date 2019/8/19 15:03
*/
abstract class CaffeineBeverage {
/**
* 冲泡饮料的算法,不允许被子类修改
**/
public final void prepareRecipe() {
// 算法的具体步骤
boilWater(); // 烧水
brew(); // 冲泡
pourInCup(); // 把饮料倒入杯子
// 如果要加调料
if(hook()) {
addCondiments(); // 加调料
}
}
public abstract void brew();
public abstract void addCondiments();
public void boilWater() {
System.out.println("烧水");
}
public void pourInCup() {
System.out.println("把饮料倒入杯子");
}
/**
* 钩子函数,可以对算法实现做一些控制
**/
public boolean hook() {
return true;
}
}
/**
* @Description TODO 咖啡类
* @Author xtf
* @Date 2019/8/19 15:12
*/
class Coffee extends CaffeineBeverage {
@Override
public void brew() {
System.out.println("用沸水冲泡咖啡");
}
@Override
public void addCondiments() {
System.out.println("加糖和牛奶");
}
@Override
public boolean hook() {
return super.hook();
}
}
/**
* @Description TODO 茶类
* @Author xtf
* @Date 2019/8/19 15:12
*/
class Tea extends CaffeineBeverage {
@Override
public void brew() {
System.out.println("用沸水浸泡茶叶");
}
@Override
public void addCondiments() {
System.out.println("加柠檬");
}
@Override
public boolean hook() {
return super.hook();
}
}
运行结果:
烧水
用沸水浸泡茶叶
把饮料倒入杯子
加柠檬
烧水
用沸水冲泡咖啡
把饮料倒入杯子
加糖和牛奶
2.策略模式
策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
当我们有一组算法时,如果我们使用传统的if…else…,会造成代码繁复并且难以维护,所以通常我们使用策略模式来代替if…else…
策略模式的主要优点如下。
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
其主要缺点如下。
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。
具体例子:
定义三组算法,分别是加减乘,使用策略模式使他们可以相互替换
代码实现:
package strategy;
/**
* @Description TODO 策略模式
* 定义一个个算法,把他们封装起来,并使他们可以相互替换
* 我们这里定义三个算法,加减乘,使用策略模式使他们可以互相替换
* @Author xtf
* @Date 2019/8/19 15:58
*/
public class Math {
public static void main(String[] args){
Context c1 = new Context(new Add());
System.out.println(c1.executeStrategy(1,2));
Context c2 = new Context(new Sub());
System.out.println(c2.executeStrategy(1,2));
Context c3 = new Context(new Mul());
System.out.println(c3.executeStrategy(1,2));
}
}
/**
* @Description TODO 面向接口编程,使他们实现相同的接口
* @Author xtf
* @Date 2019/8/19 16:00
*/
interface Strategy {
public int doOperation(int num1, int num2);
}
/**
* @Description TODO 加法策略
* @Author xtf
* @Date 2019/8/19 16:01
*/
class Add implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
/**
* @Description TODO 减法策略
* @Author xtf
* @Date 2019/8/19 16:01
*/
class Sub implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
/**
* @Description TODO 乘法策略
* @Author xtf
* @Date 2019/8/19 16:02
*/
class Mul implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
/**
* @Description TODO Context来查看改变策略时的行为变化
* @Author xtf
* @Date 2019/8/19 16:03
*/
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
3.命令模式
命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
命令模式的主要优点如下:
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
其缺点是:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
具体案例:
小爱同学是一个可以帮助便捷生活的智能AI,当我们对小爱同学说:开灯,这个时候灯打开了。当我们对小爱同学说:关灯,这个时候灯关上了。在这里我们使用命令者模式,调用者/请求者(Invoker)是小爱同学。接受者/实现者(Receiver)是灯
代码实现:
package command;
import java.util.List;
/**
* @Description TODO 命令模式
* 将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。
* 命令模式也支持可撤销的操作。命令模式通过这种封装的方式实现将客户端和接收端解耦。
* 本例子是呼叫小爱同学帮你开灯的例子,你对小爱同学说:小爱同学帮我打开灯,然后小爱同学让灯自己打开了。
* @Author xtf
* @Date 2019/8/19 16:47
*/
public class XiaoAiStudent {
public static void main(String[] args){
// 创建小爱同学
XiaoAi xa = new XiaoAi();
//创建具体的接收命令者,即电灯
Light light = new Light();
//命令小爱同学开灯
System.out.println("小爱同学开灯!");
Command command1 = new LightOnCommand(light);
xa.setCommand(command1);
xa.doCommand();
//命令小爱同学关灯
System.out.println("小爱同学关灯!");
Command command2 = new LightOffCommand(light);
xa.setCommand(command2);
xa.doCommand();
}
}
/**
* @Description TODO 命令接口
* @Author xtf
* @Date 2019/8/19 16:49
*/
interface Command {
public void execute();
}
/**
* @Description TODO 具体的命令类,传入一个灯对象,自己调用自己的开灯操作
* @Author xtf
* @Date 2019/8/19 16:52
*/
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.lightOn();
}
}
/**
* @Description TODO 具体的命令类,传入一个灯对象,自己调用自己的关灯操作
* @Author xtf
* @Date 2019/8/19 16:53
*/
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.lightOff();
}
}
/**
* @Description TODO 具体的电灯类,命令接受者Receiver
* @Author xtf
* @Date 2019/8/19 16:51
*/
class Light {
public void lightOn() {
System.out.println("灯灯开了");
}
public void lightOff() {
System.out.println("灯灯关了");
}
}
/**
* @Description TODO 传递命令的对象invoker,即小爱同学
* 传递一个命令给小爱同学,小爱同学对其作出命令
* @Author xtf
* @Date 2019/8/19 16:54
*/
class XiaoAi {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void doCommand() {
command.execute();
}
}
运行结果:
小爱同学开灯!
灯灯开了
小爱同学关灯!
灯灯关了
4.责任链模式
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式,其主要优点如下。
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
其主要缺点如下。
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSP 和 Servlet 的 Filter 等,所有这些,如果用责任链模式都能很好解决。
具体案例:
现在有一个学生请假的需求
如果请假小于两天需要班主任批准
请假小于七天需要系主任批准
请假小于十天需要院长批准
如果请更多天数的假则无人批准
此时我们可以使用责任链模式
代码实现:
package chainofresponsibility;
/**
* @Description TODO 责任链模式
* 学生请假
* 如果小于2天,需要班主任(ClassAdviser)批准
* 小于7天,需要系主任(DepartmentHead)批准
* 小于10天,需要院长(Dean)批准
* 其他情况不允许批准
* @Author xtf
* @Date 2019/8/20 10:48
*/
public class StudentLeave {
public static void main(String[] args){
//组装责任链
Leader leader1 = new ClassAdviser();
Leader leader2 = new DepartmentHead();
Leader leader3 = new Dean();
leader1.setNext(leader2);
leader2.setNext(leader3);
leader1.handleRequest(9);
}
}
/**
* @Description TODO 领导抽象类(Handle)
* @Author xtf
* @Date 2019/8/20 10:52
*/
abstract class Leader {
private Leader next;
public void setNext(Leader next) {
this.next = next;
}
public Leader getNext() {
return next;
}
public abstract void handleRequest(int leaveDays);
}
/**
* @Description TODO 班主任类,可以处理请假天数少于两天的请求
* @Author xtf
* @Date 2019/8/20 10:59
*/
class ClassAdviser extends Leader {
@Override
public void handleRequest(int leaveDays) {
// 如果请假天数小于两天,则班主任批准请假
if(leaveDays <= 2) {
System.out.println("班主任批准请假!");
} else { //如果请假天数大于两天,让下一个责任链处理
// 如果有下一个责任链有人处理,则让下一个责任链处理
if(getNext() != null) {
getNext().handleRequest(leaveDays);
} else { //如果下一个责任链为空,则输出无人处理
System.out.println("请假天数太多,无人批准该假条");
}
}
}
}
/**
* @Description TODO 系主任类,允许批准七天以下的假条
* @Author xtf
* @Date 2019/8/20 11:02
*/
class DepartmentHead extends Leader {
@Override
public void handleRequest(int leaveDays) {
// 如果请假天数小于七天,则班主任批准请假
if(leaveDays <= 7) {
System.out.println("系主任批准请假!");
} else { //如果请假天数大于七天,让下一个责任链处理
// 如果有下一个责任链有人处理,则让下一个责任链处理
if (getNext() != null) {
getNext().handleRequest(leaveDays);
} else { //如果下一个责任链为空,则输出无人处理
System.out.println("请假天数太多,无人批准该假条");
}
}
}
}
/**
* @Description TODO 院长类,允许处理10天以下的假条
* @Author xtf
* @Date 2019/8/20 11:03
*/
class Dean extends Leader {
@Override
public void handleRequest(int leaveDays) {
// 如果请假天数小于十天,则班主任批准请假
if(leaveDays <= 10) {
System.out.println("院长批准请假!");
} else { //如果请假天数大于十天,让下一个责任链处理
// 如果有下一个责任链有人处理,则让下一个责任链处理
if (getNext() != null) {
getNext().handleRequest(leaveDays);
} else { //如果下一个责任链为空,则输出无人处理
System.out.println("请假天数太多,无人批准该假条");
}
}
}
}
运行结果:
院长批准请假!
5.状态模式
状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式是一种对象行为型模式,其主要优点如下。
- 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
状态模式的主要缺点如下。
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
具体案例:
模拟线程工作,多线程存在 5 种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如图所示。
代码实现:
package state;
/**
* @Description TODO 状态模式
* 模拟线程工作,线程有五种状态
* 新建--(start)-->就绪--(获取CPU时间)-->运行--(stop)-->死亡
* \ \
* resume() suspend()
* \ \
* <--------阻塞---------<
*
* 每个状态遇到相应的方法调用或者事件触发时会跳转到另一种状态
* @Author xtf
* @Date 2019/8/20 11:45
*/
public class ThreadStateTest {
public static void main(String[] args){
ThreadContext tc = new ThreadContext();
tc.start();
tc.getCPU();
tc.suspend();
tc.resume();
tc.getCPU();
tc.stop();
}
}
class ThreadContext {
private ThreadState state;
public ThreadContext() {
state = new New();
}
public void setState(ThreadState state)
{
this.state=state;
}
public ThreadState getState()
{
return state;
}
public void start()
{
((New) state).start(this);
}
public void getCPU()
{
((Runnable) state).getCPU(this);
}
public void suspend()
{
((Running) state).suspend(this);
}
public void stop()
{
((Running) state).stop(this);
}
public void resume()
{
((Blocked) state).resume(this);
}
}
/**
* @Description TODO 线程状态的抽象类
* @Author xtf
* @Date 2019/8/20 11:51
*/
abstract class ThreadState {
/**
* 状态名
**/
protected String stateName;
}
/**
* @Description TODO 具体状态类:新建状态
* @Author xtf
* @Date 2019/8/20 11:59
*/
class New extends ThreadState {
public New() {
stateName = "新建状态";
System.out.println("当前线程处于:新建状态");
}
public void start(ThreadContext hj)
{
System.out.print("调用start()方法-->");
if("新建状态".equals(stateName)) {
hj.setState(new Runnable());
} else {
System.out.println("当前线程不是新建状态,不能调用start()方法.");
}
}
}
/**
* @Description TODO 具体状态类:就绪状态
* @Author xtf
* @Date 2019/8/20 11:59
*/
class Runnable extends ThreadState {
public Runnable() {
stateName = "就绪状态";
System.out.println("当前线程处于:运行状态");
}
public void getCPU(ThreadContext hj) {
System.out.println("获取CPU时间-->");
if("就绪状态".equals(stateName)) {
hj.setState(new Running());
} else {
System.out.println("当前线程不是就绪状态,不能获取CPU.");
}
}
}
/**
* @Description TODO 具体状态类:运行状态
* @Author xtf
* @Date 2019/8/20 11:58
*/
class Running extends ThreadState
{
public Running()
{
stateName="运行状态";
System.out.println("当前线程处于:运行状态.");
}
public void suspend(ThreadContext hj)
{
System.out.print("调用suspend()方法-->");
if(stateName.equals("运行状态"))
{
hj.setState(new Blocked());
}
else
{
System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
}
}
public void stop(ThreadContext hj)
{
System.out.print("调用stop()方法-->");
if(stateName.equals("运行状态"))
{
hj.setState(new Dead());
}
else
{
System.out.println("当前线程不是运行状态,不能调用stop()方法.");
}
}
}
/**
* @Description TODO 具体状态类:阻塞状态
* @Author xtf
* @Date 2019/8/20 11:58
*/
class Blocked extends ThreadState
{
public Blocked()
{
stateName="阻塞状态";
System.out.println("当前线程处于:阻塞状态.");
}
public void resume(ThreadContext hj)
{
System.out.print("调用resume()方法-->");
if(stateName.equals("阻塞状态"))
{
hj.setState(new Runnable());
}
else
{
System.out.println("当前线程不是阻塞状态,不能调用resume()方法.");
}
}
}
/**
* @Description TODO 具体状态类:死亡状态
* @Author xtf
* @Date 2019/8/20 11:59
*/
class Dead extends ThreadState
{
public Dead()
{
stateName="死亡状态";
System.out.println("当前线程处于:死亡状态.");
}
}
运行结果:
当前线程处于:新建状态
调用start()方法-->当前线程处于:运行状态
获取CPU时间-->
当前线程处于:运行状态.
调用suspend()方法-->当前线程处于:阻塞状态.
调用resume()方法-->当前线程处于:运行状态
获取CPU时间-->
当前线程处于:运行状态.
调用stop()方法-->当前线程处于:死亡状态.
6.观察者模式
在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。
在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是一种对象行为型模式,其主要优点如下。
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
它的主要缺点如下。
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
观察者模式在软件幵发中用得最多的是窗体程序设计中的事件处理,窗体中的所有组件都是“事件源”,也就是目标对象,而事件处理程序类的对象是具体观察者对象。下面以一个学校铃声的事件处理程序为例,介绍 Windows 中的“事件处理模型”的工作原理。
具体案例:
在本实例中,学校的“铃”是事件源和目标,“老师”和“学生”是事件监听器和具体观察者,“铃声”是事件类。学生和老师来到学校的教学区,都会注意学校的铃,这叫事件绑定;当上课时间或下课时间到,会触发铃发声,这时会生成“铃声”事件;学生和老师听到铃声会开始上课或下课,这叫事件处理。这个实例非常适合用观察者模式实现
现在用“观察者模式”来实现该事件处理模型。首先,定义一个铃声事件(RingEvent)类,它记录了铃声的类型(上课铃声/下课铃声);再定义一个学校的铃(BellEventSource)类,它是事件源,是观察者目标类,该类里面包含了监听器容器 listener,可以绑定监听者(学生或老师),并且有产生铃声事件和通知所有监听者的方法;然后,定义一声事件监听者(BellEventListener)类,它是抽象观察者,它包含了铃声事件处理方法 heardBell(RingEvent e);最后,定义老师类(TeachEventListener)和学生类(StuEventListener),它们是事件监听器,是具体观察者,听到铃声会去上课或下课。
代码实现:
package observer;
import java.util.*;
/**
* @Description TODO 观察者模式
* 模拟打上课铃和打下课铃同学和老师的行为
* @Author xtf
* @Date 2019/8/21 10:56
*/
public class BellEventTest {
public static void main(String[] args){
// 新建事件源
BellEventSource bes = new BellEventSource();
// 注册老师监听器
bes.addPersonListener(new TeacherEventListener());
// 注册学生监听器
bes.addPersonListener(new StudentEventListener());
// 打上课铃
bes.ring(true);
// 打下课铃
bes.ring(false);
}
}
/**
* @Description TODO 铃声事件类,用于封装事件源和一些事件相关的参数
* EventObject 是JDK中封装的一个事件类
* @Author xtf
* @Date 2019/8/21 10:59
*/
class RingEvent extends EventObject {
/**
* true表示上课铃,false表示下课铃
**/
private boolean sound;
public RingEvent(Object source, boolean sound) {
super(source);
this.sound = sound;
}
public boolean getSound() {
return sound;
}
public void setSound(boolean sound) {
this.sound = sound;
}
}
/**
* @Description TODO 目标类。事件源,铃
* @Author xtf
* @Date 2019/8/21 11:10
*/
class BellEventSource {
/**
* 铃事件源内部维护的监听器容器
**/
private List<BellEventListener> listener;
public BellEventSource() {
listener = new ArrayList<>();
}
/**
* @Description TODO 给事件源绑定监听器
* @Author xtf
* @Date 2019/8/21 11:13
* @Param [person]
* @return void
*/
public void addPersonListener(BellEventListener person) {
listener.add(person);
}
/**
* @Description TODO 敲钟,当铃声发生变化时触发事件
* @Author xtf
* @Date 2019/8/21 11:26
* @Param [sound]
* @return void
*/
public void ring(boolean sound) {
System.out.println(sound?"上课铃":"下课铃" + "响");
RingEvent re = new RingEvent(this, sound);
notifies(re);
}
/**
* @Description TODO 当事件发生时,通知绑定在该事件源上的所有监听器做出反应(调用事件处理方法)
* @Author xtf
* @Date 2019/8/21 11:31
* @Param [e]
* @return void
*/
public void notifies(RingEvent e) {
BellEventListener b = null;
Iterator<BellEventListener> i = listener.iterator();
while(i.hasNext()) {
b = i.next();
b.hellBell(e);
}
}
}
/**
* @Description TODO 抽象观察者类:铃声事件监听器
* @Author xtf
* @Date 2019/8/21 11:07
*/
interface BellEventListener extends EventListener {
/**
* @Description TODO 听到铃声
* @Author xtf
* @Date 2019/8/21 11:26
* @Param [e]
* @return void
*/
public void hellBell(RingEvent e);
}
/**
* @Description TODO 具体观察者类,老师事件监听器
* @Author xtf
* @Date 2019/8/21 11:09
*/
class TeacherEventListener implements BellEventListener {
@Override
public void hellBell(RingEvent e) {
if(e.getSound()) {
System.out.println("老师上课了...");
} else {
System.out.println("老师下课了...");
}
}
}
/**
* @Description TODO 具体观察者类,学生事件监听器
* @Author xtf
* @Date 2019/8/21 11:09
*/
class StudentEventListener implements BellEventListener {
@Override
public void hellBell(RingEvent e) {
if(e.getSound()) {
System.out.println("同学上课了...");
} else {
System.out.println("同学下课了...");
}
}
}
运行结果:
上课铃
老师上课了...
同学上课了...
下课铃响
老师下课了...
同学下课了...
7.中介者模式
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂。
如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参力口工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。
在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。
中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式是一种对象行为型模式,其主要优点如下。
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
其主要缺点是:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
具体案例:
我们现在有三种数据库,分别是Mysql,Redis,Elasticsearch
其中Mysql作为主数据库,当MySQL添加一条数据时,需要其他两个数据库同步添加数据
Redis作为缓存数据库,当Redis添加数据时,不需要其他数据库添加数据
Elasticsearch作为大数据数据库,当Elasticsearch添加数据时,只需要往MySQL中添加数据
代码实现:
package mediator;
import java.util.ArrayList;
import java.util.List;
/**
* @Description TODO 中介者模式
* 我们现在有三种数据库,分别是Mysql,Redis,Elasticsearch
* 其中Mysql作为主数据库,当MySQL添加一条数据时,需要其他两个数据库同步添加数据
* Redis作为缓存数据库,当Redis添加数据时,不需要其他数据库添加数据
* Elasticsearch作为大数据数据库,当Elasticsearch添加数据时,只需要往MySQL中添加数据
* @Author xtf
* @Date 2019/8/21 15:20
*/
public class DatabaseMediator {
public static void main(String[] args){
// 中介者
AbstractMediator am = new syncMediator();
// 三个数据库实例
AbstractDatabase mysql = new MysqlDatabase(am);
AbstractDatabase redis = new RedisDatabase(am);
AbstractDatabase elasticsearch = new ElasticsearchDatabase(am);
// 将三个数据库实例设置到中介者中
am.setMysqlDatabase((MysqlDatabase)mysql);
am.setRedisDatabase((RedisDatabase)redis);
am.setElasticsearchDatabase((ElasticsearchDatabase)elasticsearch);
System.out.println("-----------mysql添加数据----------");
mysql.add("1");
((MysqlDatabase) mysql).select();
((RedisDatabase) redis).select();
((ElasticsearchDatabase) elasticsearch).select();
System.out.println("-----------redis添加数据----------");
redis.add("2");
((MysqlDatabase) mysql).select();
((RedisDatabase) redis).select();
((ElasticsearchDatabase) elasticsearch).select();
System.out.println("-----------Elasticsearch添加数据----------");
elasticsearch.add("3");
((MysqlDatabase) mysql).select();
((RedisDatabase) redis).select();
((ElasticsearchDatabase) elasticsearch).select();
}
}
/**
* @Description TODO 抽象同事类,其中维护了一个中介者
* @Author xtf
* @Date 2019/8/21 15:24
*/
abstract class AbstractDatabase {
public static final String MYSQL = "mysql";
public static final String REDIS = "redis";
public static final String ELASTICSEARCH = "elasticsearch";
/**
* 维护的中介者
**/
protected AbstractMediator mediator;
public AbstractDatabase(AbstractMediator mediator) {
this.mediator = mediator;
}
public abstract void addData(String data);
public abstract void add(String data);
}
/**
* @Description TODO 具体同事类,MySQL数据库
* @Author xtf
* @Date 2019/8/21 15:31
*/
class MysqlDatabase extends AbstractDatabase {
private List<String> dataSet = null;
public MysqlDatabase(AbstractMediator mediator) {
super(mediator);
dataSet = new ArrayList<>();
}
@Override
public void addData(String data) {
System.out.println("Mysql添加数据:" + data);
dataSet.add(data);
}
@Override
public void add(String data) {
addData(data);
this.mediator.sync(AbstractDatabase.MYSQL, data);
}
public void select() {
System.out.println("mysql中的数据: " + dataSet.toString());
}
}
/**
* @Description TODO 具体同事类,Redis数据库
* @Author xtf
* @Date 2019/8/21 15:31
*/
class RedisDatabase extends AbstractDatabase {
private List<String> dataSet = null;
public RedisDatabase(AbstractMediator mediator) {
super(mediator);
dataSet = new ArrayList<>();
}
@Override
public void addData(String data) {
System.out.println("Redis添加数据:" + data);
dataSet.add(data);
}
@Override
public void add(String data) {
addData(data);
this.mediator.sync(AbstractDatabase.REDIS, data);
}
public void select() {
System.out.println("redis 中的数据 : " + dataSet.toString());
}
}
/**
* @Description TODO 具体同事类,Elasticsearch数据库
* @Author xtf
* @Date 2019/8/21 15:31
*/
class ElasticsearchDatabase extends AbstractDatabase {
private List<String> dataSet = null;
public ElasticsearchDatabase(AbstractMediator mediator) {
super(mediator);
dataSet = new ArrayList<>();
}
@Override
public void addData(String data) {
System.out.println("ElasticsearchDatabase添加数据:" + data);
dataSet.add(data);
}
@Override
public void add(String data) {
addData(data);
this.mediator.sync(AbstractDatabase.ELASTICSEARCH, data);
}
public void select() {
System.out.println("Elasticsearch中的数据 : " + dataSet.toString());
}
}
/**
* @Description TODO 抽象中介者
* @Author xtf
* @Date 2019/8/21 15:33
*/
abstract class AbstractMediator {
protected MysqlDatabase mysqlDatabase;
protected RedisDatabase redisDatabase;
protected ElasticsearchDatabase elasticsearchDatabase;
public void setMysqlDatabase(MysqlDatabase mysqlDatabase) {
this.mysqlDatabase = mysqlDatabase;
}
public void setRedisDatabase(RedisDatabase redisDatabase) {
this.redisDatabase = redisDatabase;
}
public void setElasticsearchDatabase(ElasticsearchDatabase elasticsearchDatabase) {
this.elasticsearchDatabase = elasticsearchDatabase;
}
/**
* @Description TODO 中介者的同步添加方法
* @Author xtf
* @Date 2019/8/21 15:34
* @Param [databaseName, data]
* @return void
*/
public abstract void sync(String databaseName, String data);
}
class syncMediator extends AbstractMediator {
@Override
public void sync(String databaseName, String data) {
// 如果是MySQL数据库,往Redis数据库和Elasticsearch数据库中添加数据
if(AbstractDatabase.MYSQL.equals(databaseName)) {
this.redisDatabase.addData(data);
this.elasticsearchDatabase.addData(data);
} else if(AbstractDatabase.REDIS.equals(databaseName)) {
// 如果是Redis数据库则什么也不做
} else if(AbstractDatabase.ELASTICSEARCH.equals(databaseName)) {
// 如果是Elasticsearch数据库,往mysql数据库中添加数据
this.mysqlDatabase.addData(data);
}
}
}
运行结果:
-----------redis添加数据----------
Redis添加数据:2
mysql中的数据: [1]
redis 中的数据 : [1, 2]
Elasticsearch中的数据 : [1]
-----------Elasticsearch添加数据----------
ElasticsearchDatabase添加数据:3
Mysql添加数据:3
mysql中的数据: [1, 3]
redis 中的数据 : [1, 2]
Elasticsearch中的数据 : [1, 3]
8.迭代器模式
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:
- 暴露了聚合类的内部表示,使其数据不安全;
- 增加了客户的负担。
“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。
迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
迭代器模式是一种对象行为型模式,其主要优点如下。
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
当然,迭代器模式现在使用的比较少,除了一些大型项目可能会使用以外,基本上都是用JDK中自带的迭代器
具体案例:
模拟List中的迭代器
代码实现:
package iterator;
import java.util.ArrayList;
import java.util.List;
/**
* @Description TODO 迭代器模式
* 模拟List中的迭代器
* @Author xtf
* @Date 2019/8/21 16:55
*/
public class ListDemo {
public static void main(String[] args){
AbstractXtfList ag=new XtfList();
ag.add("中山大学");
ag.add("华南理工");
ag.add("韶关学院");
System.out.print("数组的内容有:");
Iterator it=ag.getIterator();
while(it.hasNext())
{
Object ob=it.next();
System.out.print(ob.toString()+"\t");
}
Object ob=it.first();
System.out.println("\nFirst:"+ob.toString());
}
}
/**
* @Description TODO 抽象数组
* @Author xtf
* @Date 2019/8/21 16:57
*/
interface AbstractXtfList {
public void add(Object o);
public void remove(Object o);
public Iterator getIterator();
}
/**
* @Description TODO 具体数组实现类
* @Author xtf
* @Date 2019/8/21 16:58
*/
class XtfList implements AbstractXtfList {
private List<Object> list = new ArrayList<>();
@Override
public void add(Object o) {
list.add(o);
}
@Override
public void remove(Object o) {
list.remove(o);
}
@Override
public Iterator getIterator() {
return (new ConcreteIterator(list));
}
}
/**
* @Description TODO 抽象迭代器
* @Author xtf
* @Date 2019/8/21 16:57
*/
interface Iterator {
Object first();
Object next();
boolean hasNext();
}
/**
* @Description TODO 具体迭代器实现类
* @Author xtf
* @Date 2019/8/21 17:05
*/
class ConcreteIterator implements Iterator {
/**
*
**/
private List<Object> list = null;
private int index = -1;
ConcreteIterator(List<Object> list) {
this.list = list;
}
@Override
public Object first() {
index = 0;
return list.get(index);
}
@Override
public Object next() {
if(hasNext()) {
return list.get(++index);
} else {
return null;
}
}
@Override
public boolean hasNext() {
if(index < list.size() - 1) {
return true;
} else {
return false;
}
}
}
运行结果:
数组的内容有:中山大学 华南理工 韶关学院
First:中山大学
9.访问者模式
在现实生活中,有些集合对象中存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。
这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。
这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
访问者(Visitor)模式的主要缺点如下。
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
通常在以下情况可以考虑使用访问者(Visitor)模式。
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
具体案例:
艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;
造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币
对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。
代码实现:
package visitor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Description TODO 访问者模式
* 艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;
* 造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币
* 对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。
* @Author xtf
* @Date 2019/8/21 17:39
*/
public class CompanyDemo {
public static void main(String[] args){
// 创造材料集,添加材料
SetMaterial sm = new SetMaterial();
sm.add(new Paper());
sm.add(new Cuprum());
// 让公司去访问材料集
Company c1 = new ArtCompany();
sm.accept(c1);
Company c2 = new Mint();
sm.accept(c2);
}
}
/**
* @Description TODO 抽象访问者:公司
* @Author xtf
* @Date 2019/8/21 17:41
*/
interface Company {
public String create(Paper page);
public String create(Cuprum cuprum);
}
/**
* @Description TODO 具体访问者 艺术公司
* @Author xtf
* @Date 2019/8/21 17:47
*/
class ArtCompany implements Company {
@Override
public String create(Paper page) {
return "清明上河图";
}
@Override
public String create(Cuprum cuprum) {
return "朱熹铜像";
}
}
/**
* @Description TODO 具体访问者 造币公司
* @Author xtf
* @Date 2019/8/21 17:48
*/
class Mint implements Company {
@Override
public String create(Paper page) {
return "纸币";
}
@Override
public String create(Cuprum cuprum) {
return "铜币";
}
}
/**
* @Description TODO 抽象元素 材料
* @Author xtf
* @Date 2019/8/21 17:42
*/
interface Material {
public String accept(Company visitor);
}
/**
* @Description TODO 具体元素 纸类
* @Author xtf
* @Date 2019/8/21 17:44
*/
class Paper implements Material {
@Override
public String accept(Company visitor) {
return visitor.create(this);
}
}
/**
* @Description TODO 具体元素 铜类
* @Author xtf
* @Date 2019/8/21 17:45
*/
class Cuprum implements Material {
@Override
public String accept(Company visitor) {
return visitor.create(this);
}
}
/**
* @Description TODO 材料集
* @Author xtf
* @Date 2019/8/21 17:50
*/
class SetMaterial {
/**
* 内部维护了一个List来保存材料
**/
private List<Material> list = new ArrayList<>();
/**
* 传入一个公司,让公司去访问这些材料
**/
public void accept(Company company) {
Iterator<Material> i = list.iterator();
while(i.hasNext()) {
System.out.println(i.next().accept(company));
}
}
public void add(Material material) {
list.add(material);
}
public void remove(Material material) {
list.remove(material);
}
}
运行结果:
清明上河图
朱熹铜像
纸币
铜币
10.备忘录模式
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
备忘录模式是一种对象行为型模式,其主要优点如下。
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现。
备忘录模式的主要角色如下。
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
代码实现:
package memento;
/**
* @Description TODO 备忘录模式
* @Author xtf
* @Date 2019/8/22 12:17
*/
public class MementoPattern {
public static void main(String[] args){
// 发起者实例
Originator or = new Originator();
// 管理者实例
Caretaker ca = new Caretaker();
// 当前状态
or.setState("State1");
System.out.println("当前状态为:" + or.getState());
// 管理者保存当前状态
ca.setMemento(or.createMemento());
System.out.println("管理者保存了当前状态:" + ca.getMemento().getState());
// 改变当前状态
or.setState("State2");
System.out.println("当前状态为:" + or.getState());
// 恢复之前管理者保存的状态
or.restoreMemento(ca.getMemento());
System.out.println("当前状态为:" + or.getState());
}
}
/**
* @Description TODO 备忘录,具体类,其中使用一个String存储数据
* 负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
* @Author xtf
* @Date 2019/8/22 12:19
*/
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
/**
* @Description TODO 发起人
* 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能
* 实现其他业务功能,它可以访问备忘录里的所有信息。
* @Author xtf
* @Date 2019/8/22 12:20
*/
class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 创建新备忘录
**/
public Memento createMemento() {
return new Memento(state);
}
/**
* 恢复备忘录
**/
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
/**
* @Description TODO 管理者
* 对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
* @Author xtf
* @Date 2019/8/22 12:26
*/
class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
运行结果:
当前状态为:State1
管理者保存了当前状态:State1
当前状态为:State2
当前状态为:State1
11.解释器模式
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。
解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
该模式使用较少,并且笔者还没有学编译原理,以后再做具体案例
完!