天天看点

拒绝装饰模式

拒绝装饰模式

装饰模式是委托的一种,它更注重的是对原有对象功能的扩展。所以是一个使用频率较高的模式。但是!今天我要告诉你,在一些时候,你应该拒绝它的“诱惑”

==============

背景

通常,在我们的程序中都会存在两类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确没有问题呢?你难道忘啦?这可是正经的里氏替换啊:父类出现的地方,子类都可以无缝替换。

没错,就是无缝,相对于装饰模式的改变,使用了继承,就实现了无缝的代码优化。爽不爽???

装饰模式的优点

虽然我们拒绝了装饰模式,但还是要知道它的优点的

  • 装饰模式的特点是动态。这是对比继承非常大的一个特点
  • 你可以定义非常多的装饰类,这些装饰类的维护成本较继承的成本是较低里的。
  • 装饰模式的扩展和原本的基础体是分开解耦的

装饰模式的缺点

  • 多层装饰和多层继承一样,非常容易带来维护困难的问题。
  • 装饰模式并不是绝对的优于继承。这需要充分考虑使用场景,就像我们这篇文章所讲的。

继续阅读