怪怪设计论闲谈篇:职责与解耦的矛盾

正式讨论之前,先看看这两个问题:当我们的对象所涉及的操作不断增加时,我们是否应该:Book.Save,Book.Serialize,Book.Method1,Book.Method2这样一直增加下去?或者在某个垂直的逻辑中增加其它逻辑时,不断的扩充Book.Save,要么象有的朋友说的那样分离出去,再回调?但是Book.Save有理又有在,无论数据->对象,还是对象->数据,考虑到数据和对象经常一起变化,恢复对象的状态这部分确实应该留在对象内部,同时,我也认可这本来就是对象的职责。

为了大家所谓的“低耦高聚”的目标,也为了保持职责的合理性,希望大家考虑一下,当Book没有Save时,我们除了属性赋值是不是就无路可走了?这就得那些没包含在这次讨论中的习惯性做法(比如平时对.NETFramework和ADO.NET的使用方式)包含近来,看看很多同志指出的美女走光问题,除了给美女一个电棍让美女负担起警察打击偷窥者的职责,能不能通过换件裙子来解决。

我们平时用惯了IDataReader给对象赋值,所以很多人说的那种,从外部通过属性赋值的情况就广泛存在,比如CommunityServer。如果换一个方式呢? 把IDataReader直接交给Book,然后Book自己展开数据是不是好很多呢?于是有人又说了,这跟IDataReader耦合了,不利于移植等等。或者我根本没用ADO,所以没IDataReader。后者可以通过给自己的数据操作层实现IDataReader搞定;关键是前者,前者的非法性还表现在,把ADO的接口带入了逻辑层;等等等等, 反正很多。那么为什么不能自己做一些类似IDataReader的接口,然后把ADO.NET包含的概念作为变化封装出去呢? 

在保存数据的时候也一样,不是把数据全部读出来去保存,而是让Book准备好需要保存的数据,总而言之归它管的一分不落,然后实现或者返回出一个统一的接口里面全都是要持久的数据。至于如何跑回书架上,或者被贼给偷跑了,那是别人的事。毕竟某兄弟回复说的,今天加个Cache明天加个Log后天加个Permission最后数据库都不用了的情况也确实有。这些都该Book负责吗?不是说只添加不修改吗?难道非得要求必须AOP? 我是Anders的忠实Fans,我不认为AOP解决了什么本质问题。怎么又说回职责问题了,总而言之,现在总不能有人说美女走光了吧。 

其实我们所说的方法,往往都有学习的对象,大家可以想想各种Control对于ViewState的使用,它其实就是这么一个玩意(关于Control如何使用和持久化ViewState的文章,园子里就有)。那么我们也可以这样,数据就是数据,要什么属性,而且IDataReader出来的不就是一砣类似字典的东西吗?正好直接拿字符串和我们自己定义BL层与数据层之间对接的接口对应(与IDataReader不同的是,我们定义的接口是在逻辑层中使用的,除了象一个数据集,不包含任何和数据层相关的内容),觉得不过瘾,把对应关系保存到配置文件里去。无论如何,希望让对象自己负责恢复状态,同时又不希望对象负责存取逻辑的矛盾,并非无法解决,ViewState的方式只是其中一种解决方法。

我们不妨再看看.NETFramework如何让Serializer和你的对象在没有联系的情况下,通过增加一个翟通道,即不介入对象内部逻辑,又负担了对象持久的:首先实现ISerializer这一接口,同时实现固定方法签名的构造函数。保存数据时,他让你自己打包然后从你的接口实现中获得数据对象,恢复对象时,他调用你那个特别的构造函数,把数据字典还给你,然后让你自己填充。拿Book来说,如果增加了需求,难道非得让Book自己重新实现一个Book.Serialize方法吗?鉴于一些朋友可能不清楚Serializer的工作机制,我借用一下MSDN的简单例子,同时有所改动:

 

[Serializable]
public class MyObject : ISerializable 
{
    
public int N1 { getprivate set;} //公开取,私有存
    public int N2 { getinternal set;} //公开读,内部存
    public String Str { getset;} //这些是VS 2008支持的写法,不用自己定义私有变量了

    
public MyObject()
    {
    }

    
protected MyObject(SerializationInfo info, StreamingContext context) 
    {
        
//这个特殊的构造函数会被自动调用,如果是我们自己实现,就某Manager调用
        
//其实如果没有复杂的构造函数初始化逻辑,比如给readonly变量赋值
        
//可以将SetObjectData直接实现于接口,由我们负责数据存取的部分来调用
        SetObjectData(info);
    }

    
protected virtual void SetObjectData(SerializationInfo info)
    {
        N1
= info.GetInt32("i");  //由对象自己将数据字典展开
        N2 = info.GetInt32("j");  //还原对象状态
        Str = info.GetString("k"); //这样就可以把跟对象无关的存储逻辑外包出去
    }

    [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
    
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(
"i", N1);//info可以理解为Serializer定义的数据字典格式
        info.AddValue("j", N2);//这相当于持久化的概念进入了BL层
        info.AddValue("k", Str);//所以当我们实现时,应该根据业务逻辑定义自己的数据字典
    }
}

Serializer:
BinaryFormatter formatter 
= new BinaryFormatter();

保存至fs:
formatter.Serialize(fs, a1);

从fs读取:
formatter.Deserialize(fs);

it知识库怪怪设计论闲谈篇:职责与解耦的矛盾,转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。