使用单值对象的不同表示

时间:2015-10-01 14:02:59

标签: c# .net architecture domain-driven-design value-objects

我在工业自动化领域工作。该软件将使用由每个设备的制造商设计的串行连接和协议来控制多个工业设备。我已经为常用参数(如距离,电压等)创建了“值对象”,但是,每个设备都需要不同的值表示,而UI则需要另一个。

例如,假设我需要将对象的位置调整1厘米。设备#1预计距离以米为单位,设备#2预计距离以微米为单位,设备UI预计为厘米。我计划使用MKS系统,因此我的距离值对象以米为单位存储数量。

根据Vaughn Vernon所着的实施领域驱动设计这本书,我的价值观看起来好像应该满足价值对象的特征,即:

  1. 它测量,量化或描述域中的内容。
  2. 可以保持不变。
  3. 它通过将相关属性组合为一个整体单元来对概念整体进行建模。
  4. 当测量或描述发生变化时,它是完全可更换的。
  5. 可以使用值相等来与其他人进行比较。
  6. 为其合作者提供无副作用行为
  7. 思想1:将12个常用的指标前缀添加到每个值对象类中。

    public class Distance
    {
        private readonly double quantity; // in meters
    
        public Distance(double distance)
        {
            quantity = distance;
        }
    
        public double AsCentimeters()
        {
            return quantity * 100;
        }
    }
    

    这种方法似乎不正确,因为基于前缀的计算不会改变,并且必须在多个值对象中重复。

    思想2:引入带有度量标准前缀的枚举和带有计算的基类。

    public enum SIPrefix
    {
        None, Centi 
    };
    
    public class SIUnitBase
    {
        protected readonly double quantity;
    
        public double Value(SIPrefix prefix)
        {
            switch (prefix)
            {
                case SIPrefix.Centi:
                    return quantity * 100;
                    break;
                default:
                    return quantity;
                    break;
            }
        }
    }
    
    public class Distance : SIUnitBase
    {
        public Distance(double distance)
        {
            quantity = distance;
        }
    }
    
    // ... in code ...
    Distance d = new Distance(1.0, SIPrefix.None);
    Equipment.Move(d.Value(SIPrefix.Centi));
    

    这种方法似乎既冗长又容易出错。

    思考3:创建一组扩展方法以添加所需的功能。

    public static class DistanceExtensions
    {
        public static double AsCentimeters(this Distance distance)
        {
            return distance * 100;
        }
    }
    

    这种方法似乎与思想1 一样冗长,但我只能实现特定应用所需的转换。

    我应该如何建模这个架构,以便我可以在它所期望的表示中为每个域对象和UI提供值对象?

    搜索以前的价值对象问题只显示one thread与我的问题类似。

2 个答案:

答案 0 :(得分:3)

您的价值对象的设计是好的。封装例如一个距离,并为主单元提供一个访问器(例如米)是所有有价值的对象需要。

软件本身应该只能使用这些值对象(不访问原始值)来维护有意义的模型。因此,您可能还需要对这些值对象使用加法和减法运算符,可能还有其他人。 这里的目标是VO的用户永远不需要考虑使用的特定单位(除了明显的情况,比如建筑)。

但是,不同的Equipment需要不同的表示形式的事实不是你应该用额外的访问者(或扩展方法)解决的事情,因为这会使你的VO变得混乱,因此你的模型变得不那么简洁了。 /强>

我建议您为使用您的VO的Equipment定义一个界面。然后,创建一个adapter,了解Equipment的具体单位。适配器可用于在标准化VO和Equipment的具体内容之间进行转换。

答案 1 :(得分:0)

我认为你错过了单位和转换率的概念。我不太了解c#语法,所以我只是用JavaScript编写了示例:



//This would be an enum holding conversion ratios to meters
var units = {
  cm: 0.01,
  dm: 0.1,
  m: 1,
  km: 1000,
  assertValidUnit: function(unit) {
    if (!this.hasOwnProperty(unit)) throw new Error('Invalid unit: ' + unit);
  }
};


function Distance(value, unit) {
  units.assertValidUnit(unit);
  this.value = value;
  this.unit = unit;
}

Distance.prototype = {
  constructor: Distance,
  
  in: function(unit) {
    units.assertValidUnit(unit);
    return new Distance(this.value * (units[this.unit] / units[unit]), unit);
  },

  plus: function (distance) {
      var otherDistanceValueInCurrentUnit = distance.in(this.unit).value;

      return new Distance(this.value + otherDistanceValueInCurrentUnit, this.unit);
  },

  minus: function (distance) {
      var otherDistanceValueInCurrentUnit = distance.in(this.unit).value;

      return new Distance(this.value - otherDistanceValueInCurrentUnit, this.unit);
  },

  toString: function() {
    return this.value + this.unit;
  }
};

var oneKm = new Distance(1, 'km'),
  oneM = new Distance(1, 'm'),
  twoCm = new Distance(2, 'cm');

log(oneM + ' + ' + oneKm + ' is ' + oneM.plus(oneKm));
log(oneM + ' - ' + twoCm + ' is ' + oneM.minus(twoCm).in('cm'));

convertAndLog(oneKm, 'm');
convertAndLog(oneKm, 'cm');
convertAndLog(oneM, 'dm');
convertAndLog(oneM, 'km');


function convertAndLog(distance, unit) {
  var convertedDistance = distance.in(unit),
      msg = distance + ' is ' + convertedDistance;
  
  log(msg);
}

function log(msg) {
  document.body.appendChild(document.createTextNode(msg));
  document.body.appendChild(document.createElement('br'));
}




您的Equipment可以直接与Distance打交道,并在内部使用distance.in以确保其使用正确的单位。

function Equipment(distanceUnit) {
    this.distanceUnit = distanceUnit;
}

Equipment.prototype.move = function (distance) {
    alert('Moved equipment by ' + distance.in(this.distanceUnit));
};

var equipment = new Equipment('cm');

equipment.move(new Distance(5.4, 'dm')); //Moved equipment by 54cm

它可能不是惯用的C#,但这是一次尝试:

//Values stored as unit/m * 1000 to avoid enum float limitations
public enum MeasureUnit {cm = 10, dm = 100, m = 1000, km = 1000000}

public static class MesureUnitExtension {
  public static float Ratio(this MeasureUnit e) {
    return (float)e / (float)1000.0;
  }
}

public class Measure {
    private float value;
    private MeasureUnit unit;

    public Measure(float value, MeasureUnit unit) {
        this.value = value;
        this.unit = unit;
    }

    public Measure In(MeasureUnit unit) {
        return new Measure(this.value * (this.unit.Ratio() / unit.Ratio()), this.unit);
    }

    public override string ToString() {
        return this.value.ToString() + this.unit.ToString();
    }
}