设计模式
-
设计模式的目的
-
编写软件的过程中,程序员面临着耦合性、内聚性、可维护性、可扩展性、重用性、灵活性等多方面的挑战,设计模式就是为了让程序具有这些特性
-
代码重用性
相同功能的代码,不需要多次编写
-
可读性
即编程规范性,便于其他程序员的阅读和理解
-
可扩展性(可维护性)
当我们需要增加新的功能的时候,会非常的方便
-
可靠性
当我们增加新的功能之后,对原来的功能没有影响
-
使程序呈现高内聚、低耦合的特性
程序与程序之间应该尽量降低耦合度,避免A程序的错误影响到B程序
-
-
类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合
- 依赖:只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了
- 类中用到了对方
- 类的成员属性
- 方法的返回类型
- 方法接收的参数类型
- 方法中使用到
- 泛化:实际上就是继承关系,是依赖关系的特例
- 实现:A类实现B接口,是依赖关系的特例
- 关联:是类与类之间的联系,是依赖关系的特例(单向一对一关系、双向一对一关系)
- 关联具有导航性:即双向关系或单向关系
- 关系具有多重性:如“1”(表示有且仅有一个),“0…”(表示0个或者多个), “0,1”(表示0个或者一个),“n…m”(表示n到 m个都可以),“m…*”(表示至少m 个)
- 聚合:整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例,所以他具有关联的导航性与多重性
- 组合:也是整体与部分的关系,但是整体与部分不可以分开。
- 依赖:只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了
创建型模式
1. 工厂模式
-
简单工厂模式
- 具体需求
- 一个披萨的项目:要便于披萨种类的扩展,要便于维护
- 披萨的种类很多
- 披萨的制作有 prepare,bake, cut, box
- 完成披萨店订购功能
/* 1. 传统的方式的优缺点 1. 优点是比较好理解,简单易操作。 2. 缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量 不修改代码,或者尽可能少修改代码. 3. 比如我们这时要新增加一个Pizza的种类(Pepper披萨),这个时候就要修改很多地方 2. 改进的思路分析 1. 修改代码可以接受,但是如果我们在其它的地方也有创建Pizza的代码,就意味着,也需要修改,而创建 Pizza的代码,往往有多处 2. 把创建Pizza对象封装到一个类中,这样我们有新的Pizza种类时,只需要修改该类就可,其它有创建 到Pizza对象的代码就不需要修改了 --> 简单工厂模式 */ public abstract class Pizza { protected String name; public abstract void prepare(); public void bake() { System.out.println(name + " baking;"); } public void cut() { System.out.println(name + " cutting;"); } public void box() { System.out.println(name + " boxing;"); } public void setName(String name) { this.name = name; } } public class CheesePizza extends Pizza{ @Override public void prepare() { System.out.println("为准备制作奶酪披萨,准备原材料.."); } } public class GreekPizza extends Pizza{ @Override public void prepare() { System.out.println("为准备制作希腊披萨,准备原材料.."); } } public class OrderPizza { //在构造器里完成pizza订单 public OrderPizza(){ Pizza pizza = null; String orderType; //订购披萨的类型 do{ orderType = getType(); if("greek".equals(orderType)){ pizza = new GreekPizza(); pizza.setName("希腊披萨"); }else if("cheese".equals(orderType)){ pizza = new CheesePizza(); pizza.setName("奶酪披萨"); }else{ break; } //输出披萨的制作过程 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }while(true); } //从控制台获取客户希望订购的披萨种类 private String getType() { Scanner scanner = new Scanner(System.in); System.out.println("请输入你想要订购的披萨种类: "); String s = scanner.next(); return s; } } public static void main(String[] args) { OrderPizza pizza = new OrderPizza(); } /* 使用简单工厂模式 1. 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种 产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式 2. 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码) 3. 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式. */ public class SimpleFactory { public Pizza createPizza(String orderType){ Pizza pizza = null; System.out.println("使用简单工厂模式: "); if("greek".equals(orderType)){ pizza = new GreekPizza(); pizza.setName("希腊披萨"); }else if("cheese".equals(orderType)){ pizza = new CheesePizza(); pizza.setName("奶酪披萨"); } return pizza; } } public class OrderPizza2 { private SimpleFactory factory; public OrderPizza2(SimpleFactory factory){ setFactory(factory); } public void setFactory(SimpleFactory factory){ String orderType = null; Pizza pizza = null; this.factory = factory; do{ orderType = getType(); pizza = this.factory.createPizza(orderType); if(pizza != null){ pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }else{ System.out.println("订购披萨失败..."); break; } }while(true); } public String getType(){ Scanner scanner = new Scanner(System.in); System.out.println("请输入你想要订购的披萨种类: "); String s = scanner.next(); return s; } } public static void main(String[] args) { OrderPizza2 order = new OrderPizza2(new SimpleFactory()); System.out.println("退出程序..."); }
- 具体需求
-
工厂方法模式
/* 新的需求 1. 披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪pizza、北京的胡椒 pizza 或者是伦敦的奶酪pizza、伦敦的胡椒pizza 思路1: 使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzaSimpleFactory 等等.从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性不是特别好 思路2: 使用工厂方法模式 工厂方法模式 1. 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现 1. 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例 化推迟到子类 */ public class BJPepperPizza extends Pizza{ @Override public void prepare() { setName("北京胡椒披萨"); System.out.println("为" + name + "制作准备原材料..."); } } public class BJCheesePizza extends Pizza{ @Override public void prepare() { setName("北京奶酪披萨"); System.out.println("为" + name + "制作准备原材料..."); } } public class LDCheesePizza extends Pizza{ @Override public void prepare() { setName("伦敦奶酪披萨"); System.out.println("为" + name + "制作准备原材料..."); } } public class LDPepperPizza extends Pizza{ @Override public void prepare() { setName("伦敦胡椒披萨"); System.out.println("为" + name + "制作准备原材料..."); } } public abstract class OrderPizza { //定义抽象方法,让子类实现 public abstract Pizza createPizza(String orderType); public OrderPizza(){ Pizza pizza = null; String orderType; do{ orderType = getType(); if(pizza != null){ pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }else{ break; } }while(true); } private String getType() { Scanner scanner = new Scanner(System.in); System.out.println("请输入你想要订购的披萨种类: "); String s = scanner.next(); return s; } } public class BJOrderPizza extends OrderPizza{ @Override public Pizza createPizza(String orderType) { Pizza pizza = null; if("cheese".equals(orderType)){ pizza = new BJCheesePizza(); }else if("pepper".equals(orderType)){ pizza = new BJPepperPizza(); } return pizza; } } public class LDOrderPizza extends OrderPizza{ @Override public Pizza createPizza(String orderType) { Pizza pizza = null; if("cheese".equals(orderType)){ pizza = new LDCheesePizza(); }else if("pepper".equals(orderType)){ pizza = new LDPepperPizza(); } return pizza; } } public static void main(String[] args) { BJOrderPizza pizza = new BJOrderPizza(); }
2. 抽象工厂模式
-
基本介绍
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展
//抽象层 public interface AbsFactory { public Pizza createPizza(String orderType); } public class BJFactory implements AbsFactory{ @Override public Pizza createPizza(String orderType) { Pizza pizza = null; if("cheese".equals(orderType)){ pizza = new BJCheesePizza(); }else if("pepper".equals(orderType)){ pizza = new BJPepperPizza(); } return pizza; } } public class LDFactory implements AbsFactory{ @Override public Pizza createPizza(String orderType) { Pizza pizza = null; if("cheese".equals(orderType)){ pizza = new LDCheesePizza(); }else if("pepper".equals(orderType)){ pizza = new LDPepperPizza(); } return null; } } public class OrderPizza { private AbsFactory factory; public OrderPizza(AbsFactory factory){ setFactory(factory); } public void setFactory(AbsFactory factory){ Pizza pizza = null; this.factory = factory; String orderType; do{ orderType = getType(); pizza = factory.createPizza(orderType); if(pizza != null){ pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }else{ break; } }while(true); } private String getType() { Scanner scanner = new Scanner(System.in); System.out.println("请输入你想要订购的披萨种类: "); String s = scanner.next(); return s; } } public static void main(String[] args) { OrderPizza pizza = new OrderPizza(new BJFactory()); }
-
工厂模式在JDK中Calendar的运用
public static Calendar getInstance(){ return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT)); } private static Calendar createCalendar(TimeZone zone, Locale aLocale){ CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale) .getCalendarProvider(); if (provider != null) { try { return provider.getInstance(zone, aLocale); } catch (IllegalArgumentException iae) { // fall back to the default instantiation } } Calendar cal = null; if (aLocale.hasExtensions()) { String caltype = aLocale.getUnicodeLocaleType("ca"); if (caltype != null) { switch (caltype) { case "buddhist": cal = new BuddhistCalendar(zone, aLocale); break; case "japanese": cal = new JapaneseImperialCalendar(zone, aLocale); break; case "gregory": cal = new GregorianCalendar(zone, aLocale); break; } } } if (cal == null) { // If no known calendar type is explicitly specified, // perform the traditional way to create a Calendar: // create a BuddhistCalendar for th_TH locale, // a JapaneseImperialCalendar for ja_JP_JP locale, or // a GregorianCalendar for any other locales. // NOTE: The language, country and variant strings are interned. if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") { cal = new BuddhistCalendar(zone, aLocale); } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") { cal = new JapaneseImperialCalendar(zone, aLocale); } else { cal = new GregorianCalendar(zone, aLocale); } } return cal; }
-
工厂模式小结
- 工厂模式的意义将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性
- 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法 中,并返回,有的书上说,变量不要直接持有具体类的引用
- 不要让类继承具体类,而是继承抽象类或者是实现interface(接口)
- 不要覆盖基类中已经实现的方法
3. 单例模式
-
基本介绍
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
-
单例模式的八种方式
-
饿汉式(静态常量)
/* 1. 构造器私有化 (防止new对象) 2. 类的内部创建对象 3. 向外暴露一个静态的公共方法 优缺点说明: 1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题 2. 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费 3. 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果 4. 结论:这种单例模式可用,可能造成内存浪费 */ public class SingletonTest01 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); //true } } //饿汉式(静态变量) class Singleton{ //1.构造器私有化,外部不能new对象 private Singleton(){ } //2.本类内部创建实例对象 private static final Singleton instance = new Singleton(); //3.提供一个公有的静态方法,返回实例对象 public static Singleton getInstance(){ return instance; } }
-
饿汉式(静态代码块)
/* 1. 优缺点说明: 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时 候,就执行静态代码块中的代码,初始化类的实例,优缺点和上面是一样的 2. 结论:这种单例模式可用,但是也可能造成内存浪费 */ public class SingletonTest02 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); //true } } //饿汉式(静态代码块) class Singleton{ private Singleton(){ } private static final Singleton instance; static { instance = new Singleton(); } public static Singleton getInstance(){ return instance; } }
-
懒汉式(线程不安全)
/* 1. 优缺点说明: 1. 起到了Lazy Loading的效果,但是只能在单线程下使用 2. 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行, 另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式 2. 结论:在实际开发中,不要使用这种方式 */ public class SingletonTest03 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); //true } } //懒汉式(线程不安全) class Singleton{ private static Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
-
懒汉式(线程安全,同步方法)
/* 1. 优缺点说明: 1. 解决了线程不安全问题 2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这 个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率 太低 2. 结论:在实际开发中,不推荐使用这种方式 */ public class SingletonTest04 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); //true } } //懒汉式(线程安全,同步方法) class Singleton{ private static Singleton instance; private Singleton(){ } //线程同步 public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
-
懒汉式(线程安全,同步代码块)
/* 1. 优缺点说明: 1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的 的代码块 2. 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句, 这时便会产生多个实例 2. 结论:在实际开发中,不能使用这种方式 */ public class SingletonTest05 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); //true } } //懒汉式(同步代码块,线程还是不安全) class Singleton{ private static Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ instance = new Singleton(); } } return instance; } }
-
双重检查
/* 1. 优缺点说明: 1. 双重检查概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了 2. 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接 return实例化对象,也避免的反复进行方法同步 3. 线程安全,延迟加载,效率较高 2. 结论:在实际开发中,推荐使用这种单例设计模式 */ public class SingletonTest06 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); //true } } //双重检查 class Singleton{ private static volatile Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
-
静态内部类
/* 1. 优缺点说明: 1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程 2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化 3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在 类进行初始化时,别的线程是无法进入的 4. 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高 2. 结论:推荐使用 */ public class SingletonTest07 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); //true } } //静态内部类 class Singleton{ private static volatile Singleton instance; private Singleton(){ } private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static synchronized Singleton getInstance(){ return SingletonInstance.INSTANCE; } }
-
枚举
/* 1. 优缺点说明: 1. 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化 重新创建新的对象。 2. 这种方式是Effective Java作者Josh Bloch提倡的方式 2. 结论:推荐使用 */ public class SingletonTest08 { public static void main(String[] args) { Singleton instance1 = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance1 == instance2); instance1.ok(); } } //使用枚举,也可以实现单例 enum Singleton{ INSTANCE; public void ok(){ System.out.println("ok..."); } }
-
-
单例模式JDK1.8分析
//Runtime中使用到了饿汉式(静态变量) //因为Runtime随时都会使用到,所以不会产生资源浪费 public class Runtime { private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } private Runtime() { } }
-
注意事项
- 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
4. 建造者模式
-
应用案例
- 需要建房子:这一过程为打桩、砌墙、封顶
- 房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的
- 请编写程序,完成需求
/* 1. 传统方式的优缺点 1. 优点是比较好理解,简单易操作 2. 缺点是设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好. 也就是说,这种设 计方案,把产品(即:房子) 和创建产品的过程(即:建房子流程)封装在一起,耦合性增强了 2. 解决方案:将产品和产品建造过程进行解耦 => 建造者模式 */ public abstract class AbstractHouse { //打地基 public abstract void buildBasic(); //砌墙 public abstract void buildWalls(); //封顶 public abstract void buildRoof(); public void build(){ buildBasic();; buildWalls(); buildRoof(); } } public class CommonHouse extends AbstractHouse{ @Override public void buildBasic() { System.out.println("给普通房子打地基"); } @Override public void buildWalls() { System.out.println("给普通房子砌墙"); } @Override public void buildRoof() { System.out.println("给普通房子封顶"); } } public static void main(String[] args) { CommonHouse house = new CommonHouse(); house.build(); } /* 建造者模式 1. 建造者模式又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类 别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象 2. 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它 们,用户不需要知道内部的具体构建细节 建造者模式的四个角色 1. Product(产品角色): 一个具体的产品对象 2. Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类 3. ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件 4. Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主 要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程 */ //产品->Product public class House { private String basic; private String wall; private String roof; public String getBasic() { return basic; } public void setBasic(String basic) { this.basic = basic; } public String getWall() { return wall; } public void setWall(String wall) { this.wall = wall; } public String getRoof() { return roof; } public void setRoof(String roof) { this.roof = roof; } } //抽象的建造者->Builder public abstract class HouseBuilder { protected House house = new House(); //建造的流程 public abstract void buildBasic(); public abstract void buildWalls(); public abstract void buildRoof(); //将建造好的房子返回 public House buildHouse(){ return house; } } //具体的建造者->ConcreteBuilder public class CommonHouse extends HouseBuilder{ @Override public void buildBasic() { System.out.println("普通房子打地基5米"); } @Override public void buildWalls() { System.out.println("普通房子砌墙10米"); } @Override public void buildRoof() { System.out.println("普通房子封顶20米"); } } public class HighBuilding extends HouseBuilder{ @Override public void buildBasic() { System.out.println("高楼打地基100米"); } @Override public void buildWalls() { System.out.println("高楼砌墙20米"); } @Override public void buildRoof() { System.out.println("高楼封顶30米"); } } //指挥者->Director public class HouseDirector { private HouseBuilder builder; public HouseDirector(HouseBuilder builder){ this.builder = builder; } //指挥具体的建造流程(不同的建造者有不同的建造流程) public House constructHouse(){ builder.buildBasic(); builder.buildWalls(); builder.buildRoof(); House house = builder.buildHouse(); return house; } } public static void main(String[] args) { //盖普通房子 CommonHouse commonHouse = new CommonHouse(); //创建指挥者 HouseDirector director = new HouseDirector(commonHouse); //完成盖房子,提交产品 House house = director.constructHouse(); }
-
建造者模式在StringBuilder中的应用
/* 源码中建造者模式角色分析 1. Appendable接口定义了多个append方法(抽象方法), 即Appendable为抽象建造者, 定义了抽象方法 2. AbstractStringBuilder实现了Appendable接口方法,这里的AbstractStringBuilder已经是建 造者,只是不能实例化 3. StringBuilder即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由 AbstractStringBuilder完成, 而StringBuilder继承了AbstractStringBuilder */ public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{ public StringBuilder() { super(16); //默认初始化容量为16 } public StringBuilder(int capacity) { super(capacity); } AbstractStringBuilder append(AbstractStringBuilder asb) { if (asb == null) return appendNull(); int len = asb.length(); ensureCapacityInternal(count + len); asb.getChars(0, len, value, count); count += len; return this; } } abstract class AbstractStringBuilder implements Appendable, CharSequence { } public interface Appendable { }
-
注意事项
-
客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
-
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
-
可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
-
增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程, 系统扩展方便,符合 “开闭原则”
-
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
-
如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化, 导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
-
抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
-
5. 原型模式
-
应用案例
现在有一只羊tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊
/* 1. 传统方式的优缺点 1. 优点是比较好理解,简单易操作。 2. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低 3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活 2. 改进的思路分析 Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable 该接口表示该类能够复制且具有复制的能力 => 原型模式 */ public class Sheep { private String name; private int age; private String color; public Sheep() { } public Sheep(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override public String toString() { return "Sheep{" + "name='" + name + '\'' + ", age=" + age + ", color='" + color + '\'' + '}'; } } public static void main(String[] args) { Sheep sheep = new Sheep("tom",1,"白色"); Sheep sheep1 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor()); Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor()); //... System.out.println(sheep); System.out.println(sheep1); System.out.println(sheep2); } /* 原型模式 1. 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象 2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节 3. 工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone() 4. 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣 */ public class Sheep implements Cloneable{ private String name; private int age; private String color; public Sheep() { } public Sheep(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } //克隆该实例,使用默认的clone方法来完成 @Override protected Object clone() { Sheep sheep = null; try { sheep = (Sheep)super.clone(); }catch (Exception e){ System.out.println(e.getMessage()); } return sheep; } @Override public String toString() { return "Sheep{" + "name='" + name + '\'' + ", age=" + age + ", color='" + color + '\'' + '}'; } } public static void main(String[] args) { Sheep sheep = new Sheep("tom",1,"白色"); Sheep sheep1 = (Sheep) sheep.clone(); //克隆 Sheep sheep2 = (Sheep) sheep.clone(); System.out.println(sheep); System.out.println(sheep1); System.out.println(sheep2); } /* 浅拷贝和深拷贝 1. 浅拷贝的介绍 1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份 给新的对象 2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅 拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际 上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另 一个对象的该成员变量值 3. 前面我们的克隆羊就是浅拷贝 4. 浅拷贝是使用默认的 clone()方法来实现 --> sheep = (Sheep) super.clone(); 2. 深拷贝的介绍 1. 复制对象的所有基本数据类型的成员变量值 2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象, 直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝 3. 深拷贝实现方式1:重写clone方法来实现深拷贝(必须实现Serializable、Cloneable接口) 4. 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐) */ public class DeepCloneableTarget implements Serializable, Cloneable { private static final long serialVersionUID = 1L; private String cloneName; private String cloneClass; public DeepCloneableTarget(String cloneName, String cloneClass) { this.cloneName = cloneName; this.cloneClass = cloneClass; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class DeepProtoType implements Serializable,Cloneable { public String name; public DeepCloneableTarget deepCloneableTarget; public DeepProtoType() { } //完成深拷贝的方式一: 重写clone方法 @Override protected Object clone() throws CloneNotSupportedException { DeepProtoType deep = null; //先完成对基本数据类型和String类型属性的克隆 deep = (DeepProtoType) super.clone(); //对引用数据类型的属性进行单独处理 deep.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone(); return deep; } //完成深拷贝的方式一: 通过对象的序列化实现深拷贝 public Object deepClone(){ //创建流对象 ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; try { //序列化 bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); //当前这个对象以对象流的方式输出 //反序列化 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); DeepProtoType copyObj = (DeepProtoType)ois.readObject(); return copyObj; } catch (Exception e) { e.printStackTrace(); } finally { try { bos.close(); oos.close(); bis.close(); ois.close(); } catch (Exception e2) { System.out.println(e2.getMessage()); } } return null; } } public static void main(String[] args) throws CloneNotSupportedException { DeepProtoType deep = new DeepProtoType(); deep.name = "xinxin"; deep.deepCloneableTarget = new DeepCloneableTarget("昕昕","520"); //通过方式一进行深拷贝 //DeepProtoType deep1 = (DeepProtoType) deep.clone(); //System.out.println(deep.name + "\t" + deep.hashCode()); //System.out.println(deep1.name + "\t" + deep1.hashCode()); //通过方式二进行深拷贝 DeepProtoType deep1 = (DeepProtoType) deep.deepClone(); System.out.println(deep.name + "\t" + deep.hashCode()); System.out.println(deep1.name + "\t" + deep1.hashCode()); }
-
注意事项
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
- 在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则
结构型模式
6. 适配器模式
-
基本介绍
- 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器
- 适配器模式属于结构型模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
-
工作原理
- 将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口进行交互
-
类适配器
- 基本介绍:Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配
- 基本说明:以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src (即被适配者),我们的目dst(即目标)是5V直流电
/* 类适配器的注意事项: 1. Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要求dst必须是接口,有 一定局限性 2. src类的方法在Adapter中都会暴露出来,也增加了使用的成本 3. 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了 */ //被适配的类 public class Voltage220V { public int output220V(){ int src = 220; System.out.println("电压为: " + src + "V"); return src; } } //适配接口 public interface Voltage5V { public int output5V(); } //适配器 public class VoltageAdapter extends Voltage220V implements Voltage5V{ @Override public int output5V() { //获取220V的电压 int src = output220V(); int dst = src / 44; //转为5V return dst; } } public class Phone { //充电 public void charging(Voltage5V v){ if(v.output5V() == 5){ System.out.println("电压为5V,可以充电..."); }else if(v.output5V() > 5){ System.out.println("电压大于5V,无法充电..."); } } } public static void main(String[] args) { System.out.println("=========类适配器模式=========="); Phone phone = new Phone(); phone.charging(new VoltageAdapter()); }
-
对象适配器
/* 对象适配器模式介绍 1. 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例, 以解决兼容性的问题。 即:持有src类,实现dst类接口,完成src->dst的适配 2. 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系 3. 对象适配器模式是适配器模式常用的一种 对象适配器模式注意事项和细节 1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继 承, 所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口 2. 使用成本更低,更灵活 */ //被适配的类 public class Voltage220V { public int output220V(){ int src = 220; System.out.println("电压为: " + src + "V"); return src; } } //适配接口 public interface Voltage5V { public int output5V(); } //适配器 public class VoltageAdapter implements Voltage5V { private Voltage220V v; //通过构造器传入一个被适配的类的实例 public VoltageAdapter(Voltage220V v){ this.v = v; } @Override public int output5V() { int dst = 0; if(v != null){ dst = v.output220V() / 44; System.out.println("适配后的电压为: " + dst + "V"); } return dst; } } public class Phone { //充电 public void charging(Voltage5V v){ if(v.output5V() == 5){ System.out.println("电压为5V,可以充电..."); }else if(v.output5V() > 5){ System.out.println("电压大于5V,无法充电..."); } } } public static void main(String[] args) { System.out.println("=========对象适配器模式=========="); Phone phone = new Phone(); phone.charging(new VoltageAdapter(new Voltage220V())); }
-
接口适配器
/* 接口适配器模式介绍 1. 适配器模式又叫缺省适配器模式 2. 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认 实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求 3. 适用于一个接口不想使用其所有的方法的情况 */ public interface Interface { public void m1(); public void m2(); public void m3(); public void m4(); } public abstract class AbsAdapter implements Interface{ //将Interface中的方法全部进行默认实现,将具体的实现交给调用者 @Override public void m1() { } @Override public void m2() { } @Override public void m3() { } @Override public void m4() { } } public static void main(String[] args) { AbsAdapter adapter = new AbsAdapter(){ //在这里我们可以有选择的覆盖方法,进行实现 @Override public void m1() { System.out.println("m1方法..."); } @Override public void m2() { System.out.println("m2方法..."); } }; adapter.m1(); adapter.m2(); }
-
注意事项
- 三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的
- 类适配器:以类给到,在Adapter里,就是将src当做类,继承
- 对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有
- 接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现
- Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作
- 实际开发中,实现起来不拘泥于我们讲解的三种经典形式
7. 装饰器模式
-
星巴克咖啡订单项目(咖啡馆)
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式 咖啡)、Decaf(无因咖啡)
- 调料:Milk(牛奶)、Soy(豆浆)、Chocolate(巧克力)
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用OO(面向对象)的方法来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合
/* 方案一: 传统方案(性能和扩展极差) 1. Drink是一个抽象类,表示饮料 2. description就是对咖啡的描述, 比如咖啡的名字 3. cost()方法就是计算费用,Drink类中做成一个抽象方法 4. Decaf 就是单品咖啡, 继承Drink, 并实现cost方法 5. Espress && Milk 就是单品咖啡+调料, 这个组合很多 问题分析:这样设计,会有很多类当我们增加一个单品咖啡,或者一个新的调料,类的数量会倍增,就会出现类爆炸 方案二: 对方案一进行优化 1. 方案一因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会 造成类数量过多,从而提高项目的维护性 2. 说明: milk,soy,chocolate可以设计为方法,返回值为Boolean,表示是否要添加相应的调料 问题分析: 1. 方案二可以控制类的数量,不至于造成很多的类 2. 在增加或者删除调料种类时,代码的维护量很大 3. 考虑到用户可以添加多份调料时,可以将hasMilk()方法返回一个对应int 4. 考虑使用装饰者模式 方案三: 使用装饰者模式 定义: 1. 动态的将新功能附加到对象上,在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则 2. 这里提到的动态的将新功能附加到对象和开闭原则,在后面的应用实例上会以代码的形式体现 装饰者模式下的订单:2份巧克力 + 一份牛奶的LongBlack 1. Milk包含了LongBlack 2. 一份Chocolate包含了(Milk+LongBlack) 3. 一份Chocolate包含了(Chocolate+Milk+LongBlack) 4. 这样不管是什么形式的单品咖啡 + 调料组合,通过递归方式可以方便的组合和维护 */ public abstract class Drink { private String des; //描述 private float price; public String getDes() { return des; } public void setDes(String des) { this.des = des; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } //计算费用,由子类实现 public abstract float cost(); } public class Coffee extends Drink{ @Override public float cost() { return super.getPrice(); } } public class Espresso extends Coffee{ public Espresso(){ setDes("意大利咖啡"); setPrice(6.0f); } } public class LongBlack extends Coffee{ public LongBlack(){ setDes("LongBlack"); setPrice(8.0f); } } public class ShortBlack extends Coffee{ public ShortBlack(){ setDes("ShortBlack"); setPrice(10.0f); } } //装饰者 public class Decorator extends Drink{ private Drink drink; public Decorator(Drink drink){ this.drink = drink; } @Override public float cost() { return super.getPrice() + drink.cost(); //自己的价格 + 咖啡的价格 } @Override public String getDes() { return super.getDes() + " " + super.getPrice() + " && " + drink.getDes(); } } public class Chocolate extends Decorator{ public Chocolate(Drink drink) { super(drink); setDes("巧克力"); setPrice(3.0f); } } public class Milk extends Decorator{ public Milk(Drink drink) { super(drink); setDes("牛奶"); setPrice(2.0f); } } public class Soy extends Decorator{ public Soy(Drink drink) { super(drink); setDes("豆浆"); setPrice(1.5f); } } public static void main(String[] args) { //装饰者模式下的订单:2份巧克力 + 一份牛奶的LongBlack //先点一份LongBlack Drink drink = new LongBlack(); System.out.println(drink.getDes() + ": " + drink.cost()); //加入一份牛奶 drink = new Milk(drink); System.out.println(drink.getDes() + ": " + drink.cost()); //加入一份巧克力 drink = new Chocolate(drink); System.out.println(drink.getDes() + ": " + drink.cost()); //再加入一份巧克力 drink = new Chocolate(drink); System.out.println(drink.getDes() + ": " + drink.cost()); }
8. 代理模式
-
代理模式的基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种:静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 它是属于动态代理的范畴)
-
静态代理
/* 应用实例的具体要求: 1. 定义一个接口:ITeacherDao 2. 目标对象TeacherDAO实现接口ITeacherDAO 3. 使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO 4. 调用的时候通过调用代理对象的方法来调用目标对象. 5. 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法 静态代理模式的基本介绍: 静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继 承相同父类 静态代理优缺点: 1. 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展 2. 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类 3. 一旦接口增加方法,目标对象与代理对象都要维护 */ public interface ITeacherDao { public void teach(); } public class TeacherDao implements ITeacherDao{ @Override public void teach() { System.out.println("老师正在授课中..."); } } //静态代理对象 public class TeacherDaoProxy implements ITeacherDao{ private ITeacherDao target; //目标对象,通过接口来聚合 public TeacherDaoProxy(ITeacherDao target){ this.target = target; } @Override public void teach() { System.out.println("静态代理开始..."); target.teach(); System.out.println("静态代理完成..."); } } public static void main(String[] args) { //创建目标对象 TeacherDao teacherDao = new TeacherDao(); //创建代理对象,同时将目标对象传给代理对象 TeacherDaoProxy proxy = new TeacherDaoProxy(teacherDao); //通过代理对象,调用被代理对象的方法,即: 执行的是代理对象的方法,代理对象再去调用目标对象的方法 proxy.teach(); }
-
动态代理
/* 动态代理模式的基本介绍 1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理 2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象 3. 动态代理也叫做:JDK代理、接口代理 JDK中生成代理对象的API 1. 代理类所在包:java.lang.reflect.Proxy 2. JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是: static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) */ public interface ITeacherDao { public void teach(); } public class TeacherDao implements ITeacherDao{ @Override public void teach() { System.out.println("老师在授课..."); } } public class ProxyFactory { //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target = target; } //根据目标对象返回一个代理对象 public Object getProxyInstance(){ /* public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取类加载器的方法是固定的 2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型 3. InvocationHandler h: 事件处理,执行目标对象的方法时会触发事件处理器方法,会把当前执行的目标对象 方法作为参数传入 */ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("动态代理模式开始..."); Object invoke = method.invoke(target, args); System.out.println("动态代理模式完成..."); return invoke; } }); } } public static void main(String[] args) { //创建目标对象 ITeacherDao teacherDao = new TeacherDao(); //给目标对象创建动态代理对象 ProxyFactory factory = new ProxyFactory(teacherDao); //内存中动态生成的代理对象 ITeacherDao instance = (ITeacherDao) factory.getProxyInstance(); System.out.println(instance.getClass()); instance.teach(); }
-
Cglib代理
/* Cglib代理模式的基本介绍: 1. 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没 有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理 2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将 Cglib代理归属到动态代理 3. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多 AOP的框架使用,例如Spring AOP,实现方法拦截 4. 在AOP编程中如何选择代理模式: 1. 目标对象需要实现接口,用JDK代理 2. 目标对象不需要实现接口,用Cglib代理 5. Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类 Cglib代理模式实现步骤: 1. 需要引入cglib的jar文件 asm.jar、asm-commons.jar、asm-tree.jar、cglib.jar 2. 在内存中动态构建子类,注意代理的类不能为final,否则报错 java.lang.IllegalArgumentException 3. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法 */ public class TeacherDao { public void teach(){ System.out.println("老师正在授课中..."); } } //需要实现MethodInterceptor接口 public class ProxyFactory implements MethodInterceptor { //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target = target; } //返回一个代理对象 public Object getProxyInstance(){ //1. 创建一个工具类 Enhancer enhancer = new Enhancer(); //2. 设置父类 enhancer.setSuperclass(target.getClass()); //3. 设置回调函数(调用自己) enhancer.setCallback(this); //4. 创建子类对象,即代理对象 return enhancer.create(); } //重写intercept方法,这里会调用目标对象的方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib代理模式开始..."); method.invoke(target,objects); System.out.println("cglib代理模式完成..."); return null; } } public static void main(String[] args) { TeacherDao target = new TeacherDao(); TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance(); proxyInstance.teach(); }
-
代理模式的变体
- 防火墙代理:内网通过代理穿透防火墙,实现对公网的访问
- 缓存代理:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源, 再到公网或者数据库取,然后缓存
- 远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作
9. 外观模式
-
影院管理项目
- DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为
- 直接用遥控器:统筹各设备开关
- 开爆米花机
- 放下屏幕
- 开投影仪
- 开音响
- 开DVD,选dvd
- 去拿爆米花
- 调暗灯光
- 播放
- 观影结束后,关闭各种设备
/* ClientTest { public static void main() { //1. 创建相关的对象 //2. 调用创建的各个对象的一系列方法 // 3. 调用DVDPlayer对象的play方法 } } 传统方式解决影院管理问题分析: 1. 在ClientTest的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成 调用过程混乱,没有清晰的过程 2. 不利于在ClientTest中,去维护对子系统的操作 3. 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方 法ready, play, pause, end ),用来访问子系统中的一群接口 4. 也就是说 就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个 接口发生调用,而无需关心这个子系统的内部细节 => 外观模式 外观模式基本介绍: 1. 外观模式也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层 接口,这个接口使得这一子系统更加容易使用 2. 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用, 而无需关心这个子系统的内部细节 */ public class DVDPlayer { //使用单例模式的饿汉式 private static DVDPlayer player = new DVDPlayer(); public static DVDPlayer getInstance(){ return player; } public void on(){ System.out.println("dvd on..."); } public void off(){ System.out.println("dvd off..."); } public void play(){ System.out.println("dvd playing..."); } public void pause(){ System.out.println("dvd pause..."); } } public class Popcorn { private static Popcorn popcorn = new Popcorn(); public static Popcorn getInstance(){ return popcorn; } public void on(){ System.out.println("popcorn on..."); } public void off(){ System.out.println("popcorn off..."); } public void play(){ System.out.println("popcorn playing..."); } } public class Projector { private static Projector projector = new Projector(); public static Projector getInstance(){ return projector; } public void on(){ System.out.println("projector on..."); } public void off(){ System.out.println("projector off..."); } public void focus(){ System.out.println("projector focus..."); } } public class Screen { private static Screen screen = new Screen(); public static Screen getInstance(){ return screen; } public void up(){ System.out.println("screen up..."); } public void down(){ System.out.println("screen down..."); } } public class Stereo { private static Stereo stereo = new Stereo(); public static Stereo getInstance(){ return stereo; } public void up(){ System.out.println("stereo up..."); } public void down(){ System.out.println("stereo down..."); } } public class TheaterLight { private static TheaterLight light = new TheaterLight(); public static TheaterLight getInstance(){ return light; } public void on(){ System.out.println("light on..."); } public void off(){ System.out.println("light off..."); } } //统一接口 public class HomeTheater { //定义各个子系统对象 private TheaterLight light = TheaterLight.getInstance(); private Popcorn popcorn = Popcorn.getInstance(); private Stereo stereo = Stereo.getInstance(); private Projector projector = Projector.getInstance(); private Screen screen = Screen.getInstance(); private DVDPlayer player = DVDPlayer.getInstance(); //将操作分成四步 public void ready(){ popcorn.on(); popcorn.play(); screen.down(); projector.on(); stereo.up(); player.on(); light.off(); } public void play(){ player.play(); } public void pause(){ player.pause(); } public void end(){ popcorn.off(); screen.up(); projector.off(); stereo.down(); player.off(); light.on(); } } public static void main(String[] args) { HomeTheater theater = new HomeTheater(); theater.ready(); theater.play(); theater.pause(); theater.end(); }
-
注意事项
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用外观模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时 可以考虑为新系统开发一个外观类,来提供遗留系统的比较清晰简单的接口, 让新系统与外观类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的
10. 桥接模式
-
手机操作问题
- 对不同手机类型的 不同品牌实现操作编程(比如: 开机、关机、上网,打电话等)
- 传统方案解决手机操作问题分析
- 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
- 违反了单一职责原则,当我们增加手机样式时要同时增加所有品牌的手机,这样增加了代码维护成本
- 解决方案-使用桥接模式
/* 桥接模式: 1. 桥接模式是指将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变,是一种结构型设计模式 2. 桥接模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主 要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性 以及应对他们的功能扩展 */ //品牌 public interface Brand { public void open(); public void close(); public void call(); } public class XiaoMi implements Brand{ @Override public void open() { System.out.println("小米手机开机..."); } @Override public void close() { System.out.println("小米手机关机..."); } @Override public void call() { System.out.println("小米手机打电话..."); } } public class Vivo implements Brand{ @Override public void open() { System.out.println("vivo手机开机..."); } @Override public void close() { System.out.println("vivo手机关机..."); } @Override public void call() { System.out.println("vivo手机打电话..."); } } public abstract class Phone { //组合品牌 private Brand brand; public Phone(Brand brand){ this.brand = brand; } protected void open(){ brand.open(); } protected void close(){ brand.close(); } protected void call(){ brand.call(); } } //折叠式手机类 public class FoldedPhone extends Phone{ public FoldedPhone(Brand brand) { super(brand); } public void open(){ super.open(); System.out.println("折叠样式手机"); } public void close(){ super.close(); System.out.println("折叠样式手机"); } public void call(){ super.call(); System.out.println("折叠样式手机"); } } public class UpRightPhone extends Phone{ public UpRightPhone(Brand brand) { super(brand); } public void open(){ super.open(); System.out.println("直立样式手机"); } public void close(){ super.close(); System.out.println("直立样式手机"); } public void call(){ super.call(); System.out.println("直立样式手机"); } } public static void main(String[] args) { FoldedPhone phone = new FoldedPhone(new XiaoMi()); phone.open(); phone.call(); phone.close(); UpRightPhone phone1 = new UpRightPhone(new Vivo()); phone1.open(); phone1.call(); phone1.close(); }
-
注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层, 要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景
-
桥接模式其它应用场景
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
- 常见的应用场景
- JDBC驱动程序
- Jdbc的Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有 MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类
- 银行转账系统
- 转账分类: 网上转账,柜台转账,AMT转账
- 转账用户类型:普通用户,银卡用户,金卡用户
- 消息管理
- 消息类型:即时消息,延时消息
- 消息分类:手机短信,邮件消息,QQ消息…
- JDBC驱动程序
11. 组合模式
-
学校院系展示需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系 组成,一个学校有多个学院,一个学院有多个系
-
传统方案解决学校院系展示存在的问题
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如 对学院、系的添加,删除,遍历等
- 解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作 => 组合模式
/* 组合模式基本介绍: 1. 组合模式又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的 层次关系 2. 组合模式依据树形结构来组合对象,用来表示部分以及整体层次 3. 这种类型的设计模式属于结构型模式 4. 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对 象以及组合对象 组合模式解决的问题 组合模式解决的问题: 当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操 作时,它能够提供一致的方式,而不用考虑它是普通结点还是叶子结点 */ public abstract class OrganizationComponent { private String name; private String des; public OrganizationComponent(String name, String des) { this.name = name; this.des = des; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDes() { return des; } public void setDes(String des) { this.des = des; } //默认实现,具体实现交给子类 protected void add(OrganizationComponent component){ } protected void remove(OrganizationComponent component){ } public abstract void print(); } public class University extends OrganizationComponent{ private List<OrganizationComponent> list = new ArrayList<>(); public University(String name, String des) { super(name, des); } @Override public void print() { System.out.println("------------" + getName() + "--------------"); for(OrganizationComponent component : list){ component.print(); } } @Override protected void add(OrganizationComponent component) { list.add(component); } @Override protected void remove(OrganizationComponent component) { list.remove(component); } } public class College extends OrganizationComponent{ private List<OrganizationComponent> list = new ArrayList<>(); public College(String name, String des) { super(name, des); } @Override public void print() { System.out.println("------------" + getName() + "--------------"); for(OrganizationComponent component : list){ component.print(); } } @Override protected void add(OrganizationComponent component) { list.add(component); } @Override protected void remove(OrganizationComponent component) { list.remove(component); } } public class Department extends OrganizationComponent{ public Department(String name, String des) { super(name, des); } @Override public void print() { System.out.println(getDes() + " " + getName()); } } public static void main(String[] args) { OrganizationComponent university = new University("清华大学", "中国第一学府"); OrganizationComponent computerCollege = new College("计算机学院", "专注于计算机人才的培养"); OrganizationComponent infoCollege = new College("信息工程学院", "专注于信息型人才的培养"); computerCollege.add(new Department("计算机科学与技术","计算机")); computerCollege.add(new Department("网络工程","网络")); infoCollege.add(new Department("电子信息工程","电子信息")); infoCollege.add(new Department("通信工程","通信")); university.add(computerCollege); university.add(infoCollege); university.print(); }
-
注意事项
- 简化客户端操作, 客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
- 具有较强的扩展性, 当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动
- 方便创建出复杂的层次结构, 客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
12. 享元模式
-
展示网站项目
- 小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同
- 有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
- 传统方案解决网站展现项目
- 直接复制粘贴一份,然后根据客户不同要求,进行定制修改
- 给每个网站租用一个空间
/* 1. 传统方案解决网站展现项目问题分析 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网 站的实例对象很多,造成服务器的资源浪费 2. 解决思路: 1. 整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以 达成共享,减少服务器资源 2. 对于代码来说,由于是一份实例,维护和扩展都更加容易 3. 上面的解决思路就可以使用享元模式来解决 3. 享元模式 1. 享元模式也叫蝇量模式: 运用共享技术有效地支持大量细粒度的对象 2. 常用于系统底层开发,解决系统的性能问题,像数据库连接池,里面都是创建好的连接对象,在这些连接对 象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个 3. 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时,不需总是创建新 对象,可以从缓冲池里拿,这样可以降低系统内存,同时提高效率 4. 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应 用,享元模式是池技术的重要实现方式 4. 内部状态和外部状态 比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋 子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位 置是变化的,所以棋子坐标就是棋子的外部状态 1. 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态 2. 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变 3. 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态 4. 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题 */ public abstract class WebSite { public abstract void use(User user); } //内部状态 public class ConcrateWebSite extends WebSite{ private String type; //网站的类型(发布形式) public ConcrateWebSite(String type){ this.type = type; } @Override public void use(User user) { System.out.println("网站的发布形式为: " + type + ",使用者为: " + user.getName()); } } //工厂类 public class WebSiteFactory { //map -> 充当池的作用 private HashMap<String,ConcrateWebSite> map = new HashMap<>(); //根据网站的类型,返回一个网站,如果没有就创建一个并放入池中 public WebSite getWebSite(String type){ if(!map.containsKey(type)){ map.put(type,new ConcrateWebSite(type)); } return (WebSite)map.get(type); } //获取网站分类的总数(即池中有多少个网站类型) public int getWebSiteSize(){ return map.size(); } } //外部状态 public class User { private String name; public User(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public static void main(String[] args) { WebSiteFactory factory = new WebSiteFactory(); WebSite webSite1 = factory.getWebSite("新闻"); webSite1.use(new User("昕昕")); WebSite webSite2 = factory.getWebSite("博客"); WebSite webSite3 = factory.getWebSite("博客"); WebSite webSite4 = factory.getWebSite("博客"); webSite2.use(new User("xinxin")); webSite3.use(new User("xinxin")); webSite4.use(new User("xinxin")); System.out.println("网站类型个数为: " + factory.getWebSiteSize()); }
- 小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同
-
注意事项
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时, 我们就可以考虑选用享元模式
- 用唯一标识码判断,如果内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池
行为型模式
13. 策略模式
-
鸭子问题
- 有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如叫、飞行等)
- 显示鸭子的信息
-
传统的方式实现的问题分析和解决方案
- 其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的
- 上面说的1的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应
- 为了改进1的问题,我们可以通过覆盖fly方法来解决 => 覆盖解决
- 问题又来了,如果我们有一个玩具鸭子ToyDuck, 这样就需要ToyDuck去覆盖Duck 的所有实现的方法
- 解决思路:策略模式
//传统方案 public abstract class Duck { public Duck() { } public abstract void display(); //显示鸭子信息 public void quack() { System.out.println("鸭子嘎嘎叫~~"); } public void swim() { System.out.println("鸭子会游泳~~"); } public void fly() { System.out.println("鸭子会飞翔~~~"); } } public class WildDuck extends Duck{ @Override public void display() { System.out.println(" 这是野鸭 "); } } public class ToyDuck extends Duck{ @Override public void display() { System.out.println("玩具鸭"); } //需要重写父类的所有方法 public void quack() { System.out.println("玩具鸭不能叫~~"); } public void swim() { System.out.println("玩具鸭不会游泳~~"); } public void fly() { System.out.println("玩具鸭不会飞翔~~~"); } } public class PekingDuck extends Duck{ @Override public void display() { System.out.println("~~北京鸭~~~"); } //因为北京鸭不能飞翔,因此需要重写fly @Override public void fly() { System.out.println("北京鸭不能飞翔"); } } public static void main(String[] args) { PekingDuck duck = new PekingDuck(); duck.display(); duck.quack(); duck.fly(); }
/* 策略模式基本介绍: 1. 策略模式中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算 法的客户 2. 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是 具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略) */ public interface FlyBehavior { void fly(); // 子类具体实现 } public class GoodFlyBehavior implements FlyBehavior{ @Override public void fly() { System.out.println(" 飞翔技术高超 ~~~"); } } public class NoFlyBehavior implements FlyBehavior{ @Override public void fly() { System.out.println(" 不会飞翔 "); } } public class BadFlyBehavior implements FlyBehavior{ @Override public void fly() { System.out.println(" 飞翔技术一般 "); } } public interface QuackBehavior { void quack(); } public abstract class Duck { //属性,策略接口 private FlyBehavior flyBehavior; //其它属性<->策略接口 private QuackBehavior quackBehavior; public Duck() { } public abstract void display(); //显示鸭子信息 public void quack() { System.out.println("鸭子嘎嘎叫~~"); } public void swim() { System.out.println("鸭子会游泳~~"); } public void fly() { if(flyBehavior != null) { flyBehavior.fly(); } } public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } } public class ToyDuck extends Duck{ public ToyDuck() { flyBehavior = new NoFlyBehavior(); } @Override public void display() { System.out.println("玩具鸭"); } public void quack() { System.out.println("玩具鸭不能叫~~"); } public void swim() { System.out.println("玩具鸭不会游泳~~"); } } public class WildDuck extends Duck{ public WildDuck() { flyBehavior = new GoodFlyBehavior(); } @Override public void display() { System.out.println(" 这是野鸭 "); } } public class PekingDuck extends Duck{ //假如北京鸭可以飞翔,但是飞翔技术一般 public PekingDuck() { flyBehavior = new BadFlyBehavior(); } @Override public void display() { System.out.println("~~北京鸭~~~"); } } public static void main(String[] args) { WildDuck wildDuck = new WildDuck(); wildDuck.fly(); ToyDuck toyDuck = new ToyDuck(); toyDuck.fly(); PekingDuck pekingDuck = new PekingDuck(); pekingDuck.fly(); //动态改变某个对象的行为,北京鸭不能飞 pekingDuck.setFlyBehavior(new NoFlyBehavior()); System.out.println("北京鸭的实际飞翔能力"); pekingDuck.fly(); }
-
注意事项
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合,少用继承;用行为类组合,而不是行为的继承,更有弹性
- 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)
- 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
14. 模板方法模式
-
豆浆制作问题
- 制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
- 请使用 模板方法模式完成 (说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式 )
/* 模板方法模式基本介绍: 1. 模板方法模式又叫模板模式,在一个抽象类公开定义了执行它的方法的模板,它的子类可以按需要重写方 法实现,但调用将以抽象类中定义的方式进行 2. 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变 一个算法的结构,就可以重定义该算法的某些特定步骤 3. 这种类型的设计模式属于行为型模式 */ public abstract class SoyaMilk { //模板方法,可以设置为final,不让子类覆盖 public final void make(){ select(); add(); soak(); beat(); } public void select(){ } public abstract void add(); public void soak(){ System.out.println("第三步: 开始浸泡...需要3个小时"); } public void beat(){ System.out.println("第四步: 打碎..."); } } public class RedBeanSoyaMilk extends SoyaMilk{ @Override public void add() { System.out.println("加入红豆..."); } } public class PeanutSoyaMilk extends SoyaMilk{ @Override public void add() { System.out.println("加入花生..."); } } public static void main(String[] args) { System.out.println("-------制作红豆豆浆-------"); RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk(); redBeanSoyaMilk.make(); System.out.println("-------制作花生豆浆-------"); PeanutSoyaMilk peanutSoyaMilk = new PeanutSoyaMilk(); peanutSoyaMilk.make(); } /* 模板方法模式的钩子方法: 1. 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它, 该方法称为“钩子方法” 2. 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对 前面的模板方法进行改造 */ public abstract class SoyaMilk { //模板方法,可以设置为final,不让子类覆盖 public final void make(){ select(); if(isAdd()){ add(); } soak(); beat(); } public void select(){ } public abstract void add(); public void soak(){ System.out.println("第三步: 开始浸泡...需要3个小时"); } public void beat(){ System.out.println("第四步: 打碎..."); } //钩子方法,是否添加配料 boolean isAdd(){ return true; } } public class PureSoyaMilk extends SoyaMilk{ @Override public void add() { } //重写钩子方法 @Override boolean isAdd() { return false; } } public static void main(String[] args) { System.out.println("-------制作纯豆浆-------"); PureSoyaMilk pureSoyaMilk = new PureSoyaMilk(); pureSoyaMilk.make(); }
-
注意事项
- 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用,父类的模板方法和已实现的某些步骤会被子类继承而直接使用
- 既统一了算法,也提供了很大的灵活性,父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加, 使得系统更加庞大
- 一般模板方法都加上final关键字, 防止子类重写模板方法
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一 系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模 式来处理
15. 观察者模式
-
天气预报项目需求
- 气象站可以将每天测量到的温度, 湿度, 气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)
- 需要设计开放型API,便于其他第三方也能接入气象站获取数据
- 提供温度、气压和湿度的接口
- 测量数据更新时,要能实时的通知给第三方
/* 普通方案 1. 通过对气象站项目的分析,我们可以初步设计出一个WeatherData类 2. 通过getXxx方法,可以让第三方接入,并得到相关信息 3. 当数据有更新时,气象站通过调用dataChange() 去更新数据,当第三方再次获取时,就能得到最新数 据,当然也可以推送 问题分析 1. 其他第三方接入气象站获取数据的问题 2. 无法在运行时动态的添加第三方 3. 违反ocp原则=>观察者模式 */ //显示当前天气情况(即气象站自己的网站) public class CurrentConditions { // 温度,气压,湿度 private float temperature; private float pressure; private float humidity; //更新天气情况,是由WeatherData来调用,这里使用推送模式 public void update(float temperature, float pressure, float humidity) { this.temperature = temperature; this.pressure = pressure; this.humidity = humidity; display(); } //显示 public void display() { System.out.println("***Today mTemperature: " + temperature + "***"); System.out.println("***Today mPressure: " + pressure + "***"); System.out.println("***Today mHumidity: " + humidity + "***"); } } /* 1. 包含最新的天气情况信息 2. 含有CurrentConditions对象 3. 当数据有更新时,就主动的调用CurrentConditions对象的update方法(含display), 这样它们(接入方)就能看到最新的信息 */ public class WeatherData { private float temperatrue; private float pressure; private float humidity; private CurrentConditions currentConditions; //加入新的第三方(即新的网站) public WeatherData(CurrentConditions currentConditions) { this.currentConditions = currentConditions; } public float getTemperature() { return temperatrue; } public float getPressure() { return pressure; } public float getHumidity() { return humidity; } public void dataChange() { //调用接入方的 update currentConditions.update(getTemperature(), getPressure(), getHumidity()); } //当数据有更新时,就调用setData public void setData(float temperature, float pressure, float humidity) { this.temperatrue = temperature; this.pressure = pressure; this.humidity = humidity; //调用dataChange, 将最新的信息推送给接入方currentConditions dataChange(); } } public static void main(String[] args) { //创建接入方currentConditions CurrentConditions currentConditions = new CurrentConditions(); //创建 WeatherData并将接入方currentConditions传递到WeatherData中 WeatherData weatherData = new WeatherData(currentConditions); //更新天气情况 weatherData.setData(30, 150, 40); //天气情况变化 System.out.println("============天气情况变化============="); weatherData.setData(40, 160, 20); }
/* 观察者模式: 对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知 Observer变化,比如这里的奶站是Subject,是1的一方,用户是Observer,是多的一方 原理: 观察者模式类似订牛奶业务 1. 奶站/气象局:Subject 1. Subject:登记注册、移除和通知 2. registerObserver 注册 3. removeObserver 移除 4. notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也 可能是实施推送,看具体需求定 2. 用户/第三方网站:Observer 1. Observer:接收输入 2. 对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer, Subject通知Observer变化,比如这里的奶站是Subject,是1的那一方,用户是Observer,是多的 那一方 观察者模式的好处: 1. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知 2. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修 改代码,遵守了ocp原则 */ public interface Subject { public void registerObserver(Observer observer); public void removeObserver(Observer observer); public void notifyObservers(); } //观察者接口 public interface Observer { public void update(float temperature,float pressure,float humidity); } //观察者 public class CurrentConditions implements Observer{ private float temperature; private float pressure; private float humidity; public void update(float temperature, float pressure, float humidity) { this.temperature = temperature; this.pressure = pressure; this.humidity = humidity; display(); } public void display() { System.out.println("***Today mTemperature: " + temperature + "***"); System.out.println("***Today mPressure: " + pressure + "***"); System.out.println("***Today mHumidity: " + humidity + "***"); } } /* 1. 包含最新的天气情况信息 2. 含有CurrentConditions集合ArrayList 3. 当数据有更新时,就主动的调用CurrentConditions集合ArrayList, 这样它们(接入方)就能看到最新的信息 */ public class WeatherData implements Subject{ private float temperatrue; private float pressure; private float humidity; private List<Observer> observers; public WeatherData() { observers = new ArrayList<>(); } public float getTemperature() { return temperatrue; } public float getPressure() { return pressure; } public float getHumidity() { return humidity; } public void dataChange() { //调用接入方的 update notifyObservers(); } //当数据有更新时,就调用setData public void setData(float temperature, float pressure, float humidity) { this.temperatrue = temperature; this.pressure = pressure; this.humidity = humidity; //调用dataChange, 将最新的信息推送给接入方currentConditions dataChange(); } @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { if(observers.contains(observer)){ observers.remove(observer); } } @Override public void notifyObservers() { for(Observer observer : observers){ observer.update(this.temperatrue,this.pressure,this.humidity); } } } public static void main(String[] args) { WeatherData data = new WeatherData(); //创建观察者 CurrentConditions conditions = new CurrentConditions(); //注册 data.registerObserver(conditions); System.out.println("--------------通知各个新闻中心----------------"); data.setData(10f,160.0f,20.f); }
16. 迭代器模式
-
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系
-
传统的方式的问题分析
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的遍历的操作
- 解决方案:=> 迭代器模式
/* 迭代器模式基本介绍: 1. 迭代器模式是常用的设计模式,属于行为型模式 2. 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户端 要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式 解决 3. 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底 层表示,即:不暴露其内部的结构 */ //系 public class Department { private String name; private String desc; public Department(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } } //计算机学院迭代器 public class ComputerCollegeIterator implements Iterator { //这里我们需要知道department是怎么存储的 private Department[] departments; private int index; //索引 public ComputerCollegeIterator(Department[] departments){ this.departments = departments; } @Override public boolean hasNext() { if(index >= departments.length || departments[index] == null){ return false; } return true; } @Override public Object next() { return departments[index++]; } @Override public void remove() { } } //电子信息学院迭代器 public class InfoCollegeIterator implements Iterator { private List<Department> departments; private int index; public InfoCollegeIterator(List<Department> departments){ this.departments = departments; } @Override public boolean hasNext() { if(index >= departments.size() - 1){ return false; } index++; return true; } @Override public Object next() { return departments.get(index); } @Override public void remove() { } } //学院接口 public interface College { public String getName(); //增加系 public void addDepartment(String name,String desc); //返回一个迭代器 public Iterator createIterator(); } public class ComputerCollege implements College{ private Department[] departments; private int count; //统计当前数组中的元素个数 public ComputerCollege(){ departments = new Department[5]; addDepartment("Java","从入门到精通"); addDepartment("python","从入门到入坟"); addDepartment("c++","从入门到放弃"); } @Override public String getName() { return "计算机学院"; } @Override public void addDepartment(String name, String desc) { Department department = new Department(name, desc); departments[count] = department; count++; } @Override public Iterator createIterator() { return new ComputerCollegeIterator(departments); } } public class InfoCollege implements College{ private List<Department> departments; public InfoCollege(){ departments = new LinkedList<>(); departments.add(new Department("信息安全","信息类")); departments.add(new Department("网络安全","网络类")); departments.add(new Department("信息工程","信息类")); } @Override public String getName() { return "信息工程学院"; } @Override public void addDepartment(String name, String desc) { Department department = new Department(name, desc); departments.add(department); } @Override public Iterator createIterator() { return new InfoCollegeIterator(departments); } } public class Output { //学院集合 private List<College> colleges; public Output(List<College> colleges) { this.colleges = colleges; } //先遍历所有学院 public void printCollege(){ Iterator<College> iterator = colleges.iterator(); while(iterator.hasNext()){ College college = iterator.next(); System.out.println("==============" + college.getName() + "=============="); printDepartment(college.createIterator()); } } //再遍历学院中的系 public void printDepartment(Iterator iterator){ while(iterator.hasNext()){ Department department = (Department)iterator.next(); System.out.println(department.getName() + "\t" + department.getDesc()); } } } public static void main(String[] args) { List<College> list = new ArrayList<>(); ComputerCollege computerCollege = new ComputerCollege(); InfoCollege infoCollege = new InfoCollege(); list.add(computerCollege); list.add(infoCollege); Output output = new Output(list); output.printCollege(); }
-
注意事项
- 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了
- 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成
- 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则), 在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器
- 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式
- 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
17. 责任链模式
-
学校OA系统的采购审批项目需求:
- 采购员采购教学器材
- 如果金额小于等于5000, 由教学主任审批 (0<=x<=5000)
- 如果金额 小于等于10000, 由院长审批 (5000<x<=10000)
- 如果金额 小于等于30000, 由副校长审批 (10000<x<=30000)
- 如果金额 超过30000以上,有校长审批 ( 30000<x)
-
传统方案解决OA系统审批问题分析
- 传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver (审批人)完成审批
- 传统方式的问题分析 : 客户端这里会使用到分支判断(比如switch) 来对不同的采购请求处理, 这样就存在如下问题 (1) 如果各个级别的人员审批金额发生变化,在客户端的也需要变化
- 客户端必须明确的知道有多少个审批级别和访问
- 这样对一个采购请求进行处理和Approver (审批人) 就存在强耦合关系,不利于代码的扩展和维护
- 解决方案 =》 职责链模式
/* 职责链模式基本介绍: 1. 职责链模式又叫责任链模式,为请求创建了一个接收者对象的链,这种模式可以对请求的发送者和接收者进 行解耦 2. 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相 同的请求传给下一个接收者,依此类推 3. 这种类型的设计模式属于行为型模式 职责链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止 职责链模式的角色及职责 1. Handler : 抽象的处理者, 定义了一个处理请求的接口, 同时含有另外的Handler 2. ConcreteHandlerA , B 是具体的处理者, 处理它自己负责的请求, 可以访问它的后继者(即下一个 处理者), 如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链 3. Request 含有很多属性,表示一个请求 */ //请求类 public class PurchaseRequest { private int type; //请求类型 private float price; //请求金额 private int id; //第几个请求 public PurchaseRequest(int type, float price, int id) { this.type = type; this.price = price; this.id = id; } public int getType() { return type; } public float getPrice() { return price; } public int getId() { return id; } } //抽象的处理者 public abstract class Approver { protected Approver approver; //下一个处理者 protected String name; // 名字 public Approver(String name) { this.name = name; } //下一个处理者 public void setApprover(Approver approver) { this.approver = approver; } //处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象 public abstract void processRequest(PurchaseRequest purchaseRequest); } public class DepartmentApprover extends Approver{ public DepartmentApprover(String name) { super(name); } @Override public void processRequest(PurchaseRequest purchaseRequest) { if(purchaseRequest.getPrice() <= 5000){ System.out.println("请求编号 " + purchaseRequest.getId() + " 被" + this.name + "处理"); }else{ approver.processRequest(purchaseRequest); //让下一个请求者处理 } } } public class CollegeApprover extends Approver{ public CollegeApprover(String name) { super(name); } @Override public void processRequest(PurchaseRequest purchaseRequest) { if(purchaseRequest.getPrice() >= 5000 && purchaseRequest.getPrice() <= 10000){ System.out.println("请求编号 " + purchaseRequest.getId() + " 被" + this.name + "处理"); }else{ approver.processRequest(purchaseRequest); //让下一个请求者处理 } } } public class ViceSchoolMasterApprover extends Approver{ public ViceSchoolMasterApprover(String name) { super(name); } @Override public void processRequest(PurchaseRequest purchaseRequest) { if(purchaseRequest.getPrice() >= 10000 && purchaseRequest.getPrice() <= 30000){ System.out.println("请求编号 " + purchaseRequest.getId() + " 被" + this.name + "处理"); }else{ approver.processRequest(purchaseRequest); } } } public class SchoolMasterApprover extends Approver{ public SchoolMasterApprover(String name) { super(name); } @Override public void processRequest(PurchaseRequest purchaseRequest) { if(purchaseRequest.getPrice() >= 30000){ System.out.println("请求编号 " + purchaseRequest.getId() + " 被" + this.name + "处理"); }else{ approver.processRequest(purchaseRequest); } } } public static void main(String[] args) { //创建一个请求 PurchaseRequest purchaseRequest = new PurchaseRequest(1, 32000, 1); //创建相关审批人 DepartmentApprover departmentApprover = new DepartmentApprover("主任"); CollegeApprover collegeApprover = new CollegeApprover("院长"); ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("副校长"); SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("校长"); //设置各个审批级别的下一个处理者(构成环形) departmentApprover.setApprover(collegeApprover); collegeApprover.setApprover(viceSchoolMasterApprover); viceSchoolMasterApprover.setApprover(schoolMasterApprover); schoolMasterApprover.setApprover(departmentApprover); //执行 departmentApprover.processRequest(purchaseRequest); }
-
注意事项
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值, 超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
- 调试不方便,采用了类似递归的方式,调试时逻辑可能比较复杂
- 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、JavaWeb中Tomcat对Encoding的处理、拦截器
18. 命令模式
-
智能生活项目
- 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作
- 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电
- 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时就可以考虑使用命令模式
- 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来
- 在这个例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品
/* 命令模式基本介绍: 1. 命令模式: 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不 知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进 行设计 2. 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦 3. 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命 令模式也支持可撤销的操作 4. 通俗易懂的理解: 将军发布命令,士兵去执行,其中有几个角色:将军(命令发布者)、士兵(命令的具 体执行者)、命令(连接将军和士兵),Invoker是调用者(将军),Receiver是被调用者(士兵) MyCommand是命令,实现了Command接口,持有接收对象 */ //命令接口 public interface Command { public void execute(); //执行某个命令 public void undo(); //撤销某个命令 } public class LightReceiver { public void on(){ System.out.println("电灯打开了..."); } public void off(){ System.out.println("电灯关闭了..."); } } public class LightOnCommand implements Command{ //聚合LightReceiver private LightReceiver receiver; public LightOnCommand(LightReceiver receiver){ this.receiver = receiver; } @Override public void execute() { receiver.off(); } @Override public void undo() { receiver.on(); } } public class LightOffCommand implements Command{ //聚合LightReceiver private LightReceiver receiver; public LightOffCommand(LightReceiver receiver){ this.receiver = receiver; } @Override public void execute() { receiver.on(); } @Override public void undo() { receiver.off(); } } //空命令,用于初始化每个按钮,当调用空命令时,对象什么都不用做 public class NoCommand implements Command{ @Override public void execute() { } @Override public void undo() { } } //遥控器 public class RemoteController { private Command[] onCommands; private Command[] offCommands; private Command undoCommand; //撤销 public RemoteController(){ onCommands = new Command[5]; offCommands = new Command[5]; for(int i = 0;i < 5;i++){ onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } } //给指定按钮设置你需要的命令 public void setCommand(int no,Command onCommand,Command offCommand){ onCommands[no] = onCommand; offCommands[no] = offCommand; } //开按钮 public void onButton(int no){ onCommands[no].execute(); //记录这次的操作,用于撤销 undoCommand = onCommands[no]; } //关按钮 public void offButton(int no){ offCommands[no].execute(); //记录这次的操作,用于撤销 undoCommand = offCommands[no]; } //撤销按钮 public void undoButton(){ undoCommand.undo(); } } public static void main(String[] args) { //创建电灯的接收者 LightReceiver receiver = new LightReceiver(); //创建电灯的开关命令 LightOnCommand lightOnCommand = new LightOnCommand(receiver); LightOffCommand lightOffCommand = new LightOffCommand(receiver); //定义遥控器 RemoteController controller = new RemoteController(); //设置命令 controller.setCommand(0,lightOnCommand,lightOffCommand); System.out.println("--------按下电灯的开按钮---------"); controller.onButton(0); System.out.println("--------按下电灯的关按钮---------"); controller.offButton(0); System.out.println("--------按下电灯的撤销按钮---------"); controller.undoButton(); }
-
注意事项
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用
- 容易设计一个命令队列。只要把命令对象放到队列,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制
19. 备忘录模式
-
游戏角色状态恢复问题
- 游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
-
传统的方式的问题分析
- 一个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不利于管理,开销也很大
- 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
- 解决方案: => 备忘录模式
/* 基本介绍: 1. 备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后 就可将该对象恢复到原先保存的状态 2. 可以这样理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共 同意见的事情,以防忘记了,而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的 某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作 3. 备忘录模式属于行为型模式 备忘录模式的角色及职责: 1. originator : 对象(需要保存状态的对象) 2. Memento : 备忘录对象,负责保存好记录,即Originator内部状态 3. Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率 4. 说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要使用HashMap即可 */ public class Originator { private String state; //状态信息 public String getState() { return state; } public void setState(String state) { this.state = state; } //编写一个方法,可以保存一个状态对象 Memento public Memento saveStateMemento() { return new Memento(state); } //通过备忘录对象,恢复状态 public void getStateFromMemento(Memento memento) { state = memento.getState(); } } public class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } } public class Caretaker { //在List集合中会有很多的备忘录对象 private List<Memento> mementoList = new ArrayList<Memento>(); public void add(Memento memento) { mementoList.add(memento); } //获取到第index个Originator的备忘录对象(即保存状态) public Memento get(int index) { return mementoList.get(index); } } public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState("状态1 攻击力100"); caretaker.add(originator.saveStateMemento()); //保存了当前的状态 originator.setState("状态2 攻击力80"); caretaker.add(originator.saveStateMemento()); originator.setState("状态3 攻击力50"); caretaker.add(originator.saveStateMemento()); //恢复到状态1 System.out.println("当前状态为: " + originator.getState()); originator.getStateFromMemento(caretaker.get(0)); System.out.println("恢复到状态1后为: " + originator.getState()); }
//游戏角色恢复状态实例 public class Memento { //攻击力 private int vit; //防御力 private int def; public Memento(int vit, int def) { this.vit = vit; this.def = def; } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } } //守护者对象,保存游戏角色的状态 public class Caretaker { private Memento memento; //只保存一次状态 //对GameRole 保存多次状态 //private ArrayList<Memento> mementos; //对多个游戏角色保存多个状态 //private HashMap<String, ArrayList<Memento>> rolesMementos; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } } //游戏角色 public class GameRole { private int vit; private int def; public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } //创建Memento ,即根据当前的状态得到Memento public Memento createMemento() { return new Memento(vit, def); } //从备忘录对象恢复状态 public void recoverGameRole(Memento memento){ this.vit = memento.getVit(); this.def = memento.getDef(); } //显示当前游戏角色的状态 public void display(){ System.out.println("游戏角色的当前攻击力: " + this.vit + " 防御力: " + this.def); } } public static void main(String[] args) { //创建游戏角色 GameRole gameRole = new GameRole(); gameRole.setVit(100); gameRole.setDef(100); System.out.println("和Boss大战前的状态..."); gameRole.display(); //把当前的状态保存起来 Caretaker caretaker = new Caretaker(); caretaker.setMemento(gameRole.createMemento()); System.out.println("和Boss开始大战..."); gameRole.setVit(30); gameRole.setDef(30); gameRole.display(); System.out.println("大战后使用备忘录对象进行恢复..."); gameRole.recoverGameRole(caretaker.getMemento()); System.out.println("恢复后的状态..."); gameRole.display(); }
-
注意事项
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
- 适用的应用场景:1、后悔药 2、打游戏时的存档 3、Windows 里的 ctri + z 4、IE 中的后退 4、数据库的事务管理
- 为了节约内存,备忘录模式可以和原型模式配合使用
20. 状态模式
-
APP抽奖活动问题:请编写程序完成APP抽奖活动,具体要求如下:
-
假如每参加一次这个活动要扣除用户50积分,中奖概率是10%
-
奖品数量固定,抽完就不能继续抽奖了
-
活动有四个状态: 可以抽奖、 不能抽奖、发放奖品和奖品领完
/* 状态模式基本介绍: 1. 状态模式: 它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一 一对应的,状态之间可以相互转换 2. 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类 */ //状态抽象类 public abstract class State { //扣除50积分 public abstract void deductMoney(); //是否抽中奖品 public abstract boolean raffle(); //发放奖品 public abstract void dispensePrize(); } //抽奖活动 public class RaffleActivity { //state表示活动当前的状态 State state = null; //奖品数量 int count = 0; // 四个属性,表示四种状态 State noRafflleState = new NoRaffleState(this); State canRaffleState = new CanRaffleState(this); State dispenseState = new DispenseState(this); State dispensOutState = new DispenseOutState(this); //1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态) //2. 初始化奖品的数量 public RaffleActivity( int count) { this.state = getNoRafflleState(); this.count = count; } //扣分, 调用当前状态的 deductMoney public void debuctMoney(){ state.deductMoney(); } //抽奖 public void raffle(){ //如果当前的状态是抽奖成功 if(state.raffle()){ //领取奖品 state.dispensePrize(); } } public State getState() { return state; } public void setState(State state) { this.state = state; } //每领取一次奖品,count-- public int getCount() { int curCount = count; count--; return curCount; } public void setCount(int count) { this.count = count; } public State getNoRafflleState() { return noRafflleState; } public void setNoRafflleState(State noRafflleState) { this.noRafflleState = noRafflleState; } public State getCanRaffleState() { return canRaffleState; } public void setCanRaffleState(State canRaffleState) { this.canRaffleState = canRaffleState; } public State getDispenseState() { return dispenseState; } public void setDispenseState(State dispenseState) { this.dispenseState = dispenseState; } public State getDispensOutState() { return dispensOutState; } public void setDispensOutState(State dispensOutState) { this.dispensOutState = dispensOutState; } } //不能抽奖的状态 public class NoRaffleState extends State{ //初始化时传入活动的引用,扣除积分后改变其状态 RaffleActivity activity; public NoRaffleState(RaffleActivity activity) { this.activity = activity; } //当前状态可以扣积分,扣除后,将状态设置成可以抽奖状态 @Override public void deductMoney() { System.out.println("扣除50积分成功,您可以抽奖了"); activity.setState(activity.getCanRaffleState()); } //当前状态不能抽奖 @Override public boolean raffle() { System.out.println("扣了积分才能抽奖喔!"); return false; } //当前状态不能发奖品 @Override public void dispensePrize() { System.out.println("不能发放奖品"); } } //可以抽奖状态 public class CanRaffleState extends State{ private RaffleActivity activity; public CanRaffleState(RaffleActivity activity) { this.activity = activity; } //已经扣除了积分,不能再扣 @Override public void deductMoney() { System.out.println("已经扣取过了积分"); } //可以抽奖, 抽完奖后,根据实际情况,改成新的状态 @Override public boolean raffle() { System.out.println("正在抽奖,请稍等!"); Random r = new Random(); int num = r.nextInt(10); //10%中奖机会 if(num == 0){ //改变活动状态为发放奖品context activity.setState(activity.getDispenseState()); return true; }else{ System.out.println("很遗憾没有抽中奖品!"); //改变状态为不能抽奖 activity.setState(activity.getNoRafflleState()); return false; } } // 不能发放奖品 @Override public void dispensePrize() { System.out.println("没中奖,不能发放奖品"); } } //奖品发放完毕状态 //当activity改变成DispenseOutState,抽奖活动结束 public class DispenseOutState extends State{ private RaffleActivity activity; public DispenseOutState(RaffleActivity activity) { this.activity = activity; } @Override public void deductMoney() { System.out.println("奖品发送完了,请下次再参加"); } @Override public boolean raffle() { System.out.println("奖品发送完了,请下次再参加"); return false; } @Override public void dispensePrize() { System.out.println("奖品发送完了,请下次再参加"); } } //发放奖品的状态 public class DispenseState extends State{ private RaffleActivity activity; public DispenseState(RaffleActivity activity) { this.activity = activity; } @Override public void deductMoney() { System.out.println("不能扣除积分"); } @Override public boolean raffle() { System.out.println("不能抽奖"); return false; } //发放奖品 @Override public void dispensePrize() { if(activity.getCount() > 0){ System.out.println("恭喜中奖了"); //改变状态为不能抽奖 activity.setState(activity.getNoRafflleState()); }else{ System.out.println("很遗憾,奖品发送完了"); //改变状态为奖品发送完毕, 后面我们就不可以抽奖 activity.setState(activity.getDispensOutState()); //System.out.println("抽奖活动结束"); //System.exit(0); } } } public static void main(String[] args) { RaffleActivity activity = new RaffleActivity(1); for (int i = 0; i < 5; i++) { System.out.println("--------第" + (i + 1) + "次抽奖----------"); activity.debuctMoney(); //先扣除积分 activity.raffle(); //再抽奖 } }
-
-
注意事项
- 代码有很强的可读性,状态模式将每个状态的行为封装到对应的一个类中
- 方便维护,将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一 个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句, 而且容易出错
- 符合“开闭原则”,容易增删状态
- 会产生很多类,每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
- 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
21. 访问者模式
-
测评系统的需求
- 将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价 有不同的种类,比如 成功、失败 等)
- 传统方式的问题分析
- 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则, 不利于维护
- 扩展性不好,比如 增加了新的人员类型,或者管理方法,都不好做
- 引出我们会使用新的设计模式 – 访问者模式
/* 访问者模式基本介绍: 1. 访问者模式: 封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用 于这些元素的新的操作 2. 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题 3. 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口 4. 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联), 同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决 */ public abstract class Action { public abstract void getManResult(Man man); public abstract void getWomanResult(Woman woman); } public class Success extends Action{ @Override public void getManResult(Man man) { System.out.println("man给的评价是很成功..."); } @Override public void getWomanResult(Woman woman) { System.out.println("woman给的评价也是很成功..."); } } public class Fail extends Action{ @Override public void getManResult(Man man) { System.out.println("man给的评价是失败..."); } @Override public void getWomanResult(Woman woman) { System.out.println("woman给的评价也是失败..."); } } public abstract class Person { public abstract void accept(Action action); } public class Man extends Person{ @Override public void accept(Action action) { action.getManResult(this); } } public class Woman extends Person{ @Override public void accept(Action action) { action.getWomanResult(this); } } //数据结构,管理很多人 public class ObjectStructure { //维护了一个集合 private List<Person> persons = new LinkedList<>(); //添加 public void attach(Person p){ persons.add(p); } //移除 public void detach(Person p){ persons.remove(p); } //显示测评情况 public void display(Action action){ for(Person P : persons){ P.accept(action); } } } public static void main(String[] args) { ObjectStructure structure = new ObjectStructure(); structure.attach(new Man()); structure.attach(new Woman()); //评价成功 Success success = new Success(); structure.display(success); System.out.println("-------------------------"); //评价失败 Fail fail = new Fail(); structure.display(fail); }
-
注意事项
- 优点
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一, 可以做报表, UI, 拦截器与过滤器,适用于数据结构相对稳定的系统
- 缺点
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
- 违背了依赖倒转原则, 访问者依赖的是具体元素,而不是抽象元素
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的
- 优点
22. 中介者模式
-
智能家庭项目
- 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘等
- 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放
-
传统的方式的问题分析
- 当各电器对象有多种状态改变时,相互之间的调用关系会比较复杂
- 各个电器对象彼此联系,你中有我,我中有你,不利于松耦合
- 各个电器对象之间所传递的消息(参数),容易混乱
- 当系统增加一个新的电器对象时,或者执行流程改变时,代码的可维护性、扩展性 都不理想
- 考虑中介者模式
/* 中介者模式基本介绍: 1. 中介者模式: 用一个中介对象来封装一系列的对象交互,中介者使各个对象不需要显式地相互引用,从而 使其耦合松散,而且可以独立地改变它们之间的交互 2. 中介者模式属于行为型模式,使代码易于维护 3. 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交 互时起到了中间人的作用 */ //同事抽象类 public abstract class Colleague { private Mediator mediator; public String name; public Colleague(Mediator mediator, String name) { this.mediator = mediator; this.name = name; } public Mediator GetMediator() { return this.mediator; } public abstract void SendMessage(int stateChange); } //具体的同事类 public class Alarm extends Colleague{ //构造器 public Alarm(Mediator mediator, String name) { super(mediator, name); //在创建Alarm同事对象时,将自己放入到ConcreteMediator集合对象中 mediator.Register(name, this); } public void SendAlarm(int stateChange) { SendMessage(stateChange); } @Override public void SendMessage(int stateChange) { //调用中介者对象的getMessage this.GetMediator().GetMessage(stateChange, this.name); } } public class TV extends Colleague{ public TV(Mediator mediator, String name) { super(mediator, name); mediator.Register(name, this); } @Override public void SendMessage(int stateChange) { this.GetMediator().GetMessage(stateChange, this.name); } public void StartTv() { System.out.println("It's time to StartTv!"); } public void StopTv() { System.out.println("StopTv!"); } } public class Curtains extends Colleague { public Curtains(Mediator mediator, String name) { super(mediator, name); mediator.Register(name, this); } @Override public void SendMessage(int stateChange) { this.GetMediator().GetMessage(stateChange, this.name); } public void UpCurtains() { System.out.println("I am holding Up Curtains!"); } } public class CoffeeMachine extends Colleague { public CoffeeMachine(Mediator mediator, String name) { super(mediator, name); mediator.Register(name, this); } @Override public void SendMessage(int stateChange) { this.GetMediator().GetMessage(stateChange, this.name); } public void StartCoffee() { System.out.println("It's time to startcoffee!"); } public void FinishCoffee() { System.out.println("After 5 minutes!"); System.out.println("Coffee is ok!"); SendMessage(0); } } public abstract class Mediator { //将中介者对象,加入到集合中 public abstract void Register(String colleagueName, Colleague colleague); //接收消息, 具体的同事对象发出 public abstract void GetMessage(int stateChange, String colleagueName); public abstract void SendMessage(); } //具体的中介者类 public class ConcreteMediator extends Mediator{ //集合,放入所有的同事对象 private HashMap<String, Colleague> colleagueMap; private HashMap<String, String> interMap; public ConcreteMediator() { colleagueMap = new HashMap<String, Colleague>(); interMap = new HashMap<String, String>(); } @Override public void Register(String colleagueName, Colleague colleague) { colleagueMap.put(colleagueName, colleague); if (colleague instanceof Alarm) { interMap.put("Alarm", colleagueName); } else if (colleague instanceof CoffeeMachine) { interMap.put("CoffeeMachine", colleagueName); } else if (colleague instanceof TV) { interMap.put("TV", colleagueName); } else if (colleague instanceof Curtains) { interMap.put("Curtains", colleagueName); } } //具体中介者的核心方法 //1. 根据得到消息,完成对应任务 //2. 中介者在这个方法,协调各个具体的同事对象,完成任务 @Override public void GetMessage(int stateChange, String colleagueName) { //处理闹钟发出的消息 if (colleagueMap.get(colleagueName) instanceof Alarm) { if (stateChange == 0) { ((CoffeeMachine) (colleagueMap.get(interMap .get("CoffeeMachine")))).StartCoffee(); ((TV) (colleagueMap.get(interMap.get("TV")))).StartTv(); } else if (stateChange == 1) { ((TV) (colleagueMap.get(interMap.get("TV")))).StopTv(); } } else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) { ((Curtains) (colleagueMap.get(interMap.get("Curtains")))) .UpCurtains(); } else if (colleagueMap.get(colleagueName) instanceof TV) { //如果TV发现消息 } else if (colleagueMap.get(colleagueName) instanceof Curtains) { //如果是以窗帘发出的消息,这里处理... } } @Override public void SendMessage() { } } public static void main(String[] args) { //创建一个中介者对象 Mediator mediator = new ConcreteMediator(); //创建Alarm并且加入到ConcreteMediator对象的HashMap Alarm alarm = new Alarm(mediator, "alarm"); //创建了CoffeeMachine对象,并且加入到ConcreteMediator对象的HashMap CoffeeMachine coffeeMachine = new CoffeeMachine(mediator, "coffeeMachine"); //创建 Curtains,并且加入到ConcreteMediator对象的HashMap Curtains curtains = new Curtains(mediator, "curtains"); TV tV = new TV(mediator, "TV"); //让闹钟发出消息 alarm.SendAlarm(0); coffeeMachine.FinishCoffee(); alarm.SendAlarm(1); }
-
注意事项
- 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构, 进行解耦
- 减少类间依赖,降低了耦合,符合迪米特原则
- 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
- 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意
23. 解释器模式
-
四则运算问题:通过解释器模式来实现四则运算,如计算a+b-c的值
- 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复
- 在分别输入a ,b, c, d, e 的值
- 最后求出结果
-
传统方案解决四则运算问题分析
- 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
- 问题分析:如果加入新的运算符,比如 * / ( 等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰
- 解决方案:可以考虑使用解释器模式, 即: 表达式 -> 解释器(可以有多种) -> 结果
/* 1. 解释器模式基本介绍: 1. 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建 语法分析树,最终形成一颗抽象的语法分析树,这里的词法分析器和语法分析器都可以看做是解释器 2. 解释器模式:是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释 器来解释语言中的句子(表达式) 2. 应用场景 1. 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树 2. 一些重复出现的问题可以用一种简单的语言来表达 3. 一个简单语法需要解释的场景 4. 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等 解释器模式的角色及职责 1. Context: 是环境角色,含有解释器之外的全局信息 2. AbstractExpression: 抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点 所共享 3. TerminalExpression: 为终结符表达式, 实现与文法中的终结符相关的解释操作 4. NonTermialExpression: 为非终结符表达式,为文法中的非终结符实现解释操作 5. 说明: Context和TerminalExpression信息通过控制台输入即可 */ //抽象类表达式,通过HashMap键值对, 可以获取到变量的值 public abstract class Expression { //key就是公式(表达式)参数[a,b,c],value就是具体值 public abstract int interpreter(HashMap<String, Integer> var); } //变量(key)的解释器 public class VarExpression extends Expression{ private String key; public VarExpression(String key) { this.key = key; } @Override public int interpreter(HashMap<String, Integer> var) { return var.get(this.key); } } //符号解释器 public class SymbolExpression extends Expression{ protected Expression left; protected Expression right; public SymbolExpression(Expression left, Expression right) { this.left = left; this.right = right; } //因为SymbolExpression是让其子类来实现的,因此interpreter是一个默认实现 @Override public int interpreter(HashMap<String, Integer> var) { return 0; } } //抽象运算符号解析器 这里的每个运算符号,都只和自己左右两个数字有关系 //但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类 public class SubExpression extends SymbolExpression{ public SubExpression(Expression left, Expression right) { super(left, right); } //求出left和right表达式相减后的结果 public int interpreter(HashMap<String, Integer> var) { return super.left.interpreter(var) - super.right.interpreter(var); } } //加法解释器 public class AddExpression extends SymbolExpression{ public AddExpression(Expression left, Expression right) { super(left, right); } //处理相加 public int interpreter(HashMap<String, Integer> var) { //super.left.interpreter(var) : 返回 left 表达式对应的值 a = 10 //super.right.interpreter(var): 返回right 表达式对应值 b = 20 return super.left.interpreter(var) + super.right.interpreter(var); } } //计算 public class Calculator { //定义表达式 private Expression expression; //构造函数传参,并解析 public Calculator(String expStr) { // 安排运算先后顺序 Stack<Expression> stack = new Stack<>(); char[] charArray = expStr.toCharArray(); Expression left = null; Expression right = null; //遍历我们的字符数组 //针对不同的情况,做处理 for (int i = 0; i < charArray.length; i++) { switch (charArray[i]) { case '+': left = stack.pop();// 从stack取出left => "a" right = new VarExpression(String.valueOf(charArray[++i])); //取出右表达式 "b" //然后根据得到left和right构建AddExpresson加入stack stack.push(new AddExpression(left, right)); break; case '-': left = stack.pop(); right = new VarExpression(String.valueOf(charArray[++i])); stack.push(new SubExpression(left, right)); break; default: //如果是一个Var就创建要给VarExpression对象,并push到stack stack.push(new VarExpression(String.valueOf(charArray[i]))); break; } } //当遍历完整个charArray数组后,stack就得到最后Expression this.expression = stack.pop(); } public int run(HashMap<String, Integer> var) { //最后将表达式a+b和 var = {a=10,b=20} //然后传递给expression的interpreter进行解释执行 return this.expression.interpreter(var); } } public class Main { public static void main(String[] args) throws IOException { String expStr = getExpStr(); // a+b HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20} Calculator calculator = new Calculator(expStr); System.out.println("运算结果:" + expStr + "=" + calculator.run(var)); } // 获得表达式 public static String getExpStr() throws IOException { System.out.print("请输入表达式:"); return (new BufferedReader(new InputStreamReader(System.in))).readLine(); } // 获得值映射 public static HashMap<String, Integer> getValue(String expStr) throws IOException { HashMap<String, Integer> map = new HashMap<>(); for (char ch : expStr.toCharArray()) { if (ch != '+' && ch != '-') { if (!map.containsKey(String.valueOf(ch))) { System.out.print("请输入" + String.valueOf(ch) + "的值:"); String in = (new BufferedReader(new InputStreamReader(System.in))).readLine(); map.put(String.valueOf(ch), Integer.valueOf(in)); } } } return map; } }
-
注意事项
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低
七大设计原则
-
设计模式的原则
其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(设计模式为什么这么设计的依据)
1. 单一职责原则
-
基本介绍
对于类来说,应该一个类只负责一项职责,如果一个类A负责两项职责:职责1、职责2,当职责1需求变更而需要改变类A时,可能会造成职责2执行错误,所以需要将类A分解成类B和类C,类B负责职责1,类C负责职责2,这样两个职责之间即使需求变更也没有关系
-
应用案例
-
以交通工具案例进行讲解
/* 分析 run方法违反了单一职责原则 改进 根据交通工具的运行方法不同,将类进行分类 */ public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("汽车"); vehicle.run("自行车"); vehicle.run("飞机"); //飞机是不能在公路上行驶的 } //交通工具类 class Vehicle{ public void run(String name){ System.out.println(name + "在公路上行驶..."); } }
-
方案一
/* 分析 1. 遵守了单一职责原则 2. 这样做的话,改动的地方太多 3. 将类分解后,同时需要修改客户端 改进 直接修改Vehicle类,改动的代码会比较少 */ public static void main(String[] args) { RoadVehicle v1 = new RoadVehicle(); v1.run("汽车"); WaterVehicle v2 = new WaterVehicle(); v2.run("轮船"); SkyVehicle v3 = new SkyVehicle(); v3.run("飞机"); } //陆地上的交通工具 class RoadVehicle{ public void run(String name){ System.out.println(name + "在公路上运行..."); } } //水上的交通工具 class WaterVehicle{ public void run(String name){ System.out.println(name + "在水上运行..."); } } //水上的交通工具 class SkyVehicle{ public void run(String name){ System.out.println(name + "在天空运行..."); } }
-
方案二
/* 分析 1. 这种修改并没有对原来的类做太大的修改,只是添加了方法 2. 这里虽然没有在类这个级别上遵守单一职责原则,但是在方法这个级别上遵守了单一职责原则 */ public static void main(String[] args) { Vehicle2 v = new Vehicle2(); v.runRoad("汽车"); v.runWater("轮船"); v.runSky("飞机"); } //交通工具类 class Vehicle2{ public void runRoad(String name){ System.out.println(name + "在公路上运行..."); } public void runWater(String name){ System.out.println(name + "在水上运行..."); } public void runSky(String name){ System.out.println(name + "在天空上运行..."); } }
-
单一职责原则的注意事项和细节
- 降低类的复杂度,一个类只负责一项职责
- 提高类的可读性,可维护性
- 降低因变更需求而引起的风险
- 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级别上违反单一职责原则,另外只有类中方法数量足够少时,才可以在方法级别上保持单一职责原则
-
2. 接口隔离原则
-
基本介绍
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
-
应用案例
- 定义一个接口Interface
- 类B和类D均实现了该接口
- 类A通过该接口依赖类B,但是类A只需要实现接口中的m1,m2,m3方法
- 类C通过接口依赖类D,但是类C只需要实现接口中的m1,m4,m5方法
- 所以说对类B来说,m4,m5方法是不需要实现的,对于类D来说,m2,m3方法也是不需要实现的
- 按照接口隔离原则应该这样处理:将接口Interface拆分成几个独立的接口,类A和类C分别与它们需要实现的接口建立依赖关系,也就是所谓的接口隔离原则
//使用接口隔离原则之前的代码 interface Interface{ void m1(); void m2(); void m3(); void m4(); void m5(); } //类B实现接口 class B implements Interface{ @Override public void m1() { System.out.println("类C实现了m1方法..."); } @Override public void m2() { System.out.println("类C实现了m2方法..."); } @Override public void m3() { System.out.println("类C实现了m3方法..."); } @Override public void m4() { System.out.println("类C实现了m4方法..."); } @Override public void m5() { System.out.println("类C实现了m5方法..."); } } //定义类D实现接口 class D implements Interface{ @Override public void m1() { System.out.println("类D实现了m1方法..."); } @Override public void m2() { System.out.println("类D实现了m2方法..."); } @Override public void m3() { System.out.println("类D实现了m3方法..."); } @Override public void m4() { System.out.println("类D实现了m4方法..."); } @Override public void m5() { System.out.println("类D实现了m5方法..."); } } //定义类A,通过接口使用类B的m1,m2,m3方法 class A{ public void use1(Interface i){ i.m1(); } public void use2(Interface i){ i.m2(); } public void use3(Interface i){ i.m3(); } } //定义类C,通过接口使用类D的m1,m4,m5方法 class C{ public void use1(Interface i){ i.m1(); } public void use4(Interface i){ i.m4(); } public void use5(Interface i){ i.m5(); } }
-
使用接口隔离原则之后的代码
interface Interface1{ void m1(); } interface Interface2{ void m2(); void m3(); } interface Interface3{ void m4(); void m5(); } class B implements Interface1,Interface2{ @Override public void m1() { System.out.println("类B实现了m1方法..."); } @Override public void m2() { System.out.println("类B实现了m2方法..."); } @Override public void m3() { System.out.println("类B实现了m3方法..."); } } class D implements Interface1,Interface3{ @Override public void m1() { System.out.println("类D实现了m1方法..."); } @Override public void m4() { System.out.println("类D实现了m4方法..."); } @Override public void m5() { System.out.println("类D实现了m5方法..."); } } class A{ public void use1(Interface1 i){ i.m1(); } public void use2(Interface2 i){ i.m2(); } public void use3(Interface2 i){ i.m3(); } } class C{ public void use1(Interface1 i){ i.m1(); } public void use4(Interface3 i){ i.m4(); } public void use5(Interface3 i){ i.m5(); } }
3. 依赖倒转原则
-
基本介绍
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多,在java中,抽象指的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体实现的操作,把展现细节的任务交给实现类去完成
-
应用案例
编程实现Person类接收消息的功能
//方式一 public class DepedenceInversionTest01 { public static void main(String[] args) { Person p = new Person(); p.receive(new Email()); } } /* 完成person类接收消息的功能 分析: 1. 简单,比较容易想到 2. 如果我们获取的对象是微信、QQ等其他类信息,则需要新增类,同时Person也需要添加对应的方法 3. 解决思路: 引入接口IReceiver,表示接收者,这样Person类就会与接口产生依赖,而不会对实现类产生依赖 4. 直接让微信、QQ等类实现该接口即可,这样就符合依赖倒转原则 */ class Person{ public void receive(Email email){ System.out.println(email.getInfo()); } } class Email{ public String getInfo(){ return "电子邮件: hello world"; } } //方式二 public class DepedenceInversionTest02 { public static void main(String[] args) { Person2 p = new Person2(); p.receive(new Email2()); p.receive(new Weixin()); } } class Person2{ public void receive(IReceiver receiver){ System.out.println(receiver.getInfo()); } } interface IReceiver{ String getInfo(); } class Email2 implements IReceiver{ @Override public String getInfo(){ return "电子邮件: hello world"; } } class Weixin implements IReceiver{ @Override public String getInfo() { return "微信信息: hello xinxin"; } }
-
依赖关系传递的三种方式
- 接口传递
- 构造方法传递
- set方法传递
-
注意事项和细节
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性会更好
- 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间就存在一个缓冲层,利于程序的扩展和优化
- 继承时遵循里氏替换原则
4. 里氏替换原则
-
OO中的继承性的思考和说明
- 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契 约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
- 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵 入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承, 则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子 类的功能都有可能产生故障
- 问题提出:在编程中,如何正确的使用继承? => 里氏替换原则
-
基本介绍
- 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序 P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
- 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可 以通过聚合,组合,依赖来解决问题
-
应用案例
public class Liskov { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.func1(11, 3)); System.out.println("1-8=" + a.func1(1, 8)); System.out.println("-----------------------"); B b = new B(); // System.out.println("11-3=" + b.func1(11, 3)); // System.out.println("1-8=" + b.func1(1, 8)); //因为B类不再继承A类了,所以func1方法不再是求减法 System.out.println("11+3=" + b.func1(11, 3)); System.out.println("11-3=" + b.func3(11, 3)); System.out.println("1+8=" + b.func1(1, 8)); System.out.println("11+3+9=" + b.func2(11, 3)); } } //class A { // //返回两个数的差 // public int func1(int num1, int num2) { // return num1 - num2; // } //} // //class B extends A { // //这里重写了父类的方法,可能是无意识的 // public int func1(int a, int b) { // return a + b; // } // // public int func2(int a, int b) { // return func1(a, b) + 9; // } //} /* 1. 我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的 方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完 成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运 行多态比较频繁的时候 2. 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉, 采用依赖,聚合,组合等关系代替. */ //创建一个更加基础的基类,将更加基础的方法和成员写到Base类中 class Base{ } class A extends Base{ public int func1(int num1, int num2) { return num1 - num2; } } class B extends Base { //如果B类需要使用到A类的方法,使用组合关系 private A a = new A(); public int func1(int a, int b) { return a + b; } public int func2(int a, int b) { return func1(a, b) + 9; } //调用A中的方法 public int func3(int a, int b) { return this.a.func1(a, b); } }
5. 开闭原则
-
基本介绍
- 开闭原则是编程中最基础、最重要的设计原则
- 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
- 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则
-
应用案例
public class OpenClosed { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); //新增三角形 graphicEditor.drawShape(new Triangle()); } } //用于绘图的类 //class GraphicEditor { // public void drawShape(Shape s) { // if (s.m_type == 1){ // drawRectangle(s); // } // else if (s.m_type == 2){ // drawCircle(s); // }else if(s.m_type == 3){ // drawTriangle(s); // } // } // // public void drawRectangle(Shape r) { // System.out.println("绘制矩形"); // } // // public void drawCircle(Shape r) { // System.out.println("绘制圆形"); // } // // //新增三角形 // public void drawTriangle(Shape r) { // System.out.println("绘制三角形"); // } //} //class Shape { // int m_type; //} // //class Rectangle extends Shape { // Rectangle() { // super.m_type = 1; // } //} // //class Circle extends Shape { // Circle() { // super.m_type = 2; // } //} /* 1. 优点是比较好理解,简单易操作。 2. 缺点是违反了设计模式的ocp原则,即对扩展开放(提供方),对修改关闭(使用方)。 即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码. 3. 比如我们这时要新增加一个图形种类三角形,我们需要做很多修改,修改的地方较多 */ //新增三角形类 //class Triangle extends Shape{ // Triangle(){ // super.m_type = 3; // } //} /* 改进的思路分析 把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可, 这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可, 使用方的代码就不需要修 -> 满足了开闭原则 */ class GraphicEditor { public void drawShape(Shape s) { s.draw(); } } abstract class Shape{ int m_type; public abstract void draw(); } class Rectangle extends Shape { Rectangle() { super.m_type = 1; } @Override public void draw() { System.out.println("绘制矩阵"); } } class Circle extends Shape { Circle() { super.m_type = 2; } @Override public void draw() { System.out.println("绘制圆形"); } } class Triangle extends Shape{ Triangle(){ super.m_type = 3; } @Override public void draw() { System.out.println("绘制三角形"); } }
6. 迪米特法则
-
基本介绍
- 一个对象应该对其他对象保持最少的了解
- 类与类关系越密切,耦合度越大
- 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内 部。对外除了提供的public 方法,不对外泄露任何信息
- 迪米特法则还有个更简单的定义:只与直接的朋友通信
- 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系, 我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部
-
应用案例
有一个学校,下属有各个学院和 总部,现要求打印出学校总部员 工ID和学院员工的id
public class Demeter { public static void main(String[] args) { SchoolManager schoolManager = new SchoolManager(); schoolManager.printAllEmployee(new CollegeManager()); } } class Employee { private String id; public void setId(String id) { this.id = id; } public String getId() { return id; } } class CollegeEmployee { private String id; public void setId(String id) { this.id = id; } public String getId() { return id; } } class CollegeManager { public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<CollegeEmployee>(); for (int i = 0; i < 10; i++) { CollegeEmployee emp = new CollegeEmployee(); emp.setId("学院员工id= " + i); list.add(emp); } return list; } public void printEmployee(){ List<CollegeEmployee> list1 = this.getAllEmployee(); System.out.println("------------学校员工------------"); for (CollegeEmployee e : list1) { System.out.println(e.getId()); } } } /* 分析: 1. 直接朋友: Employee、CollegeManager 2. 陌生朋友: CollegeEmployee,是以局部变量的方式出现在SchoolManager中,违反了迪米特法则 1. 前面设计的问题在于SchoolManager中,CollegeEmployee类并不是 SchoolManager类的直接朋友 (分析) 2. 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合 3. 对代码按照迪米特法则进行改进 */ class SchoolManager { public List<Employee> getAllEmployee() { List<Employee> list = new ArrayList<Employee>(); for (int i = 0; i < 5; i++) { Employee emp = new Employee(); emp.setId("学校总部员工id= " + i); list.add(emp); } return list; } void printAllEmployee(CollegeManager sub) { //将这段代码封装到CollegeManager中,这里进行调用即可 sub.printEmployee(); // List<CollegeEmployee> list1 = sub.getAllEmployee(); // System.out.println("------------学校员工------------"); // for (CollegeEmployee e : list1) { // System.out.println(e.getId()); // } List<Employee> list2 = this.getAllEmployee(); System.out.println("------------学校总部员工------------"); for (Employee e : list2) { System.out.println(e.getId()); } } }
-
注意事项
- 迪米特法则的核心是降低类之间的耦合
- 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系
7. 合成复用原则
- 基本介绍
- 尽量使用合成/聚合的方式,而不是使用继承
- 设计原则的核心思想
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力