У меня серьезные проблемы с выделением подклассов как методом совместного использования кода. Честно говоря, это было в основном самоубийством из-за моих амбициозных попыток полиморфизма. Я на собственном горьком опыте убедился, что слишком легко загнать себя в угол. Чтобы избежать соблазна, я стараюсь сосредоточиться на простой механике того, что значит подкласс.
Подклассы - это способ поделиться всем из родительского класса.
Начнем с примера иерархии классов в надуманной проблемной области.
class Animal class Mammal : Animal class Dog : Mammal class Cat : Mammal class Marsupial : Animal class Kangaroo : Marsupial class Koala : Marsupial
Наследование классов намного менее уместно, чем вы думаете. Сложные иерархии со временем превратятся в сбивающий с толку беспорядок. Если вы мне не верите, попробуйте поместить класс Утконоса в указанную выше иерархию. Хотя технически утконос - это млекопитающее, у вас возникнут проблемы с передачей кода яйцекладки сумчатых.
Так какая же альтернатива?
Агрегация - это метод выборочного совместного использования другого класса.
Если вы работаете на современном языке, таком как C #, объединить общий класс так же легко.
class Dog { Animal Animal; Mammal Mammal; } class Platypus { Animal Animal; Mammal Mammal; Marsupial Marsupial; }
Классы имеют ограниченную область видимости, чтобы предоставлять только соответствующие данные и методы. Этот код также можно использовать повторно, чем просто экземпляры Animal.
Но при использовании агрегирования теряется чистый синтаксис. Вы не можете передать Platypus напрямую методу, который ожидает Animal. Это подводит нас к технике кодирования, которую я называю мимикрией.
Нет, не такая мимика.
Мимикрия - это метод неявного приведения типов к другому классу.
Это легко сделать на C #.
class Dog { private Animal Animal; public static implicit operator Animal(Dog Self) => Self?.Animal; }
Это означает, что я могу передать собаку туда, где требуется животное.
void AddPet(Animal Animal) { Console.WriteLine(Animal.Name); } var MyDog = new Dog(); AddPet(MyDog);
Это удобный синтаксис, который означает, что вам не нужно публиковать агрегированный объект. Публичный API не скомпрометирован, и при необходимости вы можете неявно передать базовый объект.
Этот метод может иметь более жесткий синтаксис, если у вас есть один логический базовый класс. Представляем Mimic ‹T›.
abstract class Mimic<T> { public static implicit operator T(Mimic<T> Self) => Self?.Base; protected T Base; } sealed class MyButton : Inv.Mimic<Inv.Button> { public MyButton(Inv.Surface Surface) { this.Base = Surface.NewButton(); this.Label = Surface.NewLabel(); Base.Content = Label; Label.Padding.Set(10); Label.Font.Size = 20; Label.Font.Colour = Inv.Colour.White; Label.JustifyCenter(); } public Inv.Colour Colour { get => Base.Background.Colour; set => Base.Background.Colour = value; } public string Text { get => Label.Text; set => Label.Text = value; } private readonly Inv.Label Label; }
Я переключил контекст на Изобретение, чтобы поговорить о том, как поделиться кодом пользовательского интерфейса. Приведенный выше пример представляет собой стилизованную кнопку с ограниченным общедоступным API. Когда вы работаете с MyButton, вы можете установить только цвет и текст. Он ограничивает доступ к любому другому аспекту кнопки или метки. При этом MyButton объявлен как имитация и может неявно приводить тип к Inv.Button. Это означает, что вы можете легко передать MyButton, когда требуется панель Invention.
var Stack = Surface.NewVerticalStack(); var MyButton = new MyButton(Surface); Stack.AddPanel(MyButton);
Техника имитации - это компромисс между синтаксическими преимуществами подкласса и избирательным наследованием, обеспечиваемым агрегацией. Он особенно хорошо подходит для совместного использования кода пользовательского интерфейса.