2015-10-01 4 views
1

Я работаю в области промышленной автоматизации. Это программное обеспечение будет контролировать несколько частей промышленного оборудования, используя последовательные соединения и протоколы, разработанные изготовителем каждого оборудования. Я создал объекты Value для общих параметров, таких как Distance, Voltage и т. Д., Однако каждая единица оборудования ожидает другого представления значения, а пользовательский интерфейс - еще один.Использование различных представлений объекта с одним значением

В качестве примера предположим, что мне нужно отрегулировать местоположение объекта на 1 см. Оборудование №1 ожидает, что расстояния будут в метрах, оборудование №2 ожидает, что расстояния будут находиться в микронах, а пользовательский интерфейс ожидает сантиметры. Я планирую использовать систему MKS, поэтому мой объект Value Value хранит количество в метрах.

Из книги Реализация домена Driven Design по Вона Вернон, мои ценностные объекты, кажется, как будто они должны удовлетворять характеристики стоимости объекта, как он описывает, а именно:

  1. Он измеряет, квантифицирует или описывает вещь в домене.
  2. Его можно сохранить неизменным.
  3. Он моделирует концептуальное целое, составляя связанные атрибуты как единое целое.
  4. Он полностью заменяется при изменении измерения или описания.
  5. Его можно сравнить с другими с помощью равенства ценности.
  6. Он поставляет коллаборационистов с побочным эффектом, свободной от поведения

Мысль 1: Добавить двенадцать общих метрических префиксов каждого класса Value Object.

public class Distance 
{ 
    private readonly double quantity; // in meters 

    public Distance(double distance) 
    { 
     quantity = distance; 
    } 

    public double AsCentimeters() 
    { 
     return quantity * 100; 
    } 
} 

Этот подход представляется неправильным, так как расчеты, основанные на приставках не изменится и будет дублироваться на нескольких объектах Value.

Мысль 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, но я мог бы осуществить только преобразования, мне нужно для конкретного применения.

Как смоделировать эту архитектуру, чтобы я мог предоставить каждому объекту домена и пользовательскому интерфейсу объект Value в ожидаемом представлении?

Поиск только вопросов по объекту с ценными данными выявил только one thread, аналогичный моей проблеме.

+0

Я могу пойти с ** Мысль 3 **. Обратите внимание, что это просто проблема в том, как отображать результаты для пользователя, и он нуждается в еще одном параметре (единица используется) в диапазоне o know units (enum?). Пользовательский интерфейс должен просто отображать значение на основе сохраненного значения и единицы, используемой для этого свойства. – jean

+0

** Мысль 4 ** - используйте F # [Единицы измерения] (http://fsharpforfunandprofit.com/posts/units-of-measure /), если язык переключения по-прежнему является опцией в этой точке – guillaume31

+1

@ guillaume31 Не уверен, что стоит переключить язык для такой простой проблемы. – plalx

ответ

3

Конструкция ваших объектов значений хорошо. Инкапсулирование, например. расстояние и предоставление одного аксессуара для основного блока (например, метров) - это все потребности объекта стоимости.

Самостоятельное программное обеспечение должно иметь возможность использовать только эти объекты значений (без доступа к необработанному значению) для поддержания значимой модели. Таким образом, вам, скорее всего, понадобятся операторы сложения и вычитания на этих объектах значений, возможно, и другие. Целью здесь является то, что пользователям VO никогда не нужно думать о конкретном блоке (за исключением очевидных случаев, таких как конструкция).

Но тот факт, что разные Equipment требует разных представлений, - это не то, что вам следует решать с помощью дополнительных аксессуаров (или методов расширения), , потому что это мутирует ваши VO, и в результате ваша модель становится менее кратким.

Предлагаю вам определить интерфейс для вашего Equipment, который использует ваши VO. Затем создайте adapter, который знает об определенных единицах Equipment. Адаптер можно использовать для преобразования между стандартизованными VO и спецификацией Equipment.

+0

Я считаю, что ваш ответ наилучшим образом отвечает духу моего вопроса с кратким архитектурным решением. Спасибо. – Noren

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(); 
    } 
} 
Смежные вопросы