#design-patterns #hierarchy
#шаблоны проектирования #иерархия
Вопрос:
Я моделирую класс документа для системы. Документ может быть одного из двух типов: in или out.
Если тип находится в, у документа есть отправитель. Отправителем может быть один из двух типов: физическое лицо или компания.
Если тип отсутствует, у документа есть получатель. Получатель может быть одного из трех типов: человек, компания, отдел.
Я не уверен, было бы лучше использовать свойство с перечислением для типа документа или использовать иерархию с базовым классом документа и двумя классами для каждого типа документа.
Для отправителя и получателя я не уверен, что иерархия будет хорошим вариантом, потому что три типа не имеют ничего общего (человек, компания, отдел) и как избежать неверного отправителя.
Было бы хорошо, если бы вы могли дать мне несколько советов о том, как моделировать класс document, или если вы можете рассказать мне о некоторых шаблонах проектирования, которые я должен использовать.
Заранее спасибо.
Есть только несколько различий между in и out, одни и те же поля, за исключением sender и receiver. Кроме того, поведение такое же, с небольшими изменениями.
У отправителя и получателя нет поведения, единственное, что им нужно сделать, это содержать правильный объект, например, отправитель может содержать человека или компанию, но не отдел, потому что отделы не являются допустимыми отправителями. Кроме того, если отправитель содержит человека, он не может содержать компанию, потому что принимается только один отправитель.
Основная проблема заключается в том, как прочитать отправителя или получателя, когда я получаю документ, и мне нужно прочитать эти данные. Например, если мне нужно прочитать отправителя, и я использую перечисление с типом отправителя, я должен выполнить код, подобный этому, если отправитель ==person прочитал person и назначил его person, другой прочитал company и назначил company. Если я использую наследование, как я избегаю использования приведения или как я узнаю, является ли отправителем человек или компания без такого большого количества кода или приведения. Еще раз спасибо.
Комментарии:
1. Какие операции вы планируете выполнять с этими документами? Как они зависят от входа / выхода и т.д.
2. @Fdo. — Я повторяю вопрос @Andrey’s. В частности, когда у вас есть документ, обычно ли вы знаете во время компиляции, является ли это входящим документом или исходящим? Или вы будете в основном обрабатывать вид документа (ввод / вывод) во время выполнения?
3. Андрей, я должен сохранить информацию о документе после некоторых проверок в зависимости от типа, отправителя или получателя. Кроме того, я должен получить документ.
4. Стивен, я знаю это во время выполнения.
5. @Andrey, я должен сохранить информацию о документе после некоторых проверок в зависимости от типа, отправителя или получателя. Также я должен получить документ. Спасибо.
Ответ №1:
Если вы используете язык, который позволяет объектам реализовывать интерфейсы, то это был бы хороший способ справиться со сложными отношениями типов.
ISender может быть реализован отдельным лицом и компанией. IReceiver может быть реализован отдельным лицом, компанией и отделом.
Таким образом, документы могли содержать ссылку на получателя, даже если эти три типа не имеют ничего общего.
Для двух типов документов это во многом зависит от того, какой объем функциональности будет разделен между ними. Если это none, то нет смысла вообще иметь отношения. Если они обладают большой функциональностью, то, возможно, имеет смысл включить абстрактный базовый класс для ее реализации. Если они полностью (или почти) одинаковы, то хорошей идеей может быть единый класс с флагом ввода / вывода.
Комментарии:
1. Спасибо за ваш ответ. Согласно вашему совету, я буду использовать перечисление для типа документа, потому что между входом и выходом нет больших различий.
Ответ №2:
Лично я не вижу особых преимуществ в моделировании в виде иерархии класса document, потому что очень мало различий между входящими и исходящими документами. Особенно учитывая, что если
Если тип в документе имеет отправителя.
Даже если это неявно, у него также есть получатель (вы). И то же самое для out document. Итак, насколько я могу судить из предоставленной вами информации, я бы использовал enum для различения как документов (входящих и исходящих), так и типов (person, company, department).
Старайтесь по возможности следовать принципу kiss.
В любом случае вы также должны учитывать тип операций, которые вы собираетесь выполнять с документами, и какие дополнительные данные они должны хранить. Если вы видите, что входящие и исходящие документы могут увеличивать свои различия по мере роста вашего приложения, поэтому введение иерархии может быть хорошей идеей. (Даже в этом случае я буду разделять типы, используя для их хранения некоторую структуру, подобную перечислению)
Редактировать:
Что касается вашего комментария: возможно, они есть. Я не знаю, какой язык вы планируете использовать. Но, например, в Java каждая сущность (person, company ..) может быть классом, реализующим интерфейс (например, объект вызова интерфейса). Затем, используя generics, вы можете создать экземпляр своего класса, заставляя универсальный тип быть реализацией объекта интерфейса. Что-то вроде:
public interface Entity{...}
public class Document<T implements Entity>{}
Комментарии:
1. Спасибо за ваш ответ. Согласно вашему совету, я буду использовать перечисление для типа документа, потому что между входом и выходом нет больших различий. Для отправителя и получателя Нормально, когда я сохраняю документ, но когда я получаю документ, мне приходится писать некоторый код каждый раз, когда мне нужно прочитать отправителя или получателя. Например, если отправитель == person, то прочитайте person else, а затем прочитайте company. Я не знаю, есть ли какой-либо способ избежать такого кода.
Ответ №3:
В основном это сводится к следующему. Если будут такие утверждения if, как
if(document.IsIncoming()){
do something
}elseif (document.SentToDepartment()) {
do something else
}
повторяется более чем в паре мест, в которых вам было бы лучше использовать какое-либо полиморфное решение (например, абстрактный класс или интерфейс). То же самое с типами отправителя и получателя.
Однако вам не нужно создавать подкласс вверху. У вас может быть один класс документа с различным поведением, привязанным к нему с помощью полиморфизма. Предположим, что поведение печати отличается для входящих и исходящих документов. Затем вы создаете интерфейс IPrinter и реализуете его в двух классах следующим образом.
public class Document
{
DocumentType type;
IPrinter printer;
}
interface IPrinter{
print();
}
class IncomingPrinter :IPrinter{}
class OutgoingPrinter :IPrinter{}
Всякий раз, когда принимается решение о том, что документ будет входящим (возможно, при его создании), вы назначаете IncomingPrinter. Если существует несколько типов поведения, которые необходимо назначить подобным образом, обычно используется заводской шаблон. Таким образом, вы локализуете if (док.Операторы IsIncoming()) в одно место. Преимуществ от того, что решение не повторяется в разных местах кода, много.
Комментарии:
1. Почему именно вам нужно ссылаться на принтер из документа? Я имею в виду, почему бы не распечатать (документ)?
2. @Boris: Предполагается, что в этом примере функциональность печати отличается для входящих и исходящих документов. Мы хотим избежать повторения, если(док. Тип решения IsIncoming()). Интерфейс IPrinter делает именно это.
3. ах, хорошо, я понял вашу точку зрения. я на самом деле пропустил «заводскую» часть вашего ответа. кстати. это также может быть реализовано как посетитель с помощью метода печати (document). в таких языках, как c # 4, это становится простым ( blogs.msdn.com/b/shawnhar/archive/2011/04/05 /… ) без взрыва классов, который типичен для visitor в строго типизированных языках.
Ответ №4:
Я совершенно против иерархий здесь. Отправитель и получатель — это явно концепции, которые имеют определенное поведение и передают некоторые данные. Я бы сосредоточился на этих обязанностях и создал сущности Sender и Reciever. Позже, при необходимости, создайте подклассы из них, такие как CompanySender. В принципе, не загрязняйте сущность Company логикой отправителя, если вся ваша система не связана с отправителями и получателями (я как бы предполагаю, что это не так).
Ответ №5:
При моделировании входящих и исходящих документов наилучший дизайн зависит от того, как вы обычно будете обрабатывать документы.
Если вы обычно знаете во время компиляции, является ли документ типом ввода / вывода, и существует множество общих свойств и поведения, отличных от отправителя или получателя, тогда иерархия была бы хороша (поскольку у вас были бы методы, которые принимают либо InDocument, либо OutDocument, которые могут быть разрешены во время компиляции или даже во время выполнения в зависимости от вашего языка).
Если, с другой стороны, все ваши документы перепутаны, и вы в основном обрабатываете их во время выполнения, то другое решение (которое я вскоре представлю) может быть более чистым. Я подозреваю, что это почти наверняка верно в отношении типов отправителя и получателя, которые почти наверняка не требуют иерархии (хотя, в зависимости от вашего языка, вам может потребоваться использовать маркер интерфейса, чтобы эмулировать функциональное решение, которое я собираюсь представить). То, что вы хотите в этих случаях, является своего рода «перечислением с данными». В объектно-функциональном языке, таком как F #, вы можете комбинировать типы записей и дискриминантные объединения, чтобы хорошо моделировать это:
type PersonInfo = {Name:string ; Age:int}
type CompanyInfo = {Name:string ; Location:string}
type sender =
| Person of PersonInfo
| Company of CompanyInfo
type DepartmentInfo = {Name:string ; Floor: int}
type receiver =
| Person of PersonInfo
| Company of CompanyInfo
| Department of DepartmentInfo
type documentType =
| In of sender
| Out of receiver
type Document = {Type:documentType ; Title:string ; Body:string ; Date:DateTime } with
//example of how to process a Document using pattern matching
override this.ToString() =
match this.Type with
| In(s) ->
match s with
| sender.Person(info) -> sprintf "In / Person, extra info: Name = %s ; Age= %i" info.Name info.Age
| sender.Company(_) -> "In / Company"
| Out(r) ->
match r with
| receiver.Person(_) -> "In / Person"
| receiver.Company(_) -> "In / Company"
| receiver.Department(_) -> "In / Department"
//create a list of In and Out documents all mixed up
let documents =
[{Type = In(sender.Person({Name="John"; Age=3})); Title="My In Doc"; Body="Hello World"; Date=DateTime.Now}
{Type = Out(receiver.Department({Name="John"; Floor=20})); Title="My Out Doc"; Body="Testing"; Date=DateTime.MinValue}]
//partition documents into a list of In and Out Types
let inDocuments, outDocuments =
documents |> List.partition (function | {Type=In(_)} -> true | {Type=Out(_)} -> false)