Подтвердить что ты не робот

Какой хороший пример для наследования классов?

Я пишу документацию для объектно-ориентированного языка, и мне интересно, какие классы будут хорошим примером для наследования.

Некоторые распространенные примеры:

class Person {
}
class Employee extends Person {
}

В настоящее время мой любимый, но мне не нравится Person- > Employee, потому что "Сотрудник" не похож на забаву.

class Bicycle {
}
class MountainBike extends Bicycle {
}

Я нашел это в каком-то учебнике по Java, но не совсем очевидно, какие атрибуты должен иметь велосипед.

class Animal {
}
class Bird extends Animal {
}

То же, что и велосипед.

class A {
}
class B extends A {
}

Слишком абстрактно. Основная проблема заключается в том, что для такого класса потребуются еще более абстрактные атрибуты и методы.

Есть ли у кого-нибудь лучший пример для простой иерархии классов?

4b9b3361

Ответ 1

Класс Animal является классическим примером наследования класса по ряду причин.

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

Некоторые классы, такие как Mammal, будут распространять Animal, добавляя достаточно очевидные атрибуты ( "Warm-Blooded" и т.д.).

Другие, более проблематичные, проблемы, которые довольно распространены в развитии иерархии классов, совершенно очевидны, если вы проиллюстрируете с наследованием животных - и это хорошо для объяснения. Птицы летят, не так ли? Ну, не все птицы... Итак, как вы представляете полет? Есть, конечно, классические решения и богатая дискуссионная информация в Интернете о том, как решать проблемы и компромиссы, которые каждое решение представляет.

Таким образом, я настоятельно рекомендую использовать "Животное" в качестве вашего примера из-за богатства доступной информации и примеров.

Ответ 2

Мне нравится иерархия Stream. Идея состоит в том, что все может использовать поток без обычного ухода за потоком, а отдельные подклассы обрабатывают хранилище по-разному (например, NetworkStream, MemoryStream и FileStream в .NET).

Если вас интересуют интерфейсы, то IEnumerable<T> в .NET - отличный вариант - вы можете перебирать любую коллекцию, не заботясь о том, какова базовая структура данных.

Ответ 3

Автозапчасти могут быть интересными, например, у вас может быть

class part
{
    OEM
    Manufacturer
    Number
    Description
}

class Tire extends Part
{
   Speed
   Rating

}

Ответ 4

Я согласен с Джоном Скитом на примере его потоков. Возможно, это не идеально, но у него есть одно преимущество по сравнению с большинством примеров:

Это реалистично

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

То, что мой питомец разглядывает с наследованием. Его слишком часто преподают как нечто, что должно использоваться для выражения каждой иерархии, которую вы можете найти. Работник - это человек, не так ли? Поэтому класс Employee должен наследоваться от класса Person. Но человек также является LivingCreature, поэтому нам лучше иметь один из этих классов. И LivingCreature также является Организмом, поэтому у нас есть другой класс. И Организм... не стесняйтесь продолжать.

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

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

Ответ 5

Многие люди используют пример Shapes, но это на самом деле опасный. Проблема возникает, когда вы интуитивно решаете, что квадрат является подклассом прямоугольника.

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

Он назвал принцип замещения Лискова, и вы должны знать об этом при любой серьезной разработке OO.

Квадраты - это, конечно, подмножество прямоугольников, а не подкласс. В этом разница между ориентированными на данные и ориентированными на поведение подходами.

Как и Джон, я предпочитаю Streams в качестве примера. Это не сложно объяснить даже не программистам, а также его четким ориентированным на поведение, избегая контринтуитивности фигуры-примера.

Ответ 6

Если вы играете в видеоигры, может быть что-то вроде этого:

class enemy{
  Health
  Posx
  posy
  Etc
}

class orc : extends enemy{
 speed
 Strength
 etc
}

Ответ 7

Как насчет иерархии алгебраических выражений. Это отличный пример, потому что он использует как наследование, так и состав:

public interface Expression {
    int evaluate();

    public class Constant implements Expression {

        private final int value;

        public Constant(int value) {
            this.value = value;
        }

        @Override
        public int evaluate() {
            return this.value;
        }

        @Override
        public String toString() {
            return String.format(" %d ", this.value);
        }

    }

    public class Negate implements Expression {

        private final Expression expression;

        public Negate(Expression expression) {
            this.expression = expression;
        }

        @Override
        public int evaluate() {
            return -(this.expression.evaluate());
        }

        @Override
        public String toString() {
            return String.format(" -%s ", this.expression);
        }
    }

    public class Exponent implements Expression {

        private final Expression expression;
        private final int exponent;

        public Exponent(Expression expression, int exponent) {
            this.expression = expression;
            this.exponent = exponent;
        }

        @Override
        public int evaluate() {
            return (int) Math.pow(this.expression.evaluate(), this.exponent);
        }

        @Override
        public String toString() {
            return String.format(" %s ^ %d", this.expression, this.exponent);
        }

    }

    public class Addition implements Expression {

        private final Expression left;
        private final Expression right;

        public Addition(Expression left, Expression right) {
            this.left = left;
            this.right = right;
        }

        @Override
        public int evaluate() {
            return this.left.evaluate() + this.right.evaluate();
        }

        @Override
        public String toString() {
            return String.format(" (%s + %s) ", this.left, this.right);
        }
    }

    public class Multiplication implements Expression {

        private final Expression left;
        private final Expression right;

        public Multiplication(Expression left, Expression right) {
            this.left = left;
            this.right = right;
        }

        @Override
        public int evaluate() {
            return this.left.evaluate() *  this.right.evaluate();
        }

        @Override
        public String toString() {
            return String.format(" (%s * %s) ", this.left, this.right);
        }
    }

}

Затем вы можете указать мотивирующий пример, например:

public static void main(String[] args) {

    Expression two = new Constant(2);
    Expression four = new Constant(4);
    Expression negOne = new Negate(new Constant(1));
    Expression sumTwoFour = new Addition(two, four);
    Expression mult = new Multiplication(sumTwoFour, negOne);
    Expression exp = new Exponent(mult, 2);
    Expression res = new Addition(exp, new Constant(1));

    System.out.println(res + " = " + res.evaluate());

}

Что даст:

(  ( ( 2  +  4 )  *  - 1  )  ^ 2 +  1 )  = 37

Ответ 8

Я думаю, что Shape - хороший абстрактный класс. Существуют как 2D, так и 3D фигуры. 2D-формы обычно имеют область, а 3D-формы имеют объем. Оба могут иметь "местоположение" или "центр масс".

Некоторые предложения:

class Shape {..}

class Shape2D extends Shape {...}

class Circle extends Shape2D {...}

class Rectangle extends Shape2D {...}

class Polygon extends Shape2D {...}

class Shape3D extends Shape {...}

class Sphere extends Shape3D {...}

Ответ 9

Будучи поклонником игры с контратакой, я бы с удовольствием разделил:

enter image description here

Ответ 10

Я всегда любил:

class Shape {
}
class Square extends Shape {
}

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

Ответ 11

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

class Device
{
  void start();
  void stop();
  DeviceStatus status { get; }
}

class VideoDevice : Device
{
  ... methods for any/all video devices ...
}

class DiskDevice : Device
{
  ... methods for any/all disk devices ...
}

Ответ 12

Я использую, чтобы показывать шахматные фигуры. Базовый класс - это общий ChessPiece, King, Rook, Bishop и т.д.

Что мне нравится в этом примере:

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

Ответ 13

Мне нравится пример транспортных средств, поскольку он позволяет относительно чистому расширению включать интерфейсы в обсуждение (IAutomaticGearbox, кто-нибудь?)

Ответ 14

Что насчет

class Weapon 
{
}

class Gun : extends Weapon
{
}

class Knife : extends Weapon
{
}

и др.

Ответ 15

Наследование может быть сложным. Начнем с простейшего наследования "все наведение". Здесь вы наследуете только поведение, а не состояние. Например: оба человека и животные являются объектами Animate, которые демонстрируют поведение IsAlive. Чтобы использовать ваш пример:

class LivingThing {
   /* We propose a new type */
  public:
    virtual bool IsAlive() = 0;
    virtual void Birth() = 0;
    virtual void Death() = 0;
};

class Person : public  LivingThing {
    /* A real living thing */
  public:
    virtual bool IsAlive() { return true; }
    virtual void Birth() {}
    virtual void Death() {}
    /* .. and has some special behavior */
    void Marry();
    void Divorce();
};

class Animal: public  LivingThing {
    /* A real living thing */
  public:
    virtual bool IsAlive() { return true; }
    virtual void Birth() {}
    virtual void Death() {}
    /* .. and has some special behavior */
    void Bite();
    void Bark();
};

Я написал это с использованием синтаксиса С++, если у вас есть проблемы с пониманием любого из них, просто скажите это!

Ответ 16

Лучший пример, который я натолкнулся (и прочитал во многих книгах), - это тот, который использует Shape.

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

Ответ 17

Пример:

Подход "Все происходит от объекта".

Собака → Животное → Живая вещь → Объект

Собака - это животное, живое существо, которое, в свою очередь, является объектом.

Ответ 18

Вы можете найти хорошие примеры наследования классов в шаблонах проектирования.

  • Abstract_factory_pattern: Предоставляет способ инкапсулировать группу отдельных фабрик, имеющих общую тему, без указания их конкретного класса

  • Template_method_pattern: это шаблон поведения, который определяет программный скелет алгоритма в операции, откладывая некоторые шаги к подклассам.

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

Обратитесь к приведенным ниже сообщениям для примеров реального мира:

Когда использовать шаблон декоратора?

Шаблон дизайна шаблона в JDK не смог найти метод, определяющий набор методов, которые будут выполняться в порядке

Ответ 19

Интернет-магазин также может быть хорошим вариантом.

Product(id, title, price, description)
Book(isbn, publisher, pageNr) extends Product
CD(company, duration, genre, taskList) extends Product

Ответ 20

Мне нравится идея использования принтеров в качестве примера. Предположим, вы работаете в HP, Brother, Lexmark или какой-либо другой организации, и вам нужно придумать программу, которая способна генерировать специальные модули для разных операционных систем.

class Printer {
    private String brand;
    private String model;

    public void powerOn() {
        // Logic to power on
    }

    public void powerOff() {
        // Logic to power off
    }

    public void init() {
        // Bootstrap/prep the system
        // Check cartridge
        // Check paper
        // Ready for usage
}

class InkJetPrinter extends Printer {
    // Inherits fields and methods from Printer
    // Demonstrate method overriding and overloading
    // Add new InkJet specific behaviors, components etc.
}

class LaserPrinter extends Printer {
    // Inherits fields and methods from Printer
    // Demonstrate method overriding and overloading
    // Add new Laser specific behaviors, components etc.
}

class LabelPrinter extends Printer {
    // Inherits fields and methods from Printer
    // Demonstrate method overriding and overloading
    // Add new Label specific behaviors, components etc.
}

После демонстрации наследования вы можете перейти к абстракции и перегрузке/переопределению метода. Метод init в классе Printer является хорошим кандидатом для абстракции. Подклассам необходимо будет реализовать свои собственные процессы инициализации.

Вы также можете расширить этот пример и начать демонстрировать правильное использование интерфейсов и композиций. В чем разница между концепцией IS-A и HAS-A? Некоторые принтеры могут иметь возможности WiFi и Bluetooth. Или некоторые из принтеров InkJet могут иметь возможности сканирования, в то время как у других есть только фидеры и т.д. Как бы вы это реализовали?

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

Ответ 21

Лучшим из тех, что я всегда использовал, был Shapes.

Затем вы можете

Shape --> Quadrilateral
Quadrilateral --> Rectangle
Quadrilateral --> Trapezoid
Rectangle --> Square
Shape --> Triangle

и др.

Методы также легко угадать.

Shape::Area
Shape::Draw
Shape::Intersect (x,y)

Ответ 22

Иерархия фигур

Я полностью понимаю опасения относительно взаимосвязи между прямоугольниками и квадратами относительно принципа замещения Лискова. Однако есть способы сохранить этот принцип и по-прежнему иметь квадрат как подкласс Rectangle. Например, (Python), предположим следующее:

class Rectangle:
  def __init__(self, w, h):
     self.width = w
     self.height = h

  def setWidth(self, neww):
     if neww > 0: self.width = neww

  def setHeight(self, newh):
     if newh > 0: self.height = newh

Мы можем определить квадрат следующим образом:

class Square(Rectangle):
   def __init__(self, side):
       super().__init__(self, side, side) # width = height = side

   def setWidth(self, newside):
       super().setWidth(self, newside)
       super().setHeight(self, newside) # force height to match

   def setHeight(self, newside):
       super().setWidth(self, newside)  # force width to match
       super().setHeight(self, newside)

Таким образом, мы всегда можем использовать экземпляр Square каждый раз, когда нужен экземпляр Rectangle, тем самым сохраняя Liskov's.

Теперь, по моему опыту, мы также можем использовать Inheritance для обеспечения соблюдения политики (обычно ограничительной). Например, предположим, что у нас есть класс Shape с свойством цвета и соответствующим установщиком (setColor).

class Shape:

   def __init__(self, ..., color, ...):
        # ...
        self.color = color
        # ...

   def setColor(self, newcolor):
        self.color = newcolor

   #...

Предположим, мы хотим создать класс RedShape. Как мы можем запретить изменению кода клиента? Ответ: деактивируйте метод setColor, переопределив его.

class RedShape(Shape):
   def __init__(self, ...):
      super().__init__(self, 'red', ...)


   def setColor(self, newcolor):
      pass

Мы переопределили setColor в RedShape, чтобы сделать ничего.

SetColor в RedShape переопределяет значение в Shape, поэтому клиентский код не может изменить цвет экземпляров RedShape. В некотором роде мы создали более ограничительный подкласс, потому что у нас нет возможности изменять его цвет, но он не нарушает принцип подстановки.

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

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