出处:装饰模式
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
在文章浅析 Decorator 模式,兼谈 CDI Decorator 注解的基础上我对代码稍微进行了一点修改,但是基本思路是一致的。以装修毛坯房(刷墙和安装地板)为例,去理解装饰模式。
首先,定义一个接口Room
。
public interface Room {
public void decorate();
}
定义一个毛坯房类,基本属性为房间名,毛坯房没有装修,所以毛坯房的decorate中为空。
public class BlankRoom implements Room {
private String mRoomName;
public BlankRoom(String roomName){
mRoomName = roomName;
}
@Override
public void decorate() {
}
}
定义抽象装饰类。
public abstract class RoomDecorator implements Room {
protected Room mRoom;
public RoomDecorator(Room room){
mRoom = room;
}
@Override
public void decorate(){
mRoom.decorate();
}
}
定义具体装饰类,例如给房间刷墙就定义PaintDecorator ,安装地板就定义为FloorDecorator。
public class PaintDecorator extends RoomDecorator {
private static final String TAG = "PaintDecorator";
public PaintDecorator(Room room) {
super(room);
}
@Override
public void decorate() {
super.decorate();
paint();
}
private void paint(){
Log.d(TAG," 装饰: " + "Paint");
}
}
public class FloorDecorator extends RoomDecorator {
private static final String TAG = "FloorDecorator";
public FloorDecorator(Room room) {
super(room);
}
@Override
public void decorate() {
super.decorate();
floor();
}
private void floor(){
Log.d(TAG, " 装饰: " + "Floor");
}
}
现同时对3间房进行装修,装修方式为:1.刷墙 2.装地板 3.刷墙 + 装地板。使用装饰模式去装修房子如下。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private BlankRoom mBlankRoom;
private PaintDecorator mPaintDecorator;
private FloorDecorator mFloorDecorator;
private BlankRoom mBlankRoom1;
private BlankRoom mBlankRoom2;
private Room mRoom;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,"装修房间0");
mBlankRoom = new BlankRoom("房间0");
mPaintDecorator = new PaintDecorator(mBlankRoom);
mPaintDecorator.decorate();
Log.d(TAG,"装修房间1");
mBlankRoom1 = new BlankRoom("房间1");
mFloorDecorator = new FloorDecorator(mBlankRoom1);
mFloorDecorator.decorate();
Log.d(TAG,"装修房间2");
mBlankRoom2 = new BlankRoom("房间2");
mRoom = new FloorDecorator(new PaintDecorator(mBlankRoom2));
mRoom.decorate();
}
}
D/MainActivity: 装修房间0
D/PaintDecorator: 装饰: Paint
D/MainActivity: 装修房间1
D/FloorDecorator: 装饰: Floor
D/MainActivity: 装修房间2
D/PaintDecorator: 装饰: Paint
D/FloorDecorator: 装饰: Floor
1.png
房间2实现刷墙跟装地板的关键在于PaintDecorator 和FloorDecorator 中的decorate调用了super.decorate(),以及RoomDecorator中的mRoom.decorate();设计模式基本上对我来说最大的问题就是由于对设计模式的不够了解造成程序的可读性下降。
如果不使用装饰模式的话,直接对BlankRoom进行修改的话代码如下。
首先定义3个不同的类,分别实现3种不同的装饰。
public class PaintRoom {
private String mRoomName;
public PaintRoom(String roomName){
mRoomName = roomName;
}
public void decorate() {
Log.d("MainActivity"," 装饰: " + "Paint");
}
}
public class FloorRoom {
private String mRoomName;
public FloorRoom(String roomName){
mRoomName = roomName;
}
public void decorate() {
Log.d("MainActivity"," 装饰: " + "Floor");
}
}
public class PainAndFloorRoom {
private String mRoomName;
public PainAndFloorRoom(String roomName){
mRoomName = roomName;
}
public void decorate() {
Log.d("MainActivity"," 装饰: " + "PainAndFloor");
}
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private PaintRoom mPaintRoom;
private FloorRoom mFloorRoom;
private PaintAndFloorRoom mPaintAndFloorRoom;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPaintRoom = new PaintRoom("房间0");
mPaintRoom.decorate();
mFloorRoom = new FloorRoom("房间1");
mFloorRoom.decorate();
mPaintAndFloorRoom = new PaintAndFloorRoom("房间2");
mPaintAndFloorRoom.decorate();
}
}
D/MainActivity: 装饰: Paint
D/MainActivity: 装饰: Floor
D/MainActivity: 装饰: PainAndFloor
当涉及到的功能较少的时候,设计模式并没有什么优势。我们现在来考虑装修过程中需求。例如
1.装水电
2.安装门窗
3.安装热水器
4.安装洗衣机
5.摆放沙发
6.摆放床
7.摆放衣柜
8.摆放桌椅板凳
这里有8种需求,加上前面2种,一共10种。现对其进行排列组合。一共有2的10次方-1 = 1023种组合方式,如果不使用设计模式,全靠定义不同的类去实现这些不同的装修的话,就需要定义1023个类。
但是,如果使用装饰模式的话,则再之前的基础上,再新增8个Decorator类即可。
设计模式的使用增加了程序的复杂度,减少了类的定义数量。设计模式需结合复杂情况联系实际去理解。
尽量靠自己回忆写一遍代码,这样更能理解。
这里的Decorator
模式例子是用的一个interface
: Room
,其实也可以用abstract class
,Android中的Context
用的是abstract class
,它也是使用的装饰模式,Decorator
模式中的抽象部分可以是interface
,有可以是abstract class
。
关于abstract class和interface的对比理解,可以参考这篇文章:深入理解abstract class和interface,这篇文章说的很详细了。
出处:深入理解abstract class和interface
考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:
使用abstract class方式定义Door:
abstract class Door {
abstract void open();
abstract void close();
}
使用interface方式定义Door:
interface Door {
void open();
void close();
}
出处:深入理解abstract class和interface
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。
如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。
解决方案一:
简单的在Door的定义中增加一个alarm方法,如下:
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}
或者
interface Door {
void open();
void close();
void alarm();
}
那么具有报警功能的AlarmDoor的定义方式如下:
class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}
或者
class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }
}
出处:深入理解abstract class和interface
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。
解决方案二:
既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
出处:深入理解abstract class和interface
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。
结论
abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。
简单来说就是,假如存在类A和B,A和B都有函数c(),现需将c抽象提取。
class A{
void c(){}
}
class B{
void c(){}
}
如果B is A(例如B是苹果,A是水果,函数c为显示数量,现在有1个苹果,3个梨子,现假设需求为描述苹果数量为1,水果数量为4这一场景),那么就使用abstract class,抽象c()。
class A extends D{
@Override
void c(){
//打印出数量4
}
}
class B extends D{
@Override
void c(){
//打印出数量1
}
}
abstract class D{
abstract void c()
}
如果B like A,就是B是苹果,A是梨子。都是水果,有相似之处,但是不是同一对象。现假设需求为描述苹果数量为1,梨子数量为2这一场景。由于是B like A的关系,所以使用interface抽象。这里伪代码就不写了。
下面以Student 为例简单理解抽象abstract class时使用装饰模式的情况。
假设在没有使用装饰模式的情况下。现有一类Student ,基本属性为学生姓名,在学校学习,需求为打印出xx学生在,在学校学习这个log。
public class Student{
private String mStudentName;
public Student(String studentName){
mStudentName = studentName;
}
public void studyInSchool() {
Log.d("MainActivity", mStudentName + "studyInSchool");
}
}
现需增加学生班级属性,并打印出班级学生班级。直接改变Student或新增一个类,这里先直接改变。
public class Student{
private String mStudentName;
private String mClassName;
public Student(String studentName){
mStudentName = studentName;
}
public Student(String studentName,String className){
mStudentName = studentName;
mClassName = className;
}
public void studyInSchool() {
Log.d("MainActivity", mStudentName + "studyInSchool");
}
public void studentClass() {
Log.d("MainActivity", " studentClass = "+ mClassName);
}
}
如果现再次改变需求,只需要学生姓名和老师姓名,并且需要在studyInSchool中将其打印出来(需改变studyInSchool函数内容)。那么则需要新增一个新的学生类(这里不考虑将这些名字相关属性抽象到一个bean类,因为我暂时没有想到姓名无关的属性,这里不是很好的例子,只能强制想象成他们之间是无关属性)。
public class StudentA {
private String mStudentName;
private String mTeacherName;
public StudentA(String studentName,String teacherName){
mStudentName = studentName;
mTeacherName = teacherName;
}
public void studyInSchool() {
Log.d("MainActivity", mStudentName + "studyInSchool");
}
private void teacherName() {
Log.d("MainActivity", " mTeacherName = "+ mTeacherName);
}
}
以上就是不使用装饰模式的情况下,当需求发生变化的时候需要作出的改变。
如果使用装饰模式的话。首先将函数抽象。由于StudentA本质是Student 所以这里使用abstract class。
public abstract class Person {
public abstract void studyInSchool();
}
public class Student extends Person{
private String mStudentName;
public Student(String studentName){
mStudentName = studentName;
}
@Override
public void studyInSchool() {
Log.d("MainActivity", mStudentName + "studyInSchool");
}
}
public class PersonDecorator extends Person{
private Person mPerson;
public PersonDecorator(Person person){
mPerson = person;
}
@Override
public void studyInSchool() {
mPerson.studyInSchool();
}
}
public class ClassDecorator extends PersonDecorator{
private String mClassName;
public ClassDecorator(Person person,String className){
super(person);
mClassName = className;
}
@Override
public void studyInSchool() {
super.studyInSchool();
studentClass();
}
private void studentClass() {
Log.d("MainActivity", " studentClass = "+ mClassName);
}
}
public class TeacherDecorator extends PersonDecorator {
private String mTeacherName;
public TeacherDecorator(Person person,String teacherName) {
super(person);
mTeacherName = teacherName;
}
@Override
public void studyInSchool() {
super.studyInSchool();
teacherName();
}
private void teacherName() {
Log.d("MainActivity", " mTeacherName = "+ mTeacherName);
}
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Student mStudent;
private Student mStudent1;
private ClassDecorator mClassDecorator;
private TeacherDecorator mTeacherDecorator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStudent = new Student("张三");
mClassDecorator = new ClassDecorator(mStudent,"三年一班");
mClassDecorator.studyInSchool();
mStudent1 = new Student("李四");
mTeacherDecorator = new TeacherDecorator(mStudent1,"李xx");
mTeacherDecorator.studyInSchool();
}
}
log如下。
D/MainActivity: 张三studyInSchool
D/MainActivity: studentClass = 三年一班
D/MainActivity: 李四studyInSchool
D/MainActivity: mTeacherName = 李xx
装饰模式,做到了在不改变Student这个类的情况下,扩展功能。
设计模式的作用,就是当需求发生变化的时候,尽量减少类的改变。
参考链接:
浅析 Decorator 模式,兼谈 CDI Decorator 注解
设计模式详解——装饰者模式
深入理解abstract class和interface
继承和组合的区别
装饰模式
装饰者模式之Context应用(二)
网友评论