装饰模式
装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态添加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用被装饰类的方法,还可以定义新的方法,以便扩充类的功能。装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,增加新的具体构件类和具体装饰类都非常方便,满足“开闭原则”的要求。
模式动机
一般有两种方式可以实现给一个类或对象增加行为:
1. 继承机制使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
2. 关联机制关联机制是更加灵活的方法,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为并扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。为了使得装饰器与它所装饰的对象对客户端来说透明,装饰器类和被装饰的类必须实现相同的接口,客户端使用时无须关心一个类的对象是否被装饰过,可以一致地使用未被装饰的对象以及装饰好的对象。我们可以在被装饰的类中调用在装饰器类中定义的方法,实现更多更复杂的功能,而且由于装饰器类和被装饰器的类实现了相同接口,已经被装饰过的对象可以继续作为新的被装饰对象进行装饰,这种透明性使得我们可以递归嵌套多个装饰,从而可以添加任意多的功能。
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前后装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。
模式定义
装饰模式(Decorator Pattern)定义:动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更加灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
模式结构
装饰模式结构图如下图所示。
装饰模式包含如下角色:
1. Component(抽象构件)抽象构件定义了对象的接口,可以给这些对象动态增加职责(方法)。抽象构件是具体构件和抽象装饰类的共同父类,它声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
2. ConcreteComponent(具体构件)具体构件定义了具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
3. Decorator(抽象装饰类)抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过改引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
4. ConcreteDecorator(具体装饰类)具体装饰类是抽象装饰类的子类,负责向构件增加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法以便扩充对象的行为。
装饰模式实例之变形金刚
1. 实例说明变形金刚在变形之前是一辆汽车,它可以在陆地上移动。当它变成机器人之后除了能够在陆地上移动之外,还可以说话;如果有需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔。
2. 实例类图通过分析,该实例类图如下。
3. 实例代码及解释(1) 抽象构件类Transform(变形金刚类)
- class Transform
- {
- public:
- virtual void move() = 0;
- };
Transform是抽象构件类,在其中声明了move()方法,无论变形金刚如何改变,这个方法都是必须具有,它是具体构件与装饰器共用的方法。
(2) 具体构件类(汽车类)
- class Car : public Transform
- {
- public:
- Car()
- {
- printf("变形金刚是一辆车\n");
- }
- virtual void move() override
- {
- printf("它在陆地上移动\n");
- }
- };
Car是Transform的子类,它是具体构件类,提供了move()方法的实现,它是一个可以被装饰的类。
(3) 抽象装饰类(变化类)
- class Changer : public Transform
- {
- private:
- Transform* m_pTransform;
- public:
- Changer(Transform* transform)
- {
- m_pTransform = transform;
- }
- virtual void move() override
- {
- m_pTransform->move();
- }
- };
Changer是抽象装饰类,它是所有具体装饰类的父类,同时它也是抽象构件的子类。Changer类是装饰模式的核心,它定义了一个抽象构件类型的对象transform,可以通过构造函数或者Setter方法来给该对象赋值,在本实例中使用的是构造函数,并且它也实现了move()方法,但是它通过调用transform对象的move()方法来实现。这样可以保证原有方法不丢失,而且可以在它的子类中增加新的方法,扩展原有对象的功能。
(4) 具体装饰类Robot(机器人类)
- class Robot : public Changer
- {
- public:
- Robot(Transform* transform) : Changer(transform)
- {
- printf("它变成了机器人\n");
- }
- void say()
- {
- printf("它说话\n");
- }
- };
Robot类是Changer类的子类,它继承了在Changer中定义的方法,还可以增加新的方法,也就是说它既可以调用原有对象的方法,又可以对其进行扩充,为其增加新的职责。
4. 辅助代码测试代码如下:
- int main()
- {
- Transform* camaro = new Car();
- camaro->move();
- printf("-----------------------------------\n");
- Robot* bumblebee = new Robot(camaro);
- bumblebee->move();
- bumblebee->say();
- return 0;
- }
在测试代码中,首先对Car进行实例化,得到一个camaro对象,可以调用其方法move()方法实现移动,然后将该对象作为参数注入到Robot类的构造函数中,用于创建一个bumblebee对象时只能通过Robot,因为say()方法未在Transform中声明。也就是说对于具体构件可以通过抽象构件来定义,但是对于具体装饰者不能通过抽象构件来定义,对于客户端来说具体构件是透明的,而具体装饰者是不透明的,这称为半透明装饰模式,在模式扩展部分将一步讲解。
装饰模式实例之多重加密系统
1. 实例说明某系统提供了一个数据加密功能,可以对字符串进行加密。最简单的加密算法通过对字母进行移位来实现,同时还提供了稍复杂的逆向输出加密,还提供了更为高级的求模加密。用户先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密之后的结果使用其他加密算法进行二次加密,当然也可以进行第三次加密。现使用装饰模式设计该多重加密系统。
2. 实例类图 3. 实例代码及解释(1) 抽象构件类Cipher(抽象加密类)
- class Cipher
- {
- public:
- virtual string& encrypt(string& plainText) = 0;
- };
Cipher是抽象构件类,它声明了加密方法encrypt(),该方法参数为待加密的字符串(明文),返回加密之后的字符串(密文)。
(2) 具体构件类SimpleCipher(简单加密类)
- class SimpleCipher : public Cipher
- {
- public:
- virtual string encrypt(string& plainText) override
- {
- string str;
- for (int i = 0; i < plainText.length(); i++)
- {
- char c = plainText[i];
- if (c >= 'a' && c <= 'z')
- {
- c += 6;
- if (c > 'z')
- c -= 26;
- if (c < 'a')
- c += 26;
- }
- if (c >= 'A' && c <= 'Z')
- {
- c += 6;
- if (c > 'Z')
- c -= 26;
- if (c < 'A')
- c += 26;
- }
- str.append(1, c);
- }
- return str;
- }
- };
SimpleCipher是最简单的加密算法类,它是具体构件类,以凯撒加密的方式实现加密方法encrypt()。
(3) 抽象装饰类CipherDecorator(加密装饰类)
- class CipherDecorator : public Cipher
- {
- private:
- Cipher* m_cipher;
- public:
- CipherDecorator(Cipher* cipher)
- {
- m_cipher = cipher;
- }
- virtual string encrypt(string& plainText) override
- {
- return m_cipher->encrypt(plainText);
- }
- };
CipherDecorator是加密装饰类,它定义了一个抽象构件Cipher类型的对象m_cipher,并在其encrypt()调用m_cipher对象的encrypt()方法。
(4) 具体装饰类ComplexCipher(复杂加密类)
- class ComplexCipher : public CipherDecorator
- {
- public:
- ComplexCipher(Cipher* cipher) : CipherDecorator(cipher)
- {
- }
- virtual string encrypt(string& plainText) override
- {
- string result = CipherDecorator::encrypt(plainText);
- result = reverse(result);
- return result;
- }
- string reverse(string& text)
- {
- string str;
- for (int i = text.length() - 1; i >= 0; i--)
- {
- str += text.substr(i, 1);
- }
- return str;
- }
- };
ComplexCipher是复杂加密类,它是抽象装饰类的子类,是具体装饰类,它有一个新增方法reverse()用于实现逆向输出,它继承并覆盖了抽象装饰类的encrypt()方法,在它的encrypt()方法中,调用了父类的encrypt()方法,并通过新增的reverse()方法对加密之后的字符串进行进一步处理。
4. 辅助代码测试代码如下:
- int main()
- {
- string password = "sunnyLiu";
- string cpassword;
- Cipher* sc, * cc;
- sc = new SimpleCipher();
- cpassword = sc->encrypt(password);
- cout << cpassword << endl;
- cc = new ComplexCipher(sc);
- cpassword = cc->encrypt(password);
- cout << cpassword << endl;
- return 0;
- }
运行程序,输出结果如下:
- yatteRoa
- aoRettay
在测试代码中可以使用抽象构件定义具体构件SimpleCipher对象sc和具体装饰对象cc,其中cc是具体装饰类ComplexCipher类型的对象。在实例化具体装饰类对象时可以将Cipher类型的对象注入其构造函数,这个对象可以是具体构件对象,也可以是已经装饰过的具体装饰类对象,因为它们都是Cipher的子类对象。
在客户端可以通过抽象构件定义所有的具体构件对象和具体装饰对象,它们都实现了在Cipher中声明的抽象方法encrypt(),在具体构件类中新职责的增加在encrypt()内部实现,因此对于客户端来说具体构件与具体装饰类是一致的,都可以通过抽象构件来定义,这称为透明装饰模式,在模式扩展部分将进一步学习。
装饰模式的简化
大多数情况下,装饰模式的实现比标准的结构图要简单,可以对装饰模式进行简化,在简化过程中需要注意如下几个问题:
(1)一个装饰类的接口必须与被装饰类的接口保持相同。对于客户端来说,无论是装饰之前的对象还是装饰之后的对象都可以同等对待。
(2)尽量保持具体构件类Component作为一个“轻”类,也就是说不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类对其进行扩展。
(3)如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类。如果只有一个具体装饰类,那么就没有必要设计一个单独的抽象装饰类,可以把抽象装饰类和具体装饰类的职责合并在一个类中。
总结和思考
在学习的过程中,我在形式上来看装饰模式是在实例化的时候将一个类对象作为参数保存。而这个保存的类是有自己的“特点”的,它和实例化的类拥有相同的父类,拥有相同的方法,从而达到了装饰的目的。感觉特别要理解一下嵌套装饰的用法,日后还需要好好理解抽象装饰类的作用。