Tell Me.Don't Ask!

Leo posted @ 2011年5月08日 01:56 in OOD with tags encapsulation interface.OOD , 1446 阅读

先看一个简单的例子,假设有这样一种场景:我们需要比较有一个有大小和单位的Length对象的相等性。

通常会这样做:

1、定义Length对象.

 

public class Length
    {
        public int Value { get; private set; }
        public Unit Unit { get; private set; }

        public Length(int count, Unit unit)
        {
            Value = count;
            Unit = unit;
        }
    }

其中Unit是一个表示单位的Enum

2.override Equal 方法.

 

public override bool Equals(object obj)
        {
            if (obj is Length)
            {
                var length = obj as Length;
                if (IsEqual(length)) return true;
            }
            return false;
        }

        private bool IsEqual(Length thatLength)
        {
            var thatBaseLength = ConvertFactory.ConvertToBaseUnit(thatLength);
            var thisBaseLength = ConvertFactory.ConvertToBaseUnit(this);
            return thatBaseLength.Value == thisBaseLength.Value;
        }
   

其中ConvertFactory的作用是把当前/待比较Length对象转换成一个基准单位(Unit)的Length对象工场。来看实现:

 

public class ConvertFactory
    {
        private ConvertFactory(){}

        public static Length ConvertToBaseUnit(Length length)
        {
            IConvert convert = null;
            if (length.Unit == Unit.M)
            {
                convert = new MConvertToCM();
            }
            if(length.Unit == Unit.DM)
            {
                convert = new DMConvertToCM();
            }
           ...................
            return convert.ConvertToBase(length);
        }
    }

代码很清晰的告诉了我们单位是M或者DM的Length对象都将会被转换成为单位是M的对象。随便看一个MConvertToCM的实现:

 

public class MConvertToCM : IConvert
    {
        public Length ConvertToBase(Length length)
        {
            return new Length(length.Value*100, Unit.CM);
        }
    }

很好,完毕了。我们不仅实现了场景的功能,而且还在其中使用了多种OO技术与设计模式。似乎做的很完美(至少我第一次写出这样的代码时是这种感觉:)

但是,让我们再回头看看Length对象,两个属性都是public getter,

-------------Q&A------------------------------

为什么要公开这两个属性呢?因为我们在MConvertToCM里会用到啊!

为什么非要在MConvertToCM里用这两个属性?.......我们就这样设计的啊!

为什么要这样设计?........无语了

-------------Q&A------------------------------

好吧,问题似乎陷入了僵局,为什么我们非要把自己的属性直接暴露给其他对象?我们如果不这样做又可以有什么办法达到将自身的数据传递给其他对象呢?

参见Tell,Don't Ask这篇文章

文章中提到如果将一个对象的数据暴露出来的目的是为了在外部改变这个对象的数据/行为的做法是不合适的。

回到我们这个场景来看,Value与Unit公开的目的是为了在构建另一个自己,看清楚了这点,后面的操作也就变得比较容易了。直接上代码

 

 public interface LengthAndUnitAction
    {
        void action(int value, Unit unit);
    }

 

public class Length
    {
        private int Value { get; set; }
        private Unit Unit { get; set; }

        public Length(int count, Unit unit)
        {
            Value = count;
            Unit = unit;
        }
        .......................
        public void Execute(LengthAndUnitAction action)
        {
            action.action(Value, Unit);
        }
    }

我们把行为单独抽成一个接口,然后让Length类再调用接口的同时将内部属性传递出去。这样Value/Unit就可以变为私有变量,从而更好的起到了数据封装的作用。

Avatar_small
heihei 说:
2011年5月09日 20:02

太复杂了,看完了,没懂。

Avatar_small
xiaodao 说:
2011年9月09日 11:24

想知道最后那段代码中Length为啥要有set方法,

Avatar_small
Leo 说:
2011年9月11日 22:31

@xiaodao: 构造函数传值需要啊!


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter