Плавни интерфейси и множествено наследяване в C#

Този въпрос е подобен на този . Разликата е, че бих искал да имам два базови класа.

Пример:

public class Circle
{
    private string _radius { get; set; }

    public Circle Radius(string radius)
    {
        _radius = radius;
        return this;
    }
}

public class Box
{
    private string _width { get; set; }

    public Circle Width(string width)
    {
        _width = width;
        return this;
    }
}

public class CircleAndBox : Circle, Box // Can't do in c#
{
    // should contain methods from both Circle and Box, but return CircleAndBox
}

Може би Circle and Box не беше най-добрият пример. По принцип те представляват класове с различни свойства и методи. Класът CircleAndBox просто има същите свойства и методи като Circle и Box. CircleAndBox може да има допълнителни свойства и методи, които не съществуват нито в Circle, нито в Box.

Желан резултат

Трябва да мога да напиша:

var circle = new Circle().Radius("5");
var box = new Box().Width("6");
var circleAndBox = new CircleAndBox().Radius("5").Width("6");

Би било супер, ако:

Когато добавя метод към Circle или Box клас, не трябва да докосвам CircleAndBox клас. Точно както при редовното наследяване от един клас, CircleAndBox трябва автоматично да наследи всички публични методи както от Circle, така и от Box


person Dmitry Efimenko    schedule 02.08.2013    source източник
comment
Интерфейси интерфейси интерфейси... За какво ви трябват и двете? Какво е кръг + кутия? Ако това е кръг в кутия, тогава радиусът на кръга е половината от ширината на кутията?   -  person Sayse    schedule 03.08.2013
comment
.Width("6")?? а какво да кажем за .Width("BigFat")??   -  person Federico Berasategui    schedule 03.08.2013
comment
new CircleAndBox().Radius("5").Width("6"); Това е толкова свободно, че дори не разбирам какво има предвид.   -  person spender    schedule 03.08.2013
comment
@spender ще работи, ако Radius върне CircleAndBox :P (съмнявам се!)   -  person Sayse    schedule 03.08.2013
comment
Объркали ли сте часовете си? т.е. Box : Shape и Circle : Shape с общи методи в Shape?   -  person Sayse    schedule 03.08.2013
comment
CircleAndBox звучи като фантастично нарушение на принципа за заместване на Лисков.   -  person David    schedule 03.08.2013
comment
хаха, толкова много вълнение! Може би Circle и Box не бяха най-добрият пример. По принцип те представляват класове с различни свойства и методи. Клас CircleAndBox просто има същите свойства и методи като Circle и Box. CircleAndBox може да има допълнителни свойства и методи, които не съществуват нито в Circle, нито в Box. Ще добавя това в описанието на въпроса   -  person Dmitry Efimenko    schedule 03.08.2013
comment
относно .Width(6)... каквото и да е, хора! Това е само пример!   -  person Dmitry Efimenko    schedule 03.08.2013


Отговори (4)


Нека CircleAndBox не наследява от нито един клас, а по-скоро препраща към обекти от тези класове. Ще трябва да предефинира методите от всеки клас. Можете да добавите неявни преобразувания към Circle и Box, за да позволите използването му в контексти, където се очакват препратки към тези обекти.

public class CircleAndBox
{
    public Circle Circle { get; private set; }
    public Box Box { get; private set; }

    public CircleAndBox()
    {
        Circle = new Circle();
        Box = new Box();
    }

    public CircleAndBox Radius(string radius)
    {
        Circle.Radius(radius);
        return this;
    }

    public CircleAndBox Width(string width)
    {
        Box.Width(width);
        return this;
    }

    public static implicit operator Circle(CircleAndBox self)
    {
        return self == null ? null : self.Circle;
    }

    public static implicit operator Box(CircleAndBox self)
    {
        return self == null ? null : self.Box;
    }
}

Имайте предвид, че имплицитните преобразувания няма да запазят типа на обекта, така че не трябва да използвате тази техника за предаване на CircleAndBox към метод, приемащ Box и очаквайки резултатът от другата страна да бъде CircleAndBox.

CircleAndBox cb = new CircleAndBox();

// Implicit conversion, b contains a Box object.
Box b = cb;

// Compile-time error CS0030.
cb = (CircleAndBox)b;
person cdhowie    schedule 02.08.2013
comment
моля, вижте актуализирания раздел Би било супер, ако: на въпроса. Разбирам, че вашето решение не се грижи за това? - person Dmitry Efimenko; 03.08.2013
comment
@Dmitry C# няма множествено наследяване, така че това, което питате, не е възможно без изключително използване на интерфейси и автоматично предоставяне на методите за делегиране чрез тип прокси по време на изпълнение. - person cdhowie; 03.08.2013
comment
това прозвуча умно :) наистина, не разбрах какво казахте. Предполагате ли, че все още има начин да го направите, но е твърде сложен? - person Dmitry Efimenko; 03.08.2013
comment
@Dmitry Единственият начин, по който бихте могли да направите това, без също така да прилагате всеки метод на CircleAndBox, би бил да създадете само interface ICircleAndBox : IBox, ICircle { } и след това да излъчвате изпълнение за този интерфейс по време на изпълнение. Или чрез написване на задача за компилиране, която ще инспектира другите два интерфейса и ще генерира C# изходен файл, прилагащ интерфейса. Така или иначе, вероятно повече работа, отколкото да повтаряте всяка реализация. - person cdhowie; 03.08.2013

Наследяване означава "е а".

Ball е RoundThing. Circle е RoundThing. Box е SquareThing. Lamborghini е Car, а Car е Transport. Bus и Bicycle също са Transports.

Ако не е връзка „е а“, това не е наследство. И в това е обяснението защо множественото наследяване не се поддържа в C# и Java и не се препоръчва от повечето хора, които познавам в C++. Един обект почти никога не е две неща едновременно.

Не злоупотребявайте с наследяването само за да запазите кодови редове. Дори ако запазването на кодови редове е легитимна цел (с което не съм съгласен), то дори не е необходимо. Две други конструкции могат да ви помогнат тук.

Първо, за връзката. Това, което търсите, е съвсем различна връзка. Не "е", а "може да се използва като". Това е мястото, където идват интерфейсите. Интерфейсите определят за какво може да се използва даден обект. Така че можете да имате два интерфейса IBox и ICircle. След това вашето CircleAndBox нещо може да имплементира и двата интерфейса, като по този начин указва, че може да се използва и като двата.

Второ, можете да използвате още едно отношение, за да улесните живота си. Няма да запазвате кодови редове. Но ще се уверите, че вашият CircleAndBox винаги има правилното поведение, дефинирано в класовете Circle и Box. За това можете да използвате агрегиране, заедно с модела на делегиране. Агрегирането е релация, която казва "има". Така че накарайте вашия CircleAndBox клас да има частен Circle обект и частен Box обект, след което имплементирайте интерфейсите ICircle и IBox просто като извикате съответния метод/свойство/събитие на съответните частни обекти и върнете това, което връщат.

Така че, вие имате клас, който има (агрегиране) Circle и Box екземпляр, към който действително да прехвърлите работата, който ги излага, като има същите методи (делегиране) и които по този начин могат да бъдат използва се като(интерфейс) кръгикато кутия.

person Jan Dörrenhaus    schedule 02.08.2013
comment
Не ми пука много за запазването на кодови редове. По-скоро ме интересува поддръжката. Добре написано! +1 - person Dmitry Efimenko; 03.08.2013

Използвайте интерфейси:

IBox и ICircle

public interface IBox
{
    Circle Width(string width)
}

public interface ICircle
{
    Circle Radius(string radius)
}

public class Circle : ICircle
{
    private int _radius { get; set; }

    public Circle Radius(string radius)
    {
        _radius = radius;
        return this;
    }
}

public class Box : IBox
{
    private int _width { get; set; }

    public Circle Width(string width)
    {
        _width = width;
        return this;
    }
}

public class CircleAndBox : ICircle, IBox
{
    // should contain methods from both Circle and Box, but return CircleAndBox
}
person Cubicle.Jockey    schedule 02.08.2013
comment
(string Width) все още е лоша идея, независимо от всичко. низът не е подходящ тип данни за представяне на размери. - person Federico Berasategui; 03.08.2013
comment
Това е просто копиране и поставяне от това, което имаха и аз съм съгласен. - person Cubicle.Jockey; 03.08.2013
comment
Причината да не харесвам този подход е, че все пак ще трябва да напиша имплементация за всеки метод на ICircle и IBox в CircleAndBox. В идеалния случай бих искал да го избегна. - person Dmitry Efimenko; 03.08.2013

Не мисля, че можете да направите точно това, което очаквате, тъй като C# (и .Net като цяло) не поддържа множествено наследяване на класове. Можете да наследите от един клас и да реализирате интерфейс.

Например:

interface iBox
{
  iBox Width(string width); //Assuming you want Box here and not Circle
}

След това имате Box

class Box : iBox ...

Сега можете да имате

public class CircleAndBox : Circle, iBox
{
private Box _helper;
public iBox Width(string width) { _helper.Width(width); return this; }
}

Това не е точно това, което искате, но според мен е толкова близо, колкото позволява C#. Ако желаете да използвате слаби типове, има и метод за делегиране за предаване на неща към _helper без изрично прилагане на всеки метод. При директно делегиране обаче няма да върнете вашия обект CircleAndBox, а вместо това обекта _helper. Не съм сигурен, че това ще послужи за целта.

Също така, това няма да работи, ако не контролирате поне един от класовете, които искате да наследите.

person Prof Von Lemongargle    schedule 02.08.2013