Как реализовать совместное поведение между классами (конечно, без множественного наследования) в C #

ОБНОВЛЕНИЕ: Практически все здесь говорили мне, что мне просто нужно начать все сначала с того, как я спроектировал свои классы (кстати, спасибо, ребята, за отличные ответы!). Поняв намек, я начал подробно изучать шаблон стратегии. Я хочу создать классы поведения (или классы стратегии), которые наследуются от абстрактного базового класса или классов. Тогда класс Candidate будет иметь свойства с различными абстрактными базовыми классами / классами как Type для поведения или стратегий. может быть что-то вроде этого:

public abstract class SalaryStrategy {
    public abstract decimal Salary { get; set; }
    public abstract decimal Min { get; set; }
    public abstract decimal Mid { get; set; }
    public decimal CompaRatio {
        get {
            if (this.Mid == 0) { return 0; }
            else { return this.Salary / this.Mid; }
        }
    }
}

public class InternalCurrentSalaryStrategy {
    public override decimal Salary { get; set; }
    public override decimal Min {
        get { return this.Salary * .25m; }
        set { }
    }
    public override decimal Mid { get; set; }
}

public class Candidate {
    public int Id { get; set; }
    public string Name { get; set; }
    public SalaryStrategy CurrentSalaryStrategy { get; set; }
}

public static void Main(string[] args) {
    var internal = new Candidate();
    internal.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var internalElp = new Candidate();
    internalElp.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var elp = new Candidate();
    // elp.CurrentSalaryStrategy can stay null cause it's not used for elps
}

Есть комментарии или предложения?


ОРИГИНАЛЬНЫЙ вопрос:

Я пытаюсь изучить шаблоны и принципы проектирования и стать более опытным. В настоящее время я работаю над дизайном для нескольких классов, которые меня озадачили. Вот очень сжатая версия кода:

public class Candidate {
    public int Id { get; set; }
    public string Comments { get; set; }
    // lots more properties and behaviors...
}

public class InternalCandidate : Candidate {
    public decimal CurrentMid { get; set; }
    public decimal CurrentMax {
         get { return this.CurrentMin * 1.3m;
    }
    // lots more properties and behaviors...
}

public class EntryLevelCandidate : Candidate {
    public string Gpa { get; set; }
    // lots more properties and behaviors...
}

public class InternalEntryLevelCandidate /* what do I inherit here??? */ {
    // needs all of the properties and behaviors of
    // EntryLevelCandidate but also needs the CurrentMin and
    // CurrentMax (and possibly more) in InternalCandidate
}

Класс InternalEntryLevelCandidate в первую очередь является EntryLevelCandidate, но должен совместно использовать некоторые реализации InternalCandidate. Я говорю «реализации», потому что не хочу, чтобы реализации отличались или повторялись, иначе я бы использовал интерфейс для общих контрактов и имел бы конкретные реализации в каждом классе. Некоторые реализации свойств и поведения InternalCandidate должны быть общими или совместно используемыми. Я читал о миксинах C ++ и Ruby, которые кажутся чем-то похожим на то, что я хочу делать. Я также прочитал этот интересный пост в блоге, в котором обсуждается идея типа поведения, при котором класс мог бы наследовать несколько вариантов поведения, сохраняя при этом единственное отношение «есть»: http://www.deftflux.net/blog/post/A-good-design-for-multiple-implementation-inheritance.aspx. Кажется, это передает то, чего я хочу. Может ли кто-нибудь дать мне какое-нибудь руководство о том, как я могу добиться этого, используя хорошие методы проектирования?


person gabe    schedule 29.04.2009    source источник
comment
Я думаю, вам нужно продумать пример, который однозначно требует множественного наследования, это будет действительно сложно   -  person Sam Saffron    schedule 30.04.2009
comment
@ samob99: Во-первых, спасибо за все ваши предложения и мысли! Я специально не пытаюсь использовать множественное наследование. Возможно, я закодировал себя в сценарии, в котором мне неоднозначно требуется множественное наследование, но моя проблема в том, что я не знаю, как отменить код. Как мне спроектировать эти классы, чтобы мне не требовалось неоднозначно множественное наследование? Наиболее полезен пример кода, связанный с принципом проектирования.   -  person gabe    schedule 30.04.2009


Ответы (5)


Неизменяемые классы значений данных. Если какие-либо свойства в ваших различных подклассах Candidate представляют какое-то значимое значение данных, создайте для него неизменяемый класс с нужным вам поведением. Каждый из ваших отдельных подклассов Candidate может затем использовать этот тип данных, но ваш код по-прежнему инкапсулирован в классы данных.

Методы расширения. Они могут быть перегружены для работы с любыми классами.

Я бы избегал шаблона декоратора и придерживался скомпилированной / отражаемой функциональности.

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

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

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

person Triynko    schedule 29.04.2009

Вот научная статья по теме, которая, на мой взгляд, довольно интересна (Ссылка на PDF < / а>).

Но я думаю, что вы пытаетесь навязать бизнес-логику в своих обобщениях. Вы случайно знаете, что средний балл внутреннего кандидата никогда не проверят. Но у InternalCandidate определенно есть средний балл. Итак, вы выделили этого странного парня по имени InternalEntryLevelCandidate, потому что случайно знаете, что хотите посмотреть на его средний балл. С архитектурной точки зрения я считаю, что EntryLevelCandidate неверен. Я бы добавил к кандидату понятие «уровень» и дал бы ему средний балл. Это дело бизнес-логики, чтобы решить, смотрят ли они на средний балл или нет.

Изменить: Кроме того, Скотт Мейерс отлично разбирается в этой проблеме в своих книгах.

person JP Alioto    schedule 29.04.2009
comment
@JP: Спасибо за ответ, JP! Однако мои требования в этом случае не требуют использования GPA в InternalCandidate. В любом случае это был просто пример кода. Свойство Gpa просто представляет свойство, которое совершенно не требуется / не используется / не хранится для Candidate или любого производного класса, кроме EntryLevelCandidate и его производных классов. - person gabe; 30.04.2009
comment
Понимать. Он думает, что общий ответ - провести рефакторинг и переосмыслить отношения, чтобы избежать бриллианта. :) - person JP Alioto; 30.04.2009
comment
Не могли бы вы дать мне пример кода того, что вы предлагаете? Это пространство дизайна - это то, с чем я боролся некоторое время. Увидеть некоторую реализацию мне бы очень помогло. - person gabe; 30.04.2009

Заявление об ограничении ответственности:

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

Возвращаясь к вопросу, чистого решения не существует, однако у вас есть несколько вариантов:

  • Использование методов расширения, имеет тот недостаток, что щелчок правой кнопкой мыши "Разрешить" не работает, также некоторым людям очень не нравятся эти щенки.

  • Создайте агрегированный объект, содержащий и экземпляр каждого класса, который вы хотите составить, повторно реализуйте методы-заглушки, которые делегируют.

  • Определите интерфейс для каждого поведения и попросите методы в базовой проверке this is IInterface перед выполнением поведения. (позволяет тянуть определения поведения к базе)

Почти дубликат: Множественное наследование в C #

person Sam Saffron    schedule 29.04.2009

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

Другая, более эзотерическая идея - подумать об аспектно-ориентированном программировании. С аспектами можно делать довольно удивительные вещи, но это очень сложная тема, которую я еще не освоил. Такие люди, как Рикард Оберг и его группа Qi4J.

person duffymo    schedule 29.04.2009
comment
Я тоже думал о Decorator. Однако создание экземпляров кандидатов будет немного сложным и потребует некоторого размышления. Может быть, фабрика, которая создает кандидатов и украшает их в зависимости от типа кандидата, что также означает, что вам нужно определить какое-то состояние или тип кандидата. Должен быть способ получше. - person Mehmet Aras; 30.04.2009
comment
Я читал о шаблоне декоратора на dofactory.com/Patterns/PatternDecorator.aspx, но мне было трудно понять, как реализовать это для моего кода. Сможете ли вы сделать снимок в реализации с моим кодом, если вы более знакомы с шаблоном? Любая помощь будет оценена. - person gabe; 30.04.2009

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

public class InternalEntryLevelCandidate : EntryLevelCandidate {
    private  InternalCandidate internalCandidateDelegate
        = new InternalCandidate();

    public decimal CurrentMid { 
        get { return internalCandidateDelegate.CurrentMid; }
        set { internalCandidateDelegate.CurrentMid = value; }
    }

    public decimal CurrentMax {
        get { return internalCandidateDelegate.CurrentMax }
    }
}
person Cameron MacFarland    schedule 29.04.2009