23种设计模式
什么是设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
模式:在某些场景下,针对某类问题的某种通用的解决方案。
设计模式的三个分类
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
结构型模式:把类或对象结合在一起形成一个更大的结构。
行为型模式:类和对象如何交互,及划分责任和算法。
1. 创建型模式
创建型模式的主要关注点是:怎样创建对象?,它的主要特点是:将对象的创建与使用分离,这样可以降低系统的耦合度
创建型模式分为:单例模式、工厂方法模式、抽象工程模式、原型模式、建造者模式
1.1 单例模式
单例模式,它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
单例模式具备典型的3个特点:1、只有一个实例。 2、自我实例化。 3、提供全局访问点。
因此当系统中只需要一个实例对象或者系统中只允许一个公共访问点,除了这个公共访问点外,不能通过其他访问点访问该实例时,可以使用单例模式。
单例模式应用场景
- 项目配置类
读取项目的配置信息的类可以做成单例的,因为只需要读取一次,且配置信息字段一般比较多节省资源。通过这个单例的类,可以对应用程序中的类进行全局访问。无需多次对配置文件进行多次读取。
- 应用日志类
日志器Logger在你的应用中是无处不在的。也应该只初始化一次,但是可以到处使用。
- 分析和报告类
如果你在使用一些数据分析工具例如Google Analytics。你就可以注意到它们被设计成单例的,仅仅初始化一次,然后在用户的每一个行为中都可以使用。
1.1.1 单例模式的结构
单例模式的主要有以下角色:
- 单例类:只能创建一个实例的类
- 访问类:使用单例类
1.1.2 单例模式的实现
单例设计模式分类两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式 1:静态变量
该方式在成员位置声明 Singleton 类型的静态变量,并创建 Singleton 类的对象 instance。
/**
* 饿汉式:静态成员变量
*/
public class Singleton {
// 1.私有构造方法
private Singleton() {}
// 2.在本类中创建本类对象
private static Singleton instance = new Singleton();
// 3.提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance() {
return instance;
}
}
instance 对象是随着类的加载而创建的,如果该对象很大,却一直没有使用就会造成内存的浪费。
测试每次获取到的 Singleton 对象都是同一个:
public class Client {
public static void main(String[] args) {
// 创建Singleton类的对象
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// 判断获取到的两个是否是同一个对象
System.out.println(instance1 == instance2); // true
}
}
饿汉式 2:静态代码块
该方式在成员位置声明 Singleton 类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。
/**
* 饿汉式: 静态代码块
*/
public class Singleton {
// 声明Singleton类型的变量
private static Singleton instance; // null
//在静态代码块中进行赋值
static {
instance = new Singleton();
}
// 私有构造方法
private Singleton() {}
//对外提供获取该类对象的方法
public static Singleton getInstance() {
return instance;
}
}
该方式和饿汉式的方式 1 基本一样,所以该方式也存在内存浪费问题。
懒汉式 3:双重检查锁
懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以没有必要让每个线程必须持有锁才能调用该方法,可以调整加锁的时机。
由此产生了一种新的实现方式:双重检查锁模式(double check)
/**
* 双重检查锁方式(有风险)
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量
private static Singleton instance;
// 对外提供公共的访问方式
public static Singleton getInstance() {
// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象
if (instance == null) {
synchronized (Singleton.class) {
// 第二次判断,抢占到锁以后再次判断
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁模式是一种比较的单例实现模式,解决了单例、性能、线程安全问题,但是虽然看上去完美无缺,其实是存在问题的;
在多线程的情况下,可能会出现空指针问题,原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁带来空指针的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
/**
* 双重检查锁方式(标准)
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量,使用volatile保证可见性和有序性
private static volatile Singleton instance;
// 对外提供公共的访问方式
public static Singleton getInstance() {
// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象
if (instance == null) {
synchronized (Singleton.class) {
// 第二次判断,抢占到锁以后再次判断
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
懒汉式 4:静态内部类
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
/**
* 静态内部类方式
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 定义一个静态内部类
private static class SingletonHolder {
// 在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
// 提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
第一次加载 Singleton 类时不会去初始化 INSTANCE,只有第一次调用 getInstance(),虚拟机加载 SingletonHolder 并初始化 INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
饿汉式 3:枚举🚀
首先,枚举方式是饿汉式单例模式,如果不考虑浪费内存空间的问题,这是极力推荐的单例实现模式。
因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
枚举的写法非常简单,而且枚举方式是所用单例实现中唯一一种不会被破坏的单例实现模式。
/**
* 饿汉式:枚举实现
*/
public enum Singleton {
INSTANCE
}
防止对象克隆破坏单例模式Singleton
涉及单例类时还要注意clone方法的正确使用:
package org.byron4j.cookbook.designpattern.singleton;
/**
* 单例模式实例
* 1. 构造器私有化
* 2. 提供静态方法供外部获取单例实例
* 3. 延迟初始化实例
*/
public class SingletonZClone implements Cloneable{
private static SingletonZClone instance;
// 构造器私有化
private SingletonZClone(){
}
// 提供静态方法
public static SingletonZClone getInstance(){
// 将同步锁范围缩小,降低性能损耗
if(instance == null){
synchronized (SingletonZClone.class){
if(instance == null){
instance = new SingletonZClone();
}
}
}
return instance;
}
/**
* 克隆方法--改为public
* @return
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void display(){
System.out.println("Hurray! I am create as a SingletonZClone!");
}
}
默认情况下clone时protected修饰的,这里改为了public修饰,测试用例如下:
@Test
public void testClone() throws CloneNotSupportedException {
SingletonZClone singletonZClone1 = SingletonZClone.getInstance();
SingletonZClone singletonZClone2 = SingletonZClone.getInstance();
SingletonZClone singletonZClone3 = (SingletonZClone)SingletonZClone.getInstance().clone();
System.out.println(singletonZClone1 == singletonZClone2);
System.out.println(singletonZClone1 == singletonZClone3);
System.out.println(singletonZClone2 == singletonZClone3);
}
true
false
false
clone方法返回一个被克隆对象的实例的副本,除了内存地址其他属性值都是一样的,所以副本和被克隆对象不是同一个实例。
可以看出clone方法破坏了单例类,为防止该问题出现,我们需要禁用clone方法,直接改为:
/**
* 克隆方法--改为public
* @return
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
防止序列化破坏单例模式
Java序列化机制允许将一个对象的状态转换为字节流,就可以很容易地存储和转移。
一旦对象被序列化,你就可以对其进行反序列化–将字节流转为对象。
如果一个Singleton类被序列化,则可能创建重复的对象。
我们可以使用钩子hook,来解释这个问题。
在Java规范中有关于readResolve()方法的介绍:
对于可序列化的和外部化的类,readResolve() 方法允许一个类可以替换/解析从流中读取到的对象。
通过实现 readResolve 方法,一个类就可以直接控制反序列化后的实例以及类型。
定义如下:readResolve 方法会在ObjectInputStream 从流中读取一个对象时调用。ObjectInputStream 会检测类是否定义了 readResolve 方法。
如果 readResolve 方法定义了,会调用该方法用于指定从流中反序列化后作为返回的结果对象。
返回的类型要与原对象的类型一致,不然会出现 ClassCastException。
解决方案:在 Singleton 类中添加readResolve()
方法。
在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。
/**
* 静态内部类方式(解决序列化破解单例模式)
*/
public class Singleton implements Serializable {
// 私有构造方法
private Singleton() {}
// 提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 下面是为了解决序列化反序列化破解单例模式
* 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
// 定义一个静态内部类
private static class SingletonHolder {
// 在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
}
当通过反射方式调用构造方法进行创建时,直接抛异常
/**
* 静态内部类方式(解决反射破坏单例模式)
*/
public class Singleton {
private static boolean flag = false;
// 私有构造方法
private Singleton() {
synchronized (Singleton.class) {
// 如果是true,说明非第一次访问,直接抛一个异常,如果是false,说明第一次访问
if (flag) {
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
// 提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
// 定义一个静态内部类
private static class SingletonHolder {
// 在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
}
枚举方式不会出现这两个问题。
使用单例模式的注意事项
- 单例类是很少使用的,如果你要使用这个设计模式,你必须清楚的知道你在做什么。因为全局范围内仅仅创建一个实例,所以在资源受约束的平台是存在风险的。
- 注意对象克隆。 单例模式需要仔细检查并阻止clone方法。
- 多线程访问下,需要注意线程安全问题。
- 小心多重类加载器,也许会破坏你的单例类。
- 如果单例类是可序列化的,需要实现严格类型
一个友好的完整单例模式实例
import java.io.Serializable;
/**
* 单例模式实例
* 1. 构造器私有化
* 2. 提供静态方法供外部获取单例实例
* 3. 延迟初始化实例
*/
public class SingletonZCloneSerializableReadResolve implements Cloneable, Serializable {
private static SingletonZCloneSerializableReadResolve instance;
// 构造器私有化
private SingletonZCloneSerializableReadResolve(){
}
// 提供静态方法
public static SingletonZCloneSerializableReadResolve getInstance(){
// 将同步锁范围缩小,降低性能损耗
if(instance == null){
synchronized (SingletonZCloneSerializableReadResolve.class){
if(instance == null){
instance = new SingletonZCloneSerializableReadResolve();
}
}
}
return instance;
}
/**
* 克隆方法--改为public
* @return
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public void display(){
System.out.println("Hurray! I am create as a SingletonZCloneSerializableReadResolve!");
}
/**
* 反序列化时返回instance实例,防止破坏单例模式
* @return
*/
protected Object readResolve(){
return getInstance();
}
}
1.2工厂设计模式
工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离 起来,达到提高灵活性的目的。
其实设计模式和面向对象设计原则都是为了使得开发项目更加容易扩展和维 护,解决方式就是一个“分工”。
社会的发展也是这样,分工越来越细。 原始社会的人:人什么都要会,自己种,自己打猎,自己织衣服,自己治病 现在的人:可以只会一样,其他都不会,只会 Java 也能活,不会做饭,不会开 车,不会….
一、面向对象的设计原则
(总共六个):这里说几个和工厂模式相关的
OCP(开闭原则,Open-Closed Principle) 一个软件的实体应当对扩展开放,对修改关闭。
所以,开闭原则是设计模式的第一大原则,它的潜台词是:控制需求变动风 险,缩小维护成本。
DIP(依赖倒转原则,Dependence Inversion Principle)
要针对接口编程,不要针对实现编程。
如果 A 中关联 B,那么尽量使得 B 实现某个接口,然后 A 与接口发生关系, 不与 B 实现类发生关联关系。
依赖倒置的潜台词是:面向抽象编程,解耦调用和被调用者。
LOD(迪米特法则,Law Of Demeter)
只与你直接的朋友通信,而避免和陌生人通信。 要求尽量的封装,尽量的独立,尽量的使用低级别的访问修饰符。这是封装 特性的典型体现。 一个类如果暴露太多私用的方法和字段,会让调用者很茫然。并且会给类造 成不必要的判断代码。所以,我们使用尽量低的访问修饰符,让外界不知道我们 的内部。这也是面向对象的基本思路。这是迪米特原则的一个特性,无法了解类 更多的私有信息。 另外,迪米特原则要求类之间的直接联系尽量的少,两个类的访问,通过第 三个中介类来实现。
迪米特原则的潜台词是:不和陌生人说话,有事去中介。
二、工厂模式的分类:
简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,
需要修改已有代码)
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无
能为力;支持增加产品族)
GOF 在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。
核心本质: 实例化对象,用工厂方法代替 new 操作。 将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
1、无工厂模式
interface Car{
void run();
}
class Audi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
public class Client01 {
public static void main(String[] args) {
Car a = new Audi();
Car b = new BYD();
a.run();
b.run();
}
}
2、简单工厂模式
简单工厂模式,从命名上就可以看出这个模式一定很简单。它存在的目的很简单: 定义一个用于创建对象的工厂类。
interface Car {
void run();
}
class Audi implements Car {
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car {
public void run() {
System.out.println("比亚迪在跑");
}
}
//工厂类
class CarFactory {
//方式一
public static Car getCar(String type) {
if ("奥迪".equals(type)) {
return new Audi();
} else if ("比亚迪".equals(type)) {
return new BYD();
} else {
return null;
}
}
}
public class Client02 {
public static void main(String[] args) {
Car a = CarFactory.getCar("奥迪");
a.run();
Car b = CarFactory.getCar("比亚迪");
b.run();
}
}
调用者只要知道他要什么,从哪里拿,如何创建,不需要知道。分工,多出了一 个专门生产 Car 的实现类对象的工厂类。把调用者与创建者分离。
小结: 简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的 参数的不同来返回不同的实例对象。
缺点:对于增加新产品,不修改代码的话,是无法扩展的。违反了开闭原则(对 扩展开放;对修改封闭)。
3、工厂方法模式
为了避免简单工厂模式的缺点,不完全满足 OCP(对扩展开放,对修改关闭)。 工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一 个项目或者一个独立的模块而言)工厂类,而工厂方法模式有一组实现了相同接 口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模 式里不同的工厂子类来分担
工厂方法模式的主要角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应
案例一:
interface Car{
void run();
}
//两个实现类
class Audi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
//工厂接口
interface Factory{
Car getCar();
}
//两个工厂类
class AudiFactory implements Factory{
public Audi getCar(){
return new Audi();
}
}
class BydFactory implements Factory{
public BYD getCar(){
return new BYD();
}
}
public class Client {
public static void main(String[] args) {
Car a = new AudiFactory().getCar();
Car b = new BydFactory().getCar();
a.run();
b.run();
}
}
案例二:
需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类:美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】
再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
public interface Coffee {
String getName();
void addMilk();
void addSugar();
}
public interface CoffeeFactory {
Coffee createCoffee();
}
public class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
public class AmericanCoffee implements Coffee {
@Override
public String getName() {
return "AmericanCofee";
}
@Override
public void addMilk() {
System.out.println("AmericanCoffee addMilk");
}
@Override
public void addSugar() {
System.out.println("AmericanCoffee addSugar");
}
}
public class LatteCoffee implements Coffee {
@Override
public String getName() {
return "LatteCoffee";
}
@Override
public void addMilk() {
System.out.println("LatteCoffee addMilk");
}
@Override
public void addSugar() {
System.out.println("LatteCoffee addSugar");
}
}
public class CoffeeStore{
private CoffeeFactory factory;
public CoffeeStore(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCofee(String type){
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
public static void main(String[] args) {
new AmericanCoffee().addMilk();
new AmericanCoffee().addSugar();
new LatteCoffee().addMilk();
new LatteCoffee().addSugar();
}
}
总结: 简单工厂模式与工厂方法模式真正的避免了代码的改动了?没有。在简单工厂模 式中,新产品的加入要修改工厂角色中的判断语句;而在工厂方法模式中,要么 将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就像 上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。 面对这种情况,Java 的反射机制与配置文件的巧妙结合突破了限制——这在 Spring 中完美的体现了出来。
4、抽象工厂模式
抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且 抽象工厂模式是三个里面最为抽象、最具一般性的。 抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品 对象。
而且使用抽象工厂模式还要满足一下条件:
系统中有多个产品族,而系统一次只可能消费其中一族产品。
同属于同一个产品族的产品以其使用
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品
- 同等级产品:就是同类别的产品,如:美式咖啡、拿铁咖啡。
- 同一产品族:就是一个工厂生产的不同类别的一系列产品,如:拿铁咖啡、意大利甜品。
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
新增类
public interface Dessert {
void show();
}
public class Trimisu implements Dessert {
@Override
public void show() {
System.out.println("Trimisu show");
}
}
public class MathchaMouse implements Dessert {
@Override
public void show() {
System.out.println("MathchaMouse show");
}
}
public interface DessertFactory {
// 生产咖啡的功能
Coffee createCoffee();
// 生产甜品的功能
Dessert createDessert();
}
public class AmericanDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
@Override
public Dessert createDessert() {
return new MathchaMouse();
}
}
public class ItalyDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
@Override
public Dessert createDessert() {
return new Trimisu();
}
}
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
使用场景
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。
5、模式扩展
简单工厂+配置文件解除耦合
可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。
在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
实现代码:
第一步:定义配置文件
为了演示方便,我们使用properties文件作为配置文件,名称为bean.properties
american=com.itheima.pattern.factory.config_factory.AmericanCoffee
latte=com.itheima.pattern.factory.config_factory.LatteCoffee
第二步:改进工厂类
public class CoffeeFactory {
// 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
// 1,定义容器对象存储咖啡对象
private static HashMap<String, Coffee> map = new HashMap<>();
// 2,加载配置文件,只需要加载一次
static {
// 2.1 创建Properties对象
Properties p = new Properties();
// 2.2 调用p对象中的load方法进行配置文件的加载
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
// 从p集合中获取全类名并创建对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
String className = p.getProperty((String) key);
// 通过反射技术创建对象
Class clazz = Class.forName(className);
Coffee coffee = (Coffee) clazz.newInstance();
// 将名称和对象存储到容器中
map.put((String) key, coffee);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//根据名称获取对象
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
静态成员变量用来存储创建的对象,键存储的是名称,值存储的是对应的对象,
而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次
JDK源码解析-Collection.iterator方法
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("令狐冲");
list.add("风清扬");
list.add("任我行");
//获取迭代器对象
Iterator<String> it = list.iterator();
//使用迭代器遍历
while(it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
}
}
在单列集合获取迭代器的方法就使用到了工厂方法模式。我们看通过类图看看结构:
- Collection接口是抽象工厂类,ArrayList是具体的工厂类;
- Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。
- 在具体的工厂类中iterator()方法创建具体的商品类的对象。
DateForamt类中的getInstance()方法使用的是工厂模式;
Calendar类中的getInstance()方法使用的是工厂模式;
1.3 原型模式
1.3.1 概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
1.3.2 结构
原型模式包含如下角色:
- 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
接口类图如下:
1.3.3 实现
原型模式的克隆分为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java中的Object类中提供了 clone()
方法来实现浅克隆。
Java中的Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。
Realizetype(具体的原型类):
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("具体的原型对象创建完成!");
}
@Override
protected Realizetype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
PrototypeTest(测试访问类):
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype r1 = new Realizetype();
Realizetype r2 = r1.clone();
System.out.println("对象r1和r2是同一个对象?" + (r1 == r2));
}
}
运行结果:clone 原型对象的时候,不会调用构造方法,因为该对象不是通过 new 创建的
具体的原型对象创建完成!
具体原型复制成功!
对象r1和r2是同一个对象?false
1.3.4 案例
[例] 用原型模式生成“三好学生”奖状
同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。
类图如下:
java
//奖状类
public class Citation implements Cloneable {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return (this.name);
}
public void show() {
System.out.println(name + "同学:在2025学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//测试访问类
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
c1.setName("张三");
//复制奖状
Citation c2 = c1.clone();
//将奖状的名字修改李四
c2.setName("李四");
c1.show();
c2.show();
}
}
1.3.5 使用场景
以下两种情况,可以使用原型模式快捷的创建对象:
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全要求比较高。
1.3.6 扩展(深克隆)
将上面的“三好学生”奖状的案例中Citation类的name属性修改为Student类型的属性,先进性浅拷贝。代码如下:
//奖状类
public class Citation implements Cloneable,Serializable {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
void show() {
System.out.println(stu.getName() + "同学:在2025学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//学生类
public class Student implements Serializable{
private String name;
private String address;
public Student(String name, String address) {
this.name = name;
this.address = address;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
//测试类
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
Student stu = new Student("小晴", "西安");
c1.setStu(stu);
//复制奖状
Citation c2 = c1.clone();
//获取c2奖状所属学生对象
Student stu1 = c2.getStu();
stu1.setName("李四");
//判断stu对象和stu1对象是否是同一个对象
System.out.println("stu和stu1是同一个对象?" + (stu == stu1));
c1.show();
c2.show();
}
}
运行结果:stu1 和 stu是同一个对象
st和stu1是否同一个对象?true
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
stu 和 stu1 是同一个对象,就会产生将 stu1 中 name 属性值改为 “李四”,两个 Citation 对象中显示的都是李四。
这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。
这种情况需要使用深克隆,而进行深克隆需要使用对象流。
import java.io.*;
public class CitationTest {
public static void main(String[] args) throws IOException, CloneNotSupportedException, ClassNotFoundException {
Citation1 c1 = new Citation1();
Citation1.Student stu = new Citation1.Student("张三", "西安");
c1.setStu(stu);
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("E:\\idea\\project\\SSM\\src\\main\\java\\com\\atguigu\\ssm\\design\\copy.txt"));
os.writeObject(c1);
os.close();
ObjectInputStream is = new ObjectInputStream(new FileInputStream("E:\\idea\\project\\SSM\\src\\main\\java\\com\\atguigu\\ssm\\design\\copy.txt"));
Citation1 c2 = (Citation1)is.readObject();
Citation1.Student stu1 = c2.getStu();
stu1.setName("李四");
//判断stu对象和stu1对象是否是同一个对象
System.out.println("stu和stu1是同一个对象?" + (stu == stu1));
c1.show();
c2.show();
}
}
运行结果为:
stu和stu1是同一个对象?false
小晴同学:在2025学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2025学年第一学期中表现优秀,被评为三好学生。特发此状!
注意:Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。
实现 1:文件流 + 对象流
学生类(Student)同上,但是要实现 Serializable 接口。
奖状类(Citation)实现 Serializable 接口,定义一个 deepClone 方法,通过文件输出流流和对象流实现深克隆。
实现 2:字节数组流 + 对象流
学生类(Student)同上,实现 Serializable 接口。
奖状类(Citation)实现 Serializable 接口,定义一个 deepClone 方法,通过字节数组流和对象流实现深克隆。
// 1 所有对象都实现序列化的接口
// 2 自定义一个深度克隆方法deepClone, 通过文件流和对象流的方式实现对象的深度拷贝
public Citation1 deepClone() throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("copy.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("copy.txt"));) {
oos.writeObject(this);
Citation1 citation = (Citation1) ois.readObject();
return citation;
}
}
// 1 所有对象都实现序列化的接口
// 2 自定义一个深度克隆方法deepClone, 通过字节数组流和对象流的方式实现对象的深度拷贝
public Citation1 deepClone2() throws Exception {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Citation1) ois.readObject();
}
}
运行结果:
stu和stu1是同一个对象?false
张三同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
1.4 建造者模式
1.4.1 概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
这个模式适用于:某个对象的构建过程复杂的情况。
- 将部件的构造与装配分离,由 Builder 负责构造,Director 进行装配,实现了构建和装配的解耦。
- 不同的构建器,相同的装配,可以做出不同的对象。
- 相同的构建器,不同的装配顺序,也可以做出不同的对象。
- 用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
1.4.2 结构
建造者(Builder)模式包含如下角色:
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
- 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
- 产品类(Product):要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
1.4.3 实例
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产:
- 车架有碳纤维,铝合金等材质。
- 车座有橡胶,真皮等材质。
对于自行车的生产就可以使用建造者模式,类图如下:
- Bike 是产品,包含车架,车座等组件。
- Builder 是抽象建造者。
- MobikeBuilder 和 OfoBuilder 是具体的建造者。
- Director 是指挥者。
具体的代码如下:产品对象:自行车
//自行车类
public class Bike {
private String frame;
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
// 抽象 builder 类
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
//摩拜单车Builder类
public class MobikeBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("铝合金车架");
}
@Override
public void buildSeat() {
mBike.setSeat("真皮车座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//ofo单车Builder类
public class OfoBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("碳纤维车架");
}
@Override
public void buildSeat() {
mBike.setSeat("橡胶车座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//指挥者类
public class Director {
private Builder mBuilder;
public Director(Builder builder) {
mBuilder = builder;
}
public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
return mBuilder.createBike();
}
}
//测试类
public class Client {
public static void main(String[] args) {
showBike(new OfoBuilder());
showBike(new MobikeBuilder());
}
private static void showBike(Builder builder) {
Director director = new Director(builder);
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
注意:
上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类.
1.1.4 优缺点
优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
- 造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
1.4.5 使用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
1.4.6 模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
重构前代码如下:
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMainboard() {
return mainboard;
}
public void setMainboard(String mainboard) {
this.mainboard = mainboard;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
//构建Phone对象
Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");
System.out.println(phone);
}
}
上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。
重构后代码:
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
private Phone(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {}
public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public Phone build() {
return new Phone(this);}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("华硕")
.memory("金士顿")
.screen("三星")
.build();
System.out.println(phone);
}
}
重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。
1.5 创建者模式对比
1.5.1 工厂方法模式VS建造者模式
两者对比:
- 工厂方法模式注重的是整体对象的创建方式
- 建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
举个简单例子来说明两者的差异,如要制造一个超人:
- 如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人。
- 如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
1.5.2 抽象工厂模式VS建造者模式
两者对比:
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
简单的例子:
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,
- 那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
2.结构型模式
2.1装饰设计模式
1.装饰模式的概念:
装饰模式是动态的给一个对象添加一些额外的功能,就增加功能来说,装饰 模式比生成子类更为灵活。
装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象 的功能。提供比继承更多的灵活性。
装饰模式是创建一个包装对象,也就是使用装饰来包裹真实的对象。
2.装饰模式的实现方式
- 装饰对象和真实对象有相同的接口/抽象类。这样客户端对象就能以和真实对 象相同的方式和装饰对象交互。
- 装饰对象包含一个真实对象的引用(reference)。
- 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了 在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象 的设计中,通常是通过继承来实现对给定类的功能扩展。
3.适用性
需要扩展一个类的功能,或给一个类添加附加职责。
需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承 关系变的不现实。
当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩 展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种 情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
4.代码实现
/**@Description 声明一个move方法,无论变形金刚如何改变该方法始终都有,是具体构件和抽象装饰类共有的方法。
*/
interface Transform {
public void move();
}
/**
*
* @Description ConcreteComponent(具体构件):Car.java 提供了move方法的实现,运用构造函数初始化输出当前状态,它是一个可以被装饰的类。在这里Car被声明为final类型,说明不能通过继承来拓展其功能,需运用类之间的关联关系来拓展。即装饰器装饰
*/
final class Car implements Transform {
// 初始化
public Car() {
System.out.println("变形金刚->车");
}
@Override
public void move() {
System.out.println("在陆地上移动");
}
}
/**
@Description Decorator(抽象装饰类):Changer.java 定义一个抽象构件类型的transform,通过构造函数或者setter方法来给该对象赋值,同时也通过调用transform对象来实现move方法,这样可以保证原方法不被丢失,而且可以在它的子类中增加新的方法,拓展原有功能。
*/
class Changer implements Transform {
private Transform transform;
public Changer(Transform transform) {
this.transform = transform;
}
@Override
public void move() {
transform.move();
}
}
/**
@Description ConcreteDecorator(具体装饰类):这里采用的是半透明
*/
class Robot extends Changer {
public Robot(Transform transform) {
super(transform);
System.out.println("->机器人");
}
@Override
public void move() {
super.move();
say();
}
private void say() {
System.out.println("说话");
}
}
class Airplane extends Changer {
public Airplane(Transform transform) {
super(transform);
System.out.println("->飞机");
}
@Override
public void move() {
super.move();
fly();
}
private void fly() {
System.out.println("飞翔");
}
}
public class DecoratorDemo {
public static void main(String[] args) {
Transform machine = new Car();
machine.move();
Robot robot = new Robot(machine);
robot.move();
Airplane airplane1 = new Airplane(machine);
airplane1.move();
Airplane airplane2 = new Airplane(robot);
airplane2.move();
}
}
5.装饰器模式的应用场景
1、需要扩展一个类的功能。
2、动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点, 继承的功能是静态的,不能动态增删。)
6.缺点
- 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
- 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
- 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具 体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可 以改变 Component 接口,增加新的公开的行为,实现“半透明”的装饰者模式。 在实际项目中要做出最佳选择。
7.装饰模式与代理模式的对比
装饰模式:
在不改变接口的前提下,动态扩展对象的访问。
动态继承,让类具有在运行期改变行为的能力。
装饰模式,突出的是运行期增加行为,这和继承是不同的,继承是在编译期 增加行为。
强调:增强
代理模式:
在不改变接口的前提下,控制对象的访问。
从封装的角度讲,是为了解决类与类之间相互调用而由此导致的耦合关系, 可以说是接口的另外一个层引用。比如:在 a 类->b 代理->c 类这个关系中, c 类的一切行为都隐藏在 b 中。即调用者不知道要访问的内容与代理了什么 对象。
从复用的角度讲,可以解决不同类调用一个复杂类时,仅仅因较小的改变而 导致整个复杂类新建一个类。比如:a 类->c 类 1;b 类->c 类 2。
可以变为 a 类->ca 代理类->c 类;b 类->cb 代理类-c 类。
代理模式,是类之间的封装和(某方面的)复用。
强调:限制
2.3代理模式
一、什么是代理模式:
代理模式的设计动机是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。在代理模式中,代理对象主要起到一个中介的作用,用于协调与连接调用者(即客户端)和被调用者(即目标对象),在一定程度上降低了系统的耦合度,同时也保护了目标对象。但缺点是在调用者与被调用者之间增加了代理对象,可能会造成请求的处理速度变慢
二、UML结构图:
- Subject:抽象角色,声明了真实对象和代理对象的共同接口;
- Proxy:代理角色,实现了与真实对象相同的接口,所以在任何时刻都能够代理真实对象,并且代理对象内部包含了真实对象的引用,所以它可以操作真实对象,同时也可以附加其他的操作,相当于对真实对象进行封装。
- RealSubject:真实对象,是我们最终要引用的对象。
三、代码实现:
public class BeautifulGirl {
String name;
public BeautifulGirl(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后是抽象主题
public interface GiveGift {
void giveFlowers();
void giveChocolate();
void giveBook();
}
public class You implements GiveGift {
BeautifulGirl mm ;
public You(BeautifulGirl mm){
this.mm = mm;
}
public void giveBook() {
System.out.println(mm.getName() +",送你一本书....");
}
public void giveChocolate() {
System.out.println(mm.getName() + ",送你一盒巧克力....");
}
public void giveFlowers() {
System.out.println(mm.getName() + ",送你一束花....");
}
}
public class HerChum implements GiveGift{
You you;
public HerChum(BeautifulGirl mm){
you = new You(mm);
}
public void giveBook() {
you.giveBook();
}
public void giveChocolate() {
you.giveChocolate();
}
public void giveFlowers() {
you.giveFlowers();
}
}
public class LoveDemo {
public static void main(String[] args) {
BeautifulGirl mm = new BeautifulGirl("小屁孩...");
You you = new You(mm);
HerChum chum = new HerChum(you);
chum.giveBook();
chum.giveChocolate();
chum.giveFlowers();
}
}
效果:
小屁孩…,送你一本书….
小屁孩…,送你一盒千克力。。。
小屁孩…,送你一束花….
四、代理模式与装饰器模式的区别:
- (1)装饰器模式关注于在对象上动态添加新行为,而代理模式虽然也可以增加新的行为,但关注于控制对象的访问。
- (2)装饰器模式执行主体是原类;代理模式是代理原类进行操作,执行主体是代理类。
- (3)代理模式中,代理类可以对客户端隐藏真实对象的具体信息,因此使用代理模式时,我们常常在代理类中创建真实对象的实例。而装饰器模式的通常做法是将原始对象作为参数传给装饰者的构造器。也就是说。代理模式的代理和真实对象之间的对象通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。
3.行为型模式
3.1策略模式
一、背景:
在开发中经常遇到这种情况,实现某个功能有多种算法策略,我们可以根据不同环境或者条件选择不同的算法策略来完成该功能,比如查找、排序等,一种常用方式是硬编码在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过 if-else 或者 case 等条件判断语句来进行选择。但是如果需要增加新的算法策略,就需要修改封装算法类的源代码;更换查找算法,也需要修改客户端的调用代码。并且在这个类中封装了大量算法,也会使得该类代码较复杂,维护较为困难。
如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?解决方法就是使用策略模式。
二、什么是策略模式:
将类中经常改变或者可能改变的部分提取为作为一个抽象策略接口类,然后在类中包含这个对象的实例,这样类实例在运行时就可以随意调用实现了这个接口的类的行为。
比如定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换,使得算法可独立于使用它的客户而变化,这就是策略模式。
1)环境类(Context):通过 ConcreteStrategy 具体策略类来配置,持有 Strategy 对象并维护对Strategy 对象的引用。可定义一个接口来让 Strategy 访问它的数据。
(2)抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy 定义的算法。
(3)具体策略类(ConcreteStrategy): Strategy 接口的具体算法。
策略模式的优点在于可以动态改变对象的行为;但缺点是会产生很多策略类,同时客户端必须知道所有的策略类,并自行决定使用哪一个策略类;
策略模式适用用于以下几种场景:
- (1)应用程序需要实现特定的功能服务,而该程序有多种实现方式使用,所以需要动态地在几种算法中选择一种
- (2)一个类定义了多种行为算法,并且这些行为在类的操作中以多个条件语句的形式出现,就可以将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
三、代码实现:
场景如下,刘备要到江东娶老婆了,走之前诸葛亮给赵云三个锦囊妙计,说是按天机拆开能解决棘手问题。场景中出现三个要素:三个妙计(具体策略类)、一个锦囊(环境类)、赵云(调用者)。
抽象策略类(Strategy):
public interface Strategy {
public void operate();
}
三个实现类(ConcreteStrategy):
//妙计一:初到吴国
public class BackDoor implements Strategy {
@Override
public void operate() {
System.out.println("找乔国老帮忙,让吴国太给孙权施加压力,使孙权不能杀刘备");
}
}
//求吴国太开绿灯放行
public class GivenGreenLight implements Strategy {
@Override
public void operate() {
System.out.println("求吴国太开个绿灯,放行");
}
}
//孙夫人断后,挡住追兵
public class BlackEnemy implements Strategy {
@Override
public void operate() {
System.out.println("孙夫人断后,挡住追兵");
}
}
环境类(Context):
public class Context {
private Strategy strategy;
//构造函数,要你使用哪个妙计
public Context(Strategy strategy){
this.strategy = strategy;
}
public void setStrategy(Strategy strategy){
this.strategy = strategy;
}
public void operate(){
this.strategy.operate();
}
}
public class Zhaoyun {//调度者
public static void main(String[] args) {
Context context;
System.out.println("----------刚到吴国使用第一个锦囊---------------");
context = new Context(new BackDoor());
context.operate();
System.out.println("\n");
System.out.println("----------刘备乐不思蜀使用第二个锦囊---------------");
context.setStrategy(new GivenGreenLight());
context.operate();
System.out.println("\n");
System.out.println("----------孙权的追兵来了,使用第三个锦囊---------------");
context.setStrategy(new BlackEnemy());
context.operate();
System.out.println("\n");
}
}
以上就是策略模式,多种不同解决方案动态切换,起到改变对象行为的效果。
3.2模板方法模式
一、什么是模板方法模式:
模板方法是基于继承实现的,在抽象父类中声明一个模板方法,并在模板方法中定义算法的执行步骤(即算法骨架)。在模板方法模式中,可以将子类共性的部分放在父类中实现,而特性的部分延迟到子类中实现,只需将特性部分在父类中声明成抽象方法即可,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤,不同的子类可以以不同的方式来实现这些逻辑。
模板方法模式的优点在于符合“开闭原则”,也能够实现代码复用,将不变的行为转移到父类,去除子类中的重复代码。但是缺点是不同的实现都需要定义一个子类,导致类的个数的增加使得系统更加庞大,设计更加抽象。
模板方法是一个方法,那么他与普通方法有什么不同呢?模板方法是定义在抽象类中,把基本操作方法组合在一起形成一个总算法或者一组步骤的方法。而普通的方法是实现各个步骤的方法,我们可以认为普通方法是模板方法的一个组成部分。
UML结构图:
- 抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。
- 具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。
二、模式实现:
举个例子,以准备去学校所要做的工作(prepareGotoSchool)为例,假设需要分三步:穿衣服(dressUp),吃早饭(eatBreakfast),带上东西(takeThings)。学生和老师要做得具体事情肯定有所区别。
抽象类AbstractClass:
//抽象类定义整个流程骨架
public abstract class AbstractPerson{
//模板方法,使用final修改,防止子类改变算法的实现步骤
public final void prepareGotoSchool(){
dressUp();
eatBreakfast();
takeThings();
}
//以下是不同子类根据自身特性完成的具体步骤
protected abstract void dressUp();
protected abstract void eatBreakfast();
protected abstract void takeThings();
}
具体类ConcreteClass:
public class Student extends AbstractPerson{
@Override
protected void dressUp() {
System.out.println(“穿校服");
}
@Override
protected void eatBreakfast() {
System.out.println(“吃妈妈做好的早饭");
}
@Override
protected void takeThings() {
System.out.println(“背书包,带上家庭作业和红领巾");
}
}
public class Teacher extends AbstractPerson{
@Override
protected void dressUp() {
System.out.println(“穿工作服");
}
@Override
protected void eatBreakfast() {
System.out.println(“做早饭,照顾孩子吃早饭");
}
@Override
protected void takeThings() {
System.out.println(“带上昨晚准备的考卷");
}
}
public class modelDemo {
public static void main(String[] args) {
Student student = new Student();
student.prepareGotoShcool();
Teacher teacher = new Teacher();
teacher.prepareGotoShcool();
}
}