拒绝装饰模式
装饰模式是委托的一种,它更注重的是对原有对象功能的扩展。所以是一个使用频率较高的模式。但是!今天我要告诉你,在一些时候,你应该拒绝它的“诱惑”
==============
背景
通常,在我们的程序中都会存在两类javaBean:Entity(实体类)与Dto(数据传输类)。
其中Entity是最普通的javaBean,只有属性与setter/getter方法,通常作为数据持久化操作。
而Dto负责应用的业务逻辑中的数据传递,除属性以外,还会有一些业务逻辑方法。通常,Dto是Entity的扩展。
实际的使用情况是什么呢?我们在开发中并没有将二者区分开,而是合二为一,以Entity为基础,在上面添加额外的属性和方法。这样做有什么好处呢?
好处:
不需要维护两套类
Entity与Dto有着高度相似性,合二为一后不需要写重复的代码。
缺点:
一个本应该“干净”的javaBean混入了过多的业务逻辑,从Entity的角度,这已经不单一了。
Entity、Dto合二为一的后果就是你得到了一个臃肿的Dto
在使用了ORM框架的情况下,为了区分出需要持久化和不需要持久化的属性,不得不又引入了注解。
一个永远没有答案的问题:javaBean里究竟应不应该有计算方法?
那么分析完优缺点后,接下来,我们尝试将二者拆开。
拆开绝对不是简单拆除两套类,这是非常愚蠢的。
既然Dto是Entity的扩展,那么很快我就想到了装饰模式。功能扩展呀,没错的。
尝试
这里假设你已经对装饰模式比较了解了。
第一步:创建,如果使用装饰模式,Entity和Dto就应该有一套相同的接口。
额……javaBean的接口?难道你是说getter和setter方法? 哦~哪个傻蛋会这么做……
姑且我们傻了一回,这样做了。第二步:使用,在使用Dto的过程中,你需要再new一个Dto装饰者。然后将Entity的对象设置进去,就像这样:
//通过manager取得了user对象
UserEntity userEntity = userManager.getUser();
//要使用Dto了
IUser userDto = new UserDto(userEntity);
....
我不知道你怎么看这个写法,我觉得有点蠢。
我为自己规定的代码优化与重构守则:
- 在不增加原有代码编写复杂度的情况进行优化。
以前我就是看着难受了一点,现在你这么一搞,直接给我增加劳动量了,我肯定不愿意!!!
就这么走了两步就走不下去了~怎么办吧你说……
拒绝装饰模式
说到功能扩展,你除了装饰模式,还能想到什么?继承!!
比较喜剧性的是,在设计模式之禅这本书中,衬托装饰模式的就是继承。
继承的缺点有哪些呢?当需要扩展的情况变多以后,容易造成类爆炸。多层继承结构维护困难等等~然后呢?其实有时候你会发现,这些对你来说并不是问题。
比学会设计模式更重要的事情是知道什么时候该使用设计模式!
你真的有很多扩展的可能吗?NO~至少我开发Androd的两年以来并没有遇到这样的需求。你真的有很多继承结构吗?NO~除了android native组件,真的很少会遇到超两层继承结构的东西。
既然继承的缺点不是缺点了,那么你可以理直气壮的拒绝装饰模式了吗?
来,我们直接上代码。
案例:纪念日类
MemorialDayEntity.java 实体类。用于数据持久化的一些基本属性。
public class MemorialDayEntity {
protected int id;
protected String title;
protected String datetime;
protected String buildName;
protected String buildAccount;
protected boolean loop;
public MemorialDayEntity(){}
public MemorialDayEntity(String buildAccount, String buildName, String datetime, int id, boolean loop, String title) {
this.buildAccount = buildAccount;
this.buildName = buildName;
this.datetime = datetime;
this.id = id;
this.loop = loop;
this.title = title;
}
/**
* getter and setter
*/
}
MemorialDayDto.java Dto类。在Entity的基础上增加了许多需要临时计算的属性:在重复类型与不重复类型下与当前日期的差值,用于显示的组合名称等等。
按时
public class MemorialDayDto extends MemorialDayEntity {
private static final DateFormat DATE_FORMAT = SimpleDateFormat.getDateInstance();
private Calendar calendar = GregorianCalendar.getInstance();
private Date nowDate;
private Date targetDate;
private int dateYear;
private int dateMonth;
private int dateDay;
private String nameStr;
private long day;
public MemorialDayDto(String buildAccount, String buildName, String datetime, int id, boolean loop, String title) {
super(buildAccount, buildName, datetime, id, loop, title);
try {
nowDate = new Date();
targetDate = DATE_FORMAT.parse(datetime);
calendar.setTime(targetDate);
dateYear = calendar.get(Calendar.YEAR);
dateMonth = calendar.get(Calendar.MONTH);
dateDay = calendar.get(Calendar.DATE);
calculateDate();
} catch (ParseException e) {
e.printStackTrace();
}
}
public String getDateStr() {
return dateYear + "年" + dateMonth + "月" + dateDay + "日";
}
private void calculateDate() {
//日期计算
}
}
两个类我们设计完了。要怎么用呢?
在之前二者合二为一的项目代码基础上,不需要做任何修改,还是直接使用Dto类。
不同的是在做数据保存时。这个时候也是非常简单的。看代码:
Dao层数据保存方法:
public interface MemorialDayDao{
public void save(MemorialDayEntity entity);
....
}
class Client{
MemorialDayDto memorialDayDto = //一些列的操作后我们得到了纪念日Dto类
MemorialDayDao mDao = Dao.getMemorialDayDao();
//就这样,这里也几乎没有任何改变
mDao.save(memorialDayDto);
}
发现我们的改变了吗?就是将需要持久化的属性抽出放到父类中当做Entity。
为什么在数据保存时,Dao层需要的是Entity,我们传Dto确没有问题呢?你难道忘啦?这可是正经的里氏替换啊:父类出现的地方,子类都可以无缝替换。
没错,就是无缝,相对于装饰模式的改变,使用了继承,就实现了无缝的代码优化。爽不爽???
装饰模式的优点
虽然我们拒绝了装饰模式,但还是要知道它的优点的
装饰模式的特点是动态。这是对比继承非常大的一个特点
你可以定义非常多的装饰类,这些装饰类的维护成本较继承的成本是较低里的。
装饰模式的扩展和原本的基础体是分开解耦的
装饰模式的缺点
多层装饰和多层继承一样,非常容易带来维护困难的问题。
装饰模式并不是绝对的优于继承。这需要充分考虑使用场景,就像我们这篇文章所讲的。