设计模式之装饰者模式

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。

来解析这个定义:

  1. 动态”指的在运行时进行的,而不是在编译时,也即客户端调用时可以临时决定增加责任到对象上。
  2. 责任附加”意味着可以在不修改原有对象的情况下,增加新的行为或功能。
  3. 比继承更有弹性”,说明使用的是组合而不是继承,通过组合可以在运行时动态地添加或移除功能。弹性意味可以组合多个能力,而不需要创建大量子类。

装饰者最重要的核心是装饰者和被装饰者来自于同一个超类。相比较于策略模式、观察者模式,难以理解一点,并不是一个自然就能想到的结构。我们从源头来看看它的设计脉络。

💡
假设我们正在开发一个文档编辑器,用户可以在编辑器中输入文本,并对文本应用各种格式(如加粗、斜体、下划线等)。编辑器需要支持以下功能:用户可以动态地为文本添加或移除格式,格式可以叠加(例如,文本可以同时加粗和斜体)。

先来一个自然能想到的简单实现:

// Text 类
public class PlainText {
    private String content;

    public PlainText(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}


// Format 工具类
public class Format {
    public static void bold(PlainText text) {
        text.setContent("<b>" + text.getContent() + "</b>");
    }

    public static void italic(PlainText text) {
        text.setContent("<i>" + text.getContent() + "</i>");
    }
}

// Client
public class Client {
    public static void main(String[] args) {
        PlainText text = new PlainText("Hello, World!");

        // 动态添加格式
        Format.bold(text);    // 加粗
        Format.italic(text);  // 斜体

        System.out.println(text.getContent());
        // 输出:<i><b>Hello, World!</b></i>
    }
}

在这个实现中,优点是代码逻辑清晰,易于理解,要改动渲染顺序,需要改动Client中的代码实现。

但利用设计模式原则来审视,会有以下问题:

  1. 违法了开闭原则:如果要新增如删除线格式,需要改动Format类,添加新的方法。
  2. 强耦合:这点十分重要,需要真正做到理解,Format依赖的是具体的PlainText类,而不是依赖抽象,这与依赖倒置原则不相符,并且setContent方法表明了Format操作了PlainText的具体属性,这是一种深度耦合,当content从String变成List时,Format就需要进行改动。(注意:getContent于setContent不同,是一种松耦合

进行优化,先解决开闭原则的问题,将bold、italic方法抽离成BoldDecorate、ItalicDecorate类,当然来自于同一个超类(因为需求是要随时替换顺序),这个思路在策略模式中也用到。再解决强耦合问题,不应该耦合具体的PlainText类,而应该依赖接口Text。

public interface Text {
	String getContent();
}

public interface Decorate {
	String getContent();
}

public class PlainText implements Text {
	private String content;

	PlainText(String content) {
		this.content = content;
	}

	public String getContent() {
		retrun content;
	}
}

public class BoldDecorate implements Decorate {
	private Text text;

	BoldText(Text text) {
		this.text = text;
	}

	public String getContent() {
		return "<b>" + text.getContent() + "</b>";
	}
}

public class ItalicDecorate implements Decorate {
	private Text text;

	ItalicText(Text text) {
		this.text = text;
	}

	public String getContent() {
		return "<i>" + text.getContent() + "</i>";
	}
}

public class client {
	public static void main(String[] args) {
		Text text = new PlainText("hello world")
		Decorate decorateItalic = new ItalicDecorate(text);
		// 无法增加Bold能力,decorateItalic类型不匹配
		// Decorate decorateBold = new BoldText(?)
	}
}

看到了吗,再完成一次功能增加后,无法继续了,因为Text与Decorate是两种不同类型的接口。因此,引出了装饰模式中最重要的一个点,即装饰类与被装饰类需来自于同一个超类。

我们让ItalicDecorate、BoldDecorate也实现Text接口。

public interface Text {
	String getContent();
}

public class PlainText implements Text {
	private String content;

	PlainText(String content) {
		this.content = content;
	}

	public String getContent() {
		retrun content;
	}
}

public class BoldDecorate implements Text {
	private Text text;

	BoldText(Text text) {
		this.text = text;
	}

	public String getContent() {
		return "<b>" + text.getContent() + "</b>";
	}
}

public class ItalicDecorate implements Text {
	private Text text;

	ItalicText(Text text) {
		this.text = text;
	}

	public String getContent() {
		return "<i>" + text.getContent() + "</i>";
	}
}

public class client {
	public static void main(String[] args) {
		Text text = new BoldDecorate(new ItalicDecorate(new PlainText("hello world")))
		String content = text.getContent();
		System.out.println(content);
	}
}

看到Text text = new BoldDecorate(new ItalicDecorate(new PlainText("hello world"))) 这个嵌套效果了吗,是不是似曾相识

DataInputStream dataIn = new DataInputStream(new BufferedInputStream(new FileInputStream("example.txt")));

java io典型调用

发现没有,不学习设计模式,都看不懂源码,不清楚为什么要如此设计,如此编码。

目前的实现已经完美了吗,其实还有优化空间,BoldDecorate、ItalicDecorate都维护了text属性和构造函数代码,这些可以利用一个抽象装饰基类解决,将 text 属性和构造函数提取到基类中,避免在每个具体装饰者中重复代码。

public interface Text {
	String getContent();
}

public abstract class DecorateText implements Text {
	Text text;

	DecorateText(Text text) {
		this.text = text;
	}

	public abstract String getContent();
}

public class PlainText implements Text {
	private String content;

	PlainText(String content) {
		this.content = content;
	}

	public String getContent() {
		return content;
	}
}

public class BoldDecorate extends DecorateText {
	BoldText(Text text) {
		super(text);
	}

	public String getContent() {
		return "<b>" + text.getContent() + "</b>";
	}
}

public class ItalicDecorate extends DecorateText {
	ItalicText(Text text) {
		super(text);
	}

	public String getContent() {
		return "<i>" + text.getContent() + "</i>";
	}
}


public class client {
	public static void main(String[] args) {
		Text text = new BoldDecorate(new ItalicDecorate(new PlainText("hello world")));
		String content = text.getContent();
		System.out.println(content);
	}
}

Read more

痛风带来的思考

昨晚一罐冰啤酒下去,睡觉时就感觉脚踝隐隐发作,果然早上起床直接下不来地。跟崴脚的感觉十分相似,无法行走,只能坐在一起上滑动,公司上班也去不了了,呆呆得躺在家里,下午疼痛感加剧,整个心思都在左脚的疼痛上,没有其他任何多余的精力去关注其他事情,而此刻的阳台,乃最美人间四月天,春日的微风吹拂着阳台的花儿,温暖的阳光抛洒下来,一切都如此惬意,而我却无心欣赏。 人在健康时,生活中有好多问题,但人在不健康时,生活中只剩一个问题。 我对这句话的理解更深刻了。人是健忘的,在疫情期间、在手术期间,这种感悟其实已经很深刻了,但是病情好转之后,人还是会被日常的琐碎、工作的烦扰搅乱心绪,没有专注的去享受生活本身的美好。 幸福的秘密在于,去享受我们所拥有的,而不是顽固的去追求所没有拥有的。阳光、草木、微风,都是幸福的玩意儿,应尽情的享受。 再等两天,脚完全恢复好了,身体健健康康后,我要以更轻盈的姿态去生活,不纠结他人的看法,不执着别人的认可,关注自己的能力,享受拥有的生后。 还有一个反省,针对咖啡、酒、烟,

By 李浩

设计模式之命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,命令模式也支持可撤销的操作。 来解析这个定义: 1. “将一个请求封装为一个对象”,请求原本是一个方法,现在要封装成一个对象,说明要新增类来完成。 2. “可以用不同的请求对客户进行参数化”,说明是将命令对象作为参数进行传递。 3. “队列”说明需要维护命令多个命令的列表队列。 4. “撤销”说明有命令对象有undo撤销方法。 命令模式在设计模式中,算是一个比较不好理解的模式,很重要的原因是不清楚设计意图,不清楚不用这个模式前有何问题,这个模式带了哪些好处,能解决什么问题。 上一篇状态模式中,看到了状态模式抽离的是状态(属性),向上提成状态对象。有了这个基础,再来理解命令模式就相对简单了。命令模式抽离的是行为(方法),向上提成命令对象。 两者都通过“对象化”来解耦和扩展系统,但解决的问题不同: * 状态模式:处理对象内部状态驱动的行为变化。 * 命令模式:处理行为请求的封装与调度。 💡智能家居遥控器,假设我们有一个智能家居遥控器,可以控制 灯(Light) 和

By 李浩

设计模式之状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 来解析这个定义: 1. “内部状态”表明对象内部有一个属性来表示状态。 2. “内部状态改变时改变它的行为,对象看起来好像修改了它的类”说明状态改变后对象的行为发生了非常大的变化,不像是同一类的行为。 从目前的分析中似乎无法推导出状态模式的类图结构。 从实际的例子出来,来看看状态模式是如何演进而来。 💡我们有一个文档审批系统,文档有以下状态和转换: 1. 草稿(Draft) → 提交 → 待审批(PendingReview) 2. 待审批 → 批准 → 已发布(Published) 3. 待审批 → 拒绝 → 草稿 4. 已发布 → 撤回 → 草稿 从直觉出发,会使用条件语句实现需求逻辑。 public class Document { private String state = "DRAFT"; // 初始状态为草稿 public void submit(

By 李浩

设计模式之代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。 解析这个定义: 1. “替身”表明在客户端看来,代理类与被代理类是同一类别,对客户端来说看上去没什么区别,依然能够满足诉求。如此可以看出代理类与被代理类来自同一个超类。 2. “控制对这个对象的访问”,能够控制访问,说明前提是能够访问,才能在访问之前做这个限制,即代理持有对真实对象的引用(或能创建它)。 当然可能会质疑,用继承的方式不是也能完成目标吗,用代理类去继承被代理类,然后重写方法,加入控制逻辑。但这违背了"组合优于继承"原则,代理类与被代理类强耦合。 💡我们要开发一个图片查看器,需求如下: 1. 图片加载开销大(从磁盘或网络加载耗时),希望首次显示时才加载(延迟加载)。 2. 某些图片需要权限校验,只有授权用户才能查看。 3. 客户端代码应统一接口,无需关心是直接加载图片还是通过代理。 // 1. 抽象接口(Subject) interface Image { void display(); } // 2. 真实对象(RealSubject)

By 李浩