1

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

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 
} 

Любые комментарии или предложения?


ORIGINAL Вопрос:

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

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 mixins, которые кажутся чем-то похожим на то, что я хочу сделать. Я также прочитал это интересное сообщение в блоге, в котором обсуждается идея типа поведения, когда класс сможет унаследовать множественное поведение, сохраняя при этом единственное отношение «есть»: http://www.deftflux.net/blog/post/A-good-design-for-multiple-implementation-inheritance.aspx. Кажется, это говорит о том, чего я хочу. Может ли кто-нибудь дать мне какое-то направление, как я могу это сделать, используя хорошие методы проектирования?

+0

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

+0

@ samob99: Во-первых, ТНХ для всех ваших предложений и мыслей! Я не пытаюсь использовать множественное наследование.Я, возможно, закодировал себя в сценарий, где я неоднозначно нуждаюсь в множественном наследовании, но моя проблема в том, что я не знаю, как закодировать код. Как я должен был спроектировать эти классы, чтобы мне не приходилось неоднозначно нуждаться в множественном наследовании? Пример кода, связанный с принципом проектирования, будет наиболее полезен. – gabe

ответ

5

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

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

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

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

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

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

1

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

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

+0

Я тоже думал об Декораторе. Продвижение кандидатов будет немного сложнее, хотя и требует некоторой мысли. Может быть, фабрика, которая создает Кандидаты и украшает их на основе своего кандидата, что также означает, что вам нужно определить какое-то состояние кандидата или тип кандидата. Должен быть лучший способ. –

+0

Я читал о шаблоне декоратора на http://www.dofactory.com/Patterns/PatternDecorator.aspx, но мне было трудно понять, как его реализовать для моего кода. Сможете ли вы сделать снимок при реализации с моим кодом, если вы более знакомы с шаблоном? Любая помощь будет оценена по достоинству. – gabe

+0

Нет времени прямо сейчас, но я постараюсь дойти до него в ближайшее время. – duffymo

2

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

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

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

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

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

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

Рядом дублированные: Multiple inheritance in C#

4

Вот научная статья на эту тему, я думаю, что это довольно интересно (PDF link).

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

Редактировать: Также Scott Meyers отлично справляется с этой проблемой в своих книгах.

+0

@JP: Thx для ответа, JP! Однако мои требования в этом случае не требуют, чтобы GPA вообще использовался в InternalCandidate. Это был всего лишь пример кода. Свойство Gpa просто представляет свойство, которое совершенно не нужно/используется/хранится для кандидата или любого производного класса, кроме EntryLevelCandidate и его производных классов. – gabe

+0

Поймите. Он считает, что общий ответ заключается в том, чтобы реорганизовать и переосмыслить отношения, чтобы избежать алмаза. :) –

+0

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

0

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

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 } 
    } 
}