Как и для других элементов UML, интерпретировать свойства в программе можно по-разному. Наиболее распространенным представлением является поле или свойство языка программирования. Так, класс Order Line (Строка заказа), показанный на рис. 3.1, мог бы быть представлен в Java следующим образом:
public class OrderLine...
private int quantity;
private Money price;
private Order order;
private Product product
В языке, подобном С#, который допускает свойства, это могло бы выглядеть так:
public class OrderLine...
public int Quantity;
public Money Price;
public Order Order;
public Product Product;
Обратите внимание, что атрибут обычно соответствует открытым (public) свойствам в языке, поддерживающем свойства, но соответствует закрытым (private) полям в языке, в котором такой поддержки нет. В языке без свойств с полями можно общаться посредством методов доступа (получение и установка). У атрибута только для чтения не будет метода установки (в случае полей) или операции установки (в случае свойства). Учтите, что если свойству не присвоить имя, то в общем случае ему будет назначено имя целевого класса.
|
|
Применение закрытых полей является интерпретацией, ориентированной сугубо на реализацию. Интерпретация, ориентированная в большей степени на интерфейс, может быть акцентирована на методах доступа, а не на данных. В этом случае атрибуты класса Order Line могли бы быть представлены следующими методами:
public class OrderLine...
private int quantity;
private Product product;
public int getQuantity () {
return quantity;
}
public void setQuantityfint quantity) {
this.quantity = quantity;
}
public Money getPrice() {
return product.getPrice().multiply(quantity);
}
Здесь нет поля для цены - ее значение вычисляется. Но поскольку клиенты класса Order Line заинтересованы в этой информации, она выглядит как поле. Клиенты не могут сказать, что является полем, а что вычисляется. Такое сокрытие информации представляет сущность инкапсуляции.
Если атрибут имеет несколько значений, то связанные с ним данные представляют собой коллекцию. Поэтому класс Order (Заказ) будет ссылаться на коллекцию классов Order Line. Поскольку эта кратность упорядочена (ordered), то и коллекция должна быть упорядочена (например, List в Java или IList в.NET). Если коллекция не упорядочена, то, строго говоря, она не должна иметь ярко выраженного порядка, то есть должна быть представлена множеством, но большинство специалистов реализуют неупорядоченные атрибуты также в виде списков. Некоторые разработчики используют массивы, но поскольку UML подразумевает неограниченность сверху, то я почти всегда для структуры данных применяю коллекцию.
Многозначные свойства имеют интерфейс, отличный от интерфейса свойств с одним значением (в Java):
class Order {
private Set lineltems = new HashSet();
public Set getLineltems() {
return Collections. unrnodifiableSet(llneltems);
|
|
}
public void addLineltem (Orderltem arg) {
lineltems,add (arg);
}
public void removeLineItem (Orderltem arg) {
lineltems.remove(arg);
}
В большинстве случаев значения многозначных свойств не присваиваются прямо; вместо этого применяются методы добавления (add) и удаления (remove). Для того чтобы управлять своим свойством Line Items (Позиции заказов), заказ должен контролировать членство этой коллекции; поэтому он не должен передавать незащищенную коллекцию. В таких случаях я использовал представителя защиты, чтобы заключить коллекцию в оболочку только для чтения. Можно также реализовать необновляемый итератор или сделать копию. Конечно, так клиентам удобнее модифицировать объекты-члены, но они не должны иметь возможность напрямую изменять саму коллекцию.
Поскольку многозначные атрибуты подразумевают коллекции, то практически вы никогда не увидите классы коллекций на диаграмме класса. Их можно увидеть только на очень низком уровне представления диаграмм самих коллекций.
Необходимо крайне остерегаться классов, являющихся не чем иным, как коллекциями полей и средствами доступа к ним. Объектно-ориентированное проектирование должно предоставлять объекты с богатым поведением, поэтому они не должны просто обеспечивать данными другие объекты. Если данные запрашиваются многократно с помощью средств доступа, то это сигнал к тому, что такое поведение должно быть перенесено в объект, владеющий этими данными.
Эти примеры также подтверждают тот факт, что между UML и программой нет обязательного соответствия, однако есть подобие. Соглашения, принятые внутри команды разработчиков, приведут к более полному соответствию.
Независимо от того, как реализовано свойство - в виде поля или как вычисляемое значение, оно представляет нечто, что объект может всегда предоставить. Не следует прибегать к свойству для моделирования транзитного отношения, такого, когда объект передается в качестве параметра во время вызова метода и используется только в рамках данного взаимодействия.