2013-06-22 2 views
2

Вот моя программа:Делегат метод или абстрактный класс

class Program 
{ 
    //DESIGN 1 
    abstract class AFoo 
    { 
     public string Bar { get; set; } 
     public abstract string SayHi(); 
    } 

    class LoudFoo : AFoo 
    { 
     public override string SayHi() 
     { 
      return this.Bar.ToUpper(); 
     } 
    } 

    class QuietFoo : AFoo 
    { 
     public override string SayHi() { return this.Bar.ToLower(); } 
    } 

    //DESIGN 2 
    class Foo{ 
     public string Bar { get; set; } 
     public Func<Foo, string> SayHi { get; set; } 
    } 

    static void Main(string[] args) 
    { 
     //USING DESIGN 1 
     var quietFoo2 = new QuietFoo{ Bar = "Mariane"}; 

     var loudFoo2 = new LoudFoo{ Bar = "Ginger"}; 

     Console.WriteLine(quietFoo2.SayHi()); 
     Console.WriteLine(loudFoo2.SayHi()); 

     //USING DESIGN 2 
     var quietFoo = new Foo 
     { 
      Bar = "Felix", 
      SayHi = (f) => { return f.Bar.ToLower(); } 
     }; 
     var loudFoo = new Foo 
     { 
      Bar = "Oscar", 
      SayHi = (f) => { return f.Bar.ToUpper(); } 
     }; 
     Console.WriteLine(quietFoo.SayHi(quietFoo)); 
     Console.WriteLine(loudFoo.SayHi(loudFoo)); 

    } 
} 

Я могу выполнить «то же самое», - на самом деле не то же самое, но подобные вещи, идущие два различных маршрута.

Design 1) Я могу создать абстрактный класс, который заставляет реализатор этого класса, как SayHi()

--или--

дизайн 2) я мог бы создать класс определяет свойство SayHi, которое является функцией. (Я называю это delegate--, но я не уверен, что это правильный термин для этого здесь)

Design 1 беспокоит меня, потому что это может привести к profliferation классов

еще ...

Дизайн 2 беспокоит меня, потому что он чувствует себя излишним, когда мне нужно иметь Foo на самом деле SayHi().

felix.SayHi(felix) 

Вопрос в том, лучше ли использовать дизайн 1 или дизайн 2-- или, возможно, ни один из них. Когда я говорю лучше, я говорю, что более практично с точки зрения возможности поддерживать мою программу. Я столкнулся с этим, когда создал разные классы, которые будут использоваться для загрузки файлов из разных облачных API (Google Диск, Box.com, DropBox) - сначала я создал отдельные классы, но затем я пошел другим путем.

+1

Когда вы говорите «лучше», пожалуйста, квалифицируйте его с помощью каких-либо критериев измерения. Лучше для чего? Читаемость? Представление? Обслуживание? Фаза луны? Что-то другое? – Oded

+0

@Oded - хорошая точка - я добавил еще несколько комментариев - я бы сказал, что лучше было бы «ремонтопригодность» - –

+0

Что относительно Design 3: Частичные классы? – GolezTrol

ответ

5

Когда дело доходит до этих вариантов дизайна, я нахожу, что это помогает думать об объектах с точки зрения проблемного домена, который вы пытаетесь моделировать. Вы показали LoudFoo и QuietFoo как отличающиеся одним поведением, но это преднамеренно упрощенный пример. В реальной системе у вас могут быть веские причины считать два объекта концептуально отличными.

В предыдущей версии SayHi является неотъемлемой частью поведения класса, что является подходящим, если природа этого поведения каким-то образом взаимодействует со своим внутренним состоянием. Возможно, реализация SayHi зависит от свойств объекта, специфичных для этого типа производного класса.

В последней версии SayingHi больше похоже на инструмент, который может быть передан в различные экземпляры. Это уместно, когда нет других причин различать разные типы экземпляров Foo.

Stream является хорошим примером прежнего шаблона, где различные методы, которые он предоставляет, являются неотъемлемой частью природы потоковой операции. Различные производные классы будут использовать разные состояния для реализации своих методов.

Comparer является хорошим примером последнего шаблона, в котором многие типы объектов хотят работать с использованием понятия сравнения. Классы, которые используют эту функциональность, не должны иметь ничего общего, кроме желания использовать этот тип поведения.


Что касается вашего конкретного приложения, которое вызвало этот вопрос, как насчет многоклассового подхода неловко? Если бы была избыточность, она, вероятно, указывает на то, что обязанности могут быть учтены по-другому, что лучше моделирует проблему. Трудно сказать больше, не зная дополнительной информации о конкретной проблеме, но, вероятно, хороший подход был бы сочетанием двух предложенных вами, причем один класс отвечал за последовательность операции и отдельную иерархию (или набор реализаций интерфейса) выполнение операций, характерных для каждой службы. По сути, интерфейс (или базовый класс) объединяет всех делегатов, которые вы передадите отдельно. Это похоже на то, как StreamReader берет Stream и дополняет его дополнительными поведениями, которые работают с Stream.

+0

Это было очень полезно. Я собираюсь вернуться и посмотреть на проблему реального мира, над которой я работал - это определенно помогло. –

+0

Я действительно восхищаюсь тем, как вы ответили ... очень впечатляюще и полезно ... Огромное спасибо Дэну! –

2

Как правило: меньше кода == более ремонтопригодное.

В конкретном случае, у вас есть, вы также имеющая развязку дизайна - логику как SayHi отдельно от класса, который говорит, что, давая вам возможность составить поведение. Низкое сцепление также является признаком кода, который обычно поддерживается.

Мое предпочтение было бы со вторым дизайном.

+0

Спасибо @Oded --- и спасибо, что помогли мне уточнить вопрос. –

4

В Design 1 ваше поведение реализовано внутри класса, но в Design 2 вы просите своего вызывающего пользователя определить поведение.

Я склоняюсь к дизайну 1, потому что он сохраняет реализацию поведения черным ящиком внутри класса. Дизайн 2 может изменить вашу реализацию, когда кто-то создает новый объект. Мне также не нравится, как ответственность за выполнение несет пользователь.

Если как реализовать SayHi изменения только одно место, чтобы изменить его в дизайн 1, но вы могли бы потенциально иметь несколько мест всего кода, чтобы изменить его, если вы использовали дизайн 2.

+0

это отличные моменты - спасибо. –

1

Первый дизайн более стандартный, и логика последовательна (означает, что любой другой класс, использующий LoudFoo (или QuietFoo), будет иметь одинаковую повсюду. Однако он повторно используется, но только в его унаследованном пути. Означает, что дочерний класс от LoudFoo (скажем DerivedLoudFoo не может используйте логику SayHi, определенную в QuietFoo).

Это может показаться простым, но может быть неприятно позже. Вы можете прочитать мой ответ в here для реального случая.

Второй вариант является более расширяемым, но недостатки могут иметь различное поведение. Не используйте это для основного бизнес-процесса (например, insert/update/delete), поскольку его трудно отладить или изменить. Однако это лучше всего использовать на уровне Framework для некоторых методов, таких как OnAfterInsert, OnAfterSubmit.

+0

thanks-- Я прочитаю этот пример. Я собираюсь изменить свой вопрос, чтобы добавить пример моего реального мира. –

1

Предполагая, что это больше, чем полностью выдуманный пример и на самом деле может быть переведен на реальный код (который мне сложно определить, что это может быть), я нахожу вариант 2 ужасным.

  • Вы можете в значительной степени приписывать ничего SayHi, в том числе лямбда, которая не связана с Bar каким-либо образом, который, кажется, не быть вашим изначальное намерение.

  • Вы в основном пытаетесь вставить красиво обработанный функциональный колышек в хорошее старое объектно-ориентированное отверстие.Используя lambda, вы разделили данные (Bar) на поведение, которое работает на нем, что является действительной функциональной практикой, но затем, создав свойство Foo.SayHi, вы вернетесь к стилю OO, пытаясь инкапсулировать их обратно в один класс , Кажется немного надуманным.

+0

ты первый раз был чем-то, что меня беспокоило - спасибо. –

0

Design 2 беспокоит меня, потому что он чувствует себя излишним, когда я должен иметь Foo на самом деле SayHi().

Если Foo класс переопределяется

class Foo 
    public property Bar as string 
    public property SayHi as func(of string) 
end class 

Тогда замыкание может быть использован для создания и вызова SayHi функцию без прохождения Foo в качестве параметра функции:

dim Bar = "Felix" 

dim Felix as new Foo with { 
    .Bar = Bar, 
    .SayHi = function() Bar.toLower 
} 

dim FelixSays = Felix.SayHi() 

I он склоняется к дизайну 1, потому что он сохраняет поведение i добавление черного цвета внутри класса.

Design 2 всегда готов к реализации поведения черного ящика, например, внутри фабричного метода:

function CreateQuietFoo(Bar as string) as Foo 
    return new Foo with { 
     .Bar = Bar, 
     .SayHi = function() Bar.toLower 
    } 
end function 

dim Felix = CreateQuietFoo("Felix") 
dim Oscar = CreateQuietFoo("Oscar") 

Таким образом, вызывающий абонент не должен предоставлять SayHi метод для создания тихой Foo экземпляра , он просто использует метод фабрики CreateQuietFoo.

Вопрос в том, лучше ли использовать дизайн 1 или дизайн 2 - или, возможно, ни один из них.

Использовать дизайн 2, если вы предпочитаете Состав над наследованием. Это делает код более гибким.