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

UPDATE: Так что почти все здесь сказали мне, что мне просто нужно начать все сначала с того, как я спроектировал свои уроки (спасибо вам, ребята, за отличные ответы!). Поняв подсказку, я начал много читать о стратегии . Я хочу создать классы поведения (или классы стратегии), которые наследуются от абстрактного базового класса или классов. Класс 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. Это, кажется, передает то, что я хочу , Кто-нибудь может дать мне какое-то руководство о том, как я могу сделать это, используя передовые методы проектирования?

Ответы [ 5 ]

5 голосов
/ 30 апреля 2009

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

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

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

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

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

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

4 голосов
/ 30 апреля 2009

Вот научная статья на эту тему, которая мне кажется довольно интересной ( PDF ссылка ).

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

Редактировать: Кроме того, Скотт Мейерс проделал большую работу по распространению этой проблемы в своих книгах.

2 голосов
/ 30 апреля 2009

Отказ от ответственности :

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

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

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

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

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

Почти в двух экземплярах: Множественное наследование в C #

1 голос
/ 30 апреля 2009

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

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

0 голосов
/ 30 апреля 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 }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...