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

Подклассы - это способ поделиться всем из родительского класса.

Начнем с примера иерархии классов в надуманной проблемной области.

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);

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