Страницы

Поиск по вопросам

Показаны сообщения с ярлыком наследование. Показать все сообщения
Показаны сообщения с ярлыком наследование. Показать все сообщения

четверг, 9 апреля 2020 г.

Имитация множественного наследования

#наследование #typescript #множественное_наследование #mixins #прототипное_наследование

                    
Есть класс и функция-конструктор. Делается попытка реализовать класс, являющийся
чем-то типа потомка обоих. Точнее, методы из прототипа функции-конструктора копируются
в прототип класса-потомка, унаследованный от класса.

Да, я понимаю, что это не полноценное наследование, но для решения задачи этого хватает.

Проблема в другом. Как заставить тайпскрипт воспринимать копируемые методы?

class First {
  someMethod() {
    console.log('someMethod from First');
  }
}

function Second() {
  console.log('Second');
}

Second.prototype.doSmth = function () { 
  console.log('doSmth from Second');
}

interface IBoth {
  someMethod()
  doSmth()
}

class Both extends First /* implements IBoth */ {
  constructor() {
    console.log('constructor of Both');
    super();
    Second.call(this);
  }
}

for (let key in Second.prototype) {
  Both.prototype[key] = Second.prototype[key];
}


На самом деле, надо обеспечить видимость методов ещё на уровень дальше

class Final extends Both {
  doIt() {
    this.someMethod();
    //this.doSmth(); // Надо заставить видеть метод тут
    (this as any as IBoth).doSmth(); // Компилируется, но это ужас
  }
}


если при этом методы не будут видны из самого Both, то это годится.

Вот что я уже пробовал:


При попытке написать

class Both extends First implements IBoth {


возникает ошибка, что я не реализую методы интерфейса.
При переименовании Both в _Both и использовании

var Both = _Both as typeof _Both;


всё остаётся как было, что логично, поскольку тут никак не используется First.
При переименовании Both в _Both и использовании

var Both = _Both as typeof IBoth;


говорит, что не может найти имя IBoth.


Пробовал ещё несколько вариантов, но они совсем бредовые.
Что ещё можно сделать?



Попробовать можно тут: http://www.typescriptlang.org/Playground
Полный код для проверки

Запустить (код из правой панели) после добавления строки:

(new Final).doIt();


Вывод при запуске при раскомментированной строке this.doSmth();  

constructor of Both
Second
someMethod from First
doSmth from Second
doSmth from Second




PS: Этот вопрос на английском.
    


Ответы

Ответ 1



Интерфейс вообще не нужен, нужно просто объявить прототипное поле с нужным типом: doSmth: () => void Она видима как свойство, а не как метод, но это непринципиально. Полный код: class First { someMethod() { console.log('someMethod from First'); } } function Second() { console.log('Second'); } Second.prototype.doSmth = function () { console.log('doSmth from Second'); } class Both extends First { constructor() { console.log('constructor of Both'); super(); Second.call(this); } doSmth: () => void } for (let key in Second.prototype) { Both.prototype[key] = Second.prototype[key]; } class Final extends Both { doIt() { this.someMethod(); this.doSmth(); //Both.prototype.doSmth(); // ok //Final.prototype.doSmth(); // ok } } PS: Надо было гуглить не всяческие варианты с наследованием, а typescript class prototype variable - сразу нашёл подходящий вариант.

пятница, 13 марта 2020 г.

Наследование с вложенными классами

#java #наследование


class SinglyLinkedList extends AbstractList {
protected Node first;
protected Node last;
protected int size;

static class Node {
    Node next;
    Object val;

    Node(Node next, Object val) {
        this.next = next;
        this.val = val;
    }

    Node() {
        this.next = null;
        this.val = null;
    }
}


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

public class DoublyLinkedList extends SinglyLinkedList {
static class Node extends SinglyLinkedList.Node {
    Node prev;

    Node(Node next, Object val, Node prev) {
        super(next, val);
        this.prev = prev;
    }
}


Уверен, что делаю это не правильно. 
    


Ответы

Ответ 1



Правильно будет вынести общую логику списков в абстрактный класс и унаследовать от него оба списка. Если Node хранит только состояние, то пусть он будет отличаться в реализациях списка. Либо его можно вынести в protected / package-private классы, для того чтобы была видимость только внутри пакета. Статические внутренние классы не нарушают SOLID, с их помощью обычно выполняют логическую группировку сущностей внутри класса. В вашем случае плохо то, что вы усложняете дизайн, что сопряжено в последующей тестируемостью и расширяемостью кода, не извлекая при этом никакой практической пользы. Примеры из JDK: AbstractSequentialList LinkedList

среда, 4 марта 2020 г.

Агрегация и наследование

#cpp #ооп #наследование


Можно ли два класса одновременно связать путем агрегации и наследования? 
    


Ответы

Ответ 1



Конечно, можно: class Base { }; class Derived: public Base { Base b; }; int main(int argc, const char * argv[]) { Derived d; }

C# интерфейсы и абстрактные классы

#c_sharp #классы #наследование #интерфейс


Доброго!

Есть интерфейс IInterface,

Есть абстрактный класс его реализующий AClass

И есть абстрактный класс 

AClasses
{
  List Elements;

  void SomeMethod()
  {
    T _el = new T();
    _el.Some();
    Elements.Add(_el);
  }
}


Вот в методе SomeMethod() проблема так как это сделать нельзя. T должен наследоваться
от IInterface ну или на крайний случай от AClass. 

Так вот вопрос. Как заполнить List Elements в абстрактном классе
    


Ответы

Ответ 1



Например, при помощи ограничений на T: class AClasses where T : IInterface, new() { ... Это будет работать, если у ваших классов есть пустой конструктор. Если пустого конструктора нет, то создание экземпляров нужно делать как-нибудь по-другому, например, через фабрику или создающий делегат.

пятница, 21 февраля 2020 г.

Почему родительский объект не приводится к типу дочернего?

#cpp #функции #наследование


1) Пытаюсь запустить такой код:

#include 

struct A
{
    virtual void m() {
        std::cout << "A" ;
    }
};

struct B : public A
{
    void m() {
        std::cout << "B";
    }
};

int main()
{
    A a;
    B *b;

    b = &(B)a;
    b->m();

    return 0;
}


Если в main прописать:

int main()
{
    A *a;
    B b;

    a = &b;
    a->m();

    return 0;
}


то все ожидаемо работает. Но вот вопрос, почему нельзя привести тип родителя к типу
наследника, а наоборот можно? Как здесь работает механизм приведения типов? Или все
же можно привести тип родителя к типу наследника?

1) Еще такой вопрос, если написать так:

struct A
{
     void m() {
        std::cout << "A" ;
    }
};

struct B : public A
{
    void m() {
        std::cout << "B";
    }
};

int main()
{
    A *a;
    B b;

    a = &b;
    a->m();

    return 0;
}


то вызывается m() у обьекта A. Но объекта А как бы не существует. Мне не понятно,
где же тогда вызывается эта m(), у какого объекта типа A если обьекта типа A не существует?
    


Ответы

Ответ 1



Дочерний объект содержит в себе родительский объект, поэтому приведение дочернего объекта к родительскому объекту не вызывает проблем и может быть осуществлено неявно. С другой стороны, родительский объект ничего не знает о том, какие и как могут быть определены дочерние от него объекты. Он такой информацией не обладает. А потому нет неявного преобразования из родительского объекта в какой-либо дочерний. Когда нет виртуальных функций, то происходит статическое связывание вызываемых функций с типом объекта на этапе компиляции. В этой программе #include struct A { void m() { std::cout << "A" ; } }; struct B : public A { void m() { std::cout << "B"; } }; int main() { A *a; B b; a = &b; a->m(); return 0; } статическим типом указателя a является тип struct A, поэтому компилятор связывает функцию, объявленную в этом типе. На самом деле то же самое происходит и для виртуальных функций, то есть компилятор осуществляет поиск имени функции в соответствии со статическим типом указателя или ссылки на объект. Другое дело, что вызов функции осуществляется с помощью механизма, использующего таблицу виртуальных функций. Адрес этой таблицы разрешается динамически на этапе выполнения программы, хотя в некоторых простых случаях, когда статический и динамический типы совпадают, то может это делать на этапе компиляции.

Ответ 2



привидение родителя к наследнику выполнять разрешается и это называется downcast, которое можно делать, например, так b = static_cast(&a); А в вашем первом примере будет ошибка компиляции ввиду некорректного с точки зрения синтаксиса языка С++ кода b = &(B)a; вероятно, вы хотели написать так b = (B*)&a; edit ошибка компиляции будет, как отметил Ant, не с точки зрения синтаксиса а с точки зрения семантики языка С++ это каст в стиле С - которого лучше избегать, а в случае с downcast подавно - предпочтительнее применять специальные и более безопасные операторы явного приведения типов из С++ (как написано выше до этого) А вобще, подобный downcast в некоторых случаях может приводить к неопределённому поведению по поводу третьего вопроса - выводится A так как эта функция в родительском классе не виртуальна - в этом и основной смысл виртуальных функий - обеспечение вызова переопределённого в наследнике родительского метода через указатель на базовый класс (родительский класс), соответственно если в базовом классе функция не виртуальна то он и вызовется

Ответ 3



Спасибо, ampawd! Я прозрел! Код ниже выводит ВВ, как и типа ожидалось #include class A {public: virtual void m() { std::cout << "A" ; } }; class B : public A {public: void m() { std::cout << "B"; } }; int main() { A *a; B b; a = &b; a->m(); b.m(); return 0; }

воскресенье, 16 февраля 2020 г.

Почему родительский объект не приводится к типу дочернего?

#cpp #функции #наследование


1) Пытаюсь запустить такой код:

#include 

struct A
{
    virtual void m() {
        std::cout << "A" ;
    }
};

struct B : public A
{
    void m() {
        std::cout << "B";
    }
};

int main()
{
    A a;
    B *b;

    b = &(B)a;
    b->m();

    return 0;
}


Если в main прописать:

int main()
{
    A *a;
    B b;

    a = &b;
    a->m();

    return 0;
}


то все ожидаемо работает. Но вот вопрос, почему нельзя привести тип родителя к типу
наследника, а наоборот можно? Как здесь работает механизм приведения типов? Или все
же можно привести тип родителя к типу наследника?

1) Еще такой вопрос, если написать так:

struct A
{
     void m() {
        std::cout << "A" ;
    }
};

struct B : public A
{
    void m() {
        std::cout << "B";
    }
};

int main()
{
    A *a;
    B b;

    a = &b;
    a->m();

    return 0;
}


то вызывается m() у обьекта A. Но объекта А как бы не существует. Мне не понятно,
где же тогда вызывается эта m(), у какого объекта типа A если обьекта типа A не существует?
    


Ответы

Ответ 1



Дочерний объект содержит в себе родительский объект, поэтому приведение дочернего объекта к родительскому объекту не вызывает проблем и может быть осуществлено неявно. С другой стороны, родительский объект ничего не знает о том, какие и как могут быть определены дочерние от него объекты. Он такой информацией не обладает. А потому нет неявного преобразования из родительского объекта в какой-либо дочерний. Когда нет виртуальных функций, то происходит статическое связывание вызываемых функций с типом объекта на этапе компиляции. В этой программе #include struct A { void m() { std::cout << "A" ; } }; struct B : public A { void m() { std::cout << "B"; } }; int main() { A *a; B b; a = &b; a->m(); return 0; } статическим типом указателя a является тип struct A, поэтому компилятор связывает функцию, объявленную в этом типе. На самом деле то же самое происходит и для виртуальных функций, то есть компилятор осуществляет поиск имени функции в соответствии со статическим типом указателя или ссылки на объект. Другое дело, что вызов функции осуществляется с помощью механизма, использующего таблицу виртуальных функций. Адрес этой таблицы разрешается динамически на этапе выполнения программы, хотя в некоторых простых случаях, когда статический и динамический типы совпадают, то может это делать на этапе компиляции.

Ответ 2



привидение родителя к наследнику выполнять разрешается и это называется downcast, которое можно делать, например, так b = static_cast(&a); А в вашем первом примере будет ошибка компиляции ввиду некорректного с точки зрения синтаксиса языка С++ кода b = &(B)a; вероятно, вы хотели написать так b = (B*)&a; edit ошибка компиляции будет, как отметил Ant, не с точки зрения синтаксиса а с точки зрения семантики языка С++ это каст в стиле С - которого лучше избегать, а в случае с downcast подавно - предпочтительнее применять специальные и более безопасные операторы явного приведения типов из С++ (как написано выше до этого) А вобще, подобный downcast в некоторых случаях может приводить к неопределённому поведению по поводу третьего вопроса - выводится A так как эта функция в родительском классе не виртуальна - в этом и основной смысл виртуальных функий - обеспечение вызова переопределённого в наследнике родительского метода через указатель на базовый класс (родительский класс), соответственно если в базовом классе функция не виртуальна то он и вызовется

Ответ 3



Спасибо, ampawd! Я прозрел! Код ниже выводит ВВ, как и типа ожидалось #include class A {public: virtual void m() { std::cout << "A" ; } }; class B : public A {public: void m() { std::cout << "B"; } }; int main() { A *a; B b; a = &b; a->m(); b.m(); return 0; }

четверг, 13 февраля 2020 г.

Может ли базовый класс знать что-то про методы потомков?

#python #наследование #методы


Разбираюсь сейчас с наследованием в Python, а также с магическими методами. Имеется код:

class Researcher:
    def __getattr__(self, name):
        return 'Nothing found :()\n'

    def __getattribute__(self, name):
        print('Looking for {}'.format(name))
        return super().__getattribute__(name)


obj = Researcher()

print(obj.attr)
print(obj.method)
print(obj.DFG2H3J00KLL)


Возник вопрос: откуда объект object() (который создался после вызова super()) знает
о методе __getattr__ класса Researcher? Разве базовый класс знает что-то про методы
потомков?
    


Ответы

Ответ 1



super() вовсе не возвращает просто объект родительского класса. В этом легко убедиться, если перед строчкой где вы его вызываете поставить print(super()). Если такое проделать, то код в этом месте выведет: , > Как видите, это не экземпляр родительского класса object. Это особый объект-обёртка, который управляет вызовом методов из классов выше по иерархии для текущего объекта. И вот эта обёртка и знает всё и про методы Researcher, и про всех его родителей. UPD: Действительно, как правильно отметил автор вопроса в комментариях, если заменить super() на object(), то код всё равно работает. Но это уже не связано с наследованием, а связано с нюансами работы __getattr__ и __getattribute__ Дело в том, что __getattribute__, если он реализован, то он срабатывает для вообще всех обращений к атрибутам - как к существующим, так и к несуществующим. В то время, как __getattr__ срабатывает только для обращения к несуществующим атрибутам - то есть тогда, когда при обращении к атрибуту произошло исключение AttributeError. То есть вот что происходит, когда вы меняете super на object: Код делает обращение к атрибуту объекта. Так как это объект класса Researcher, для которого есть метод __getattribute__, то код выполняет именно этот метод. Внутри него идёт обращение к object().__getattribute__(name) Для экземпляра класса object обращение к аттрибуту работает стандартно, то есть для неизвестного аттрибута, такого как DFG2H3J00KLL будет выброшено исключение AttributeError Т.к. при возникновении этого исключение всегда проверяется, есть ли у объекта __getattr__, и так как всё это происходит всё ещё для экземпляра Researcher, а у него этот метод есть, то именно он и срабатывает. Проиллюстрировать это можно следующими примерами. Демонстрация того, что исключение AttributeError действительно выбрасывается: class Researcher: def __getattr__(self, name): return 'Nothing found :()\n' def __getattribute__(self, name): print('Looking for {}'.format(name)) try: object().__getattribute__(name) except Exception as e: print('!!! ', type(e)) obj = Researcher() print(obj.attr) print(obj.method) print(obj.DFG2H3J00KLL) Демонстрация того, что даже если мы не будем обращаться ни к object, ни к super, а просто руками выбросим AttributeError, то всё будет работать так же, как и изначально: class Researcher: def __getattr__(self, name): return 'Nothing found :()\n' def __getattribute__(self, name): print('Looking for {}'.format(name)) raise AttributeError obj = Researcher() print(obj.attr) print(obj.method) print(obj.DFG2H3J00KLL)

воскресенье, 9 февраля 2020 г.

Как правильно модифицировать базовый конструткор?

#c_sharp #ооп #наследование


Мучаю наследование. Стакнулся на моменте использования конструктора базового класса
в производном.

Базовый класс:

namespace ConsoleApp12
{
    class Employee
    {
        public string Name { get; set; }
        public float Pay { get; set; }
        public int Age { get; set; }
        public Employee()
        {

        }
        public Employee(string name, int age, float pay)
        {
            Name = name;
            Age = age;
            Pay=pay;
        }

    }
}


Производный:

namespace ConsoleApp12
{
    class Manager : Employee
    {
        public int SalaryIndex { get; set; }
        public Manager(int salIndex) : base(name, age, pay)
        {

        }
    }
}


Получаю ошибку: 

Кто может объяснить что я делаю не так?
    


Ответы

Ответ 1



Вы вызываете конструктор базовогo класса, передавая туда в качестве параметров переменные, которых в этом месте нет. class Manager : Employee { public int SalaryIndex { get; set; } public Manager(int salIndex) : base() { SalaryIndex = salIndex; } public Manager(int salIndex, string name, int age, float pay) : base(name, age, pay) { SalaryIndex = salIndex; } }

Ответ 2



Если в потомке уже определены некоторые параметры базового класса (например, есть значения по умолчанию, либо значения вычисляются из других параметров), то, если эти свойства не приватные, их можно инициализировать в конструкторе потомка. class Manager : Employee { public int SalaryIndex { get; set; } public Manager(int salIndex) : base() { SalaryIndex = salIndex; Name = "Имя по умолчанию"; Age = 20; Pay = 1000 * Age; } }

воскресенье, 2 февраля 2020 г.

Java: наследование контркуктора

#java #наследование


Объясните дураку(в основном работаю с php), почему при наследовании исключений, не
наследуются все конструкторы.

Кейс №1
Есть класс расширяющий исключения(без конструктора)

package test.exceptions;
public class StatusException extends Exception {
    final public static String STATUS_NOT_EXIST = "Status: not exist";
}


Есть код выкидывающий исключение:

public static void test() throws StatusException {
    String message= StatusException.STATUS_NOT_EXIST;
    throw new StatusException(message);
}


При компиляции получаю ошибку о несоответствие сигнатур конструкторов

Error:(26, 15) java: constructor StatusException in class test.exceptions.StatusException
cannot be applied to given types;
  required: no arguments
  found: java.lang.String
  reason: actual and formal argument lists differ in length


Кейс №2
Исключение с конструктором принимающим String

package test.exceptions;
public class StatusException extends Exception {
    final public static String STATUS_NOT_EXIST = "Status: not exist";

    public StatusException(String message) {
        super(message);
    }
}


Код выкидывающий исключение:

public static void test() throws StatusException {
    String message= StatusException.STATUS_NOT_EXIST;
    throw new StatusException();
}


Ошибка

Error:(24, 15) java: constructor StatusException in class test.exceptions.StatusException
cannot be applied to given types;
  required: java.lang.String
  found: no arguments
  reason: actual and formal argument lists differ in length

    


Ответы

Ответ 1



Конструкторы не наследуются. Касательно первого примера - вы должны явно создать конструктор с параметром: public class StatusException extends Exception { final public static String STATUS_NOT_EXIST = "Status: not exist"; public StatusException(String message) { super(message); } } Касательно второго примера. Если явно не задан ни один конструктор, то по умолчанию создается пустой конструктор без параметров. То есть в первом примере у вас будет неявно создан такой конструктор. Если же явно определен хотя бы один конструктор, то конструктор без параметров следует так же явно указать. Следовательно, для второго примера нужно так: public class StatusException extends Exception { final public static String STATUS_NOT_EXIST = "Status: not exist"; public StatusException(String message) { super(message); } public StatusException() { //nothing or call super } }

Константное поле для родительского класса

#c_sharp #наследование


я хочу что бы в абстрактном базовом классе Base было константное поле которое должно
задаваться у каждого потомка Derived1, Derived2 своим значением.



UPD: Забыл спросить: поле должно быть статическим?



UPD: Переформулирую вопрос.
Есть базовый класс В в котором хочу обязать всех потомков реализовывать статическое
поле size, которое будет защищено от изменения даже внутри класса. Есть 2 потомка:
класс Х и Y. В классе X поле size должно равняться 6. В классе Y поле должно равняться 10.
    


Ответы

Ответ 1



public class Base { public int ConstValue { get; private set; } public Base(int aConstValue) { ConstValue = aConstValue; } } public class Child1 : Base { public Child1() : base(1) { } } public class Child2 : Base { public Child2() : base(2) { } } @andreycha - А лучше сделать свойство абстрактным. public abstract class Base { public abstract int ConstValue { get; } } public class Child1 : Base { public override int ConstValue { get { return 1; } } } public class Child2 : Base { public override int ConstValue { get { return 2; } } }

Ответ 2



Используйте свойство только для чтения: abstract class Player { public abstract string Name { get; } } class Bot : Player { public override string Name => "Robot"; }

пятница, 31 января 2020 г.

Массив с разными типами данных

#cpp #массивы #классы #указатели #наследование


Есть основной класс Human, есть наследуемый от него Doctor. Нужно сделать массив,
в котором будут как объекты первого класса, так и второго. Я своим скудным умишкой
додумался только до этого.

#include "Human.h"
#include "Doctor.h"

using namespace std;

void main() {

int n = 0;
cout << "How many? ";
cin >> n;
int m;
Human **uni;
ofstream fout("humans.txt", ios::app);

for (int i = 0; i < n; i++) {
    cout << "1 for Human\n2 for Doctor ";
    cin >> m;
    switch (m) {
    case 1: {
                uni[i] = new Human(); \\в этой строке возникает ошибка
                uni[i]->Set(cin);
                uni[i]->InFile(fout);
                break;
    }
    case 2: {
                uni[i] = new Doctor();
                uni[i]->Set(cin);
                uni[i]->InFile(fout);
                break;
    }
        default: cout << "Wrong input."; system("pause");
              }
    }
}


Но вылазит ошибка:


  Error 1   error C4700: uninitialized local variable


Подскажите, что нужно исправить, чтобы заработало. Или может это можно сделать как-то
по-другому?
    


Ответы

Ответ 1



Переменная uni объявлена, но не инициализирована Human **uni; Поэтому использования случайного значения этой переменной в предложениях uni[i] = new Human(); и uni[i] = new Doctor(); ведет к неопределенному поведению программы, о чем компилятор вам любезно сообщил. Вам сначала надо выделить память под массив указателей на базовый класс uni = new Human *[n]; и только затем вы можете инициализировать элементы этого массива в предложениях uni[i] = new Human(); и uni[i] = new Doctor(); Также вы должны не забыть освободить всю выделенную память в обратном порядке, когда массив объектов больше вам будет не нужен. В подобных случаях лучше использовать стандартный класс std::vector. Например, #include //... std::vector uni; И еще лучше использовать умные указатели #include #include //... std::vector> uni; В этом случае вам не надо беспокоиться об освобождении памяти и вызовах деструкторов объектов. Имейте в виду, что в C++ функция main без параметров должна быть объявлена как int main() то есть она должна иметь тип возвращаемого значения int. Кроме того ваша программа содержит баг, так как в случае, когда пользователь в цикле введет неверное значение, то есть ни 1 и ни 2, то элемент массива останется не инициализированным и пропущенным. По крайней мере вам следует ввод значения также заключить в цикл. Например, do { cout << "1 for Human\n2 for Doctor "; cin >> m; } while ( m != 1 && m != 2 ); И только после этого цикла размещать switch предложение. Также вместо магических чисел 1 и 2 лучше объявить перечисление. Например enum { HUMAN = 1, DOCTOR = 2 }; и использовать эти константы в switch предложении switch (m) { case HUMAN: { uni[i] = new Human(); \\в этой строке возникает ошибка uni[i]->Set(cin); uni[i]->InFile(fout); break; } case DOCTOR: { uni[i] = new Doctor(); uni[i]->Set(cin); uni[i]->InFile(fout); break; } //... Надеюсь, что деструктор базового класса объявлен со спецификатором функции virtual, не так ли?:)

Ответ 2



Надо для массива память выделить. Human **uni = new Human*[n];

среда, 29 января 2020 г.

Почему все переменные в интерфейсе являются final?

#java #ооп #наследование #интерфейс


И всетаки, почему все переменные в интерфейсе являются final? Для чего это ключевое
слово тут? В чем его смысл?

Для чего вообще это ключевое слово там?

Пусть бы они были не final! 

Что тогда?
    


Ответы

Ответ 1



Интерфейс определяет поведение объектов, а не конкретную реализацию. Если бы мы могли переопределять значения в дочерних интерфейсах или классах, реализующих этот интерфейс, то часть нашего интерфейса становилась бы частью конкретной реализации, что нарушало бы исходное понятие термина "интерфейс". Поскольку создавать экземпляры интерфейса нельзя и интерфейс не должен быть связан с конкретной реализацией, то неявно используется ключевое слово static (то есть данное поле определено в контексте, где нет никаких объектов). Отсутствие модификатора final также приводило бы к неоднозначности и путанице, поскольку каждый класс/интерфейс мог бы по своему усмотрению изменять это значение, что влияло бы на другие классы/интерфейсы. Но тут стоит заметить, что неизменяемость характерна для примитивных типов данных и immutable классов, для остальных же классов - final гарантирует только неизменяемость ссылки на область памяти, в которой расположен объект. Поля самого объекта при этом могут быть изменяемы. Если предположить возможность, что интерфейс может иметь нестатические изменяемые поля, то тогда бы возникла проблема с ромбовидным наследованием (diamond inheritance). Допустим, есть интерфейс A, в котором определено поле int v = 10. От этого интерфейса наследуются два других интерфейса B и C, каждый из которых переопределяет нашу переменную v (например, в B имеем значение 15, а в C - 20). Теперь мы определяем еще один интерфейс D, который наследуется от B и C. Тогда возникает вопрос: какое значение поле v должно унаследовать?

Ответ 2



final использует для того что бы переменному давали значение только один раз. К примеру: Если классу дали final тогда нельзя из этого класса создать подкласс. Если в классе есть методы с final. И вы создаете подклассы, Вы не сможете Переписать (Overwrite) этот метод. Это удобно, когда Вам не хочется что бы метод переписивали, и использовали в других целях. Примеру, функция sin(x). Если переменная имеет final, тогда вы можете передать значение этому переменному только 1 раз. Далже компилятор не дасть изменять значение. Это Удобно когда нужно объявлять константы. Что бы не чайно не изменить значение Переменной-константы. К примеру, pi = 3.1415

вторник, 28 января 2020 г.

java полиморфизм

#java #ооп #наследование #полиморфизм



  Использование дочернего класса в качестве родительского класса


Важным аспектом полиморфизма является возможность использовать объект дочернего класса,
где ожидается объект его родительского класса.
Один из способов сделать это явно - создать экземпляр объекта дочернего класса в
качестве члена родительского класса. 

Теперь вопрос

ЗАчем 

Noodle biangBiang = new Spaghetti();


если мы можем написать и результат получим один и тот же 

Spaghetti biangBiang = new Spaghetti();


Пример всего кода

class Noodle {

  protected double lengthInCentimeters;
  protected double widthInCentimeters;
  protected String shape;
  protected String ingredients;
  protected String texture = "brittle";

  Noodle(double lenInCent, double wthInCent, String shp, String ingr) {

    this.lengthInCentimeters = lenInCent;
    this.widthInCentimeters = wthInCent;
    this.shape = shp;
    this.ingredients = ingr;

  }

  public String getCookPrep() {

    return "Boil noodle for 7 minutes and add sauce.";

  }

  public static void main(String[] args) {
    Noodle n = new Noodle(30.0, 0.2, "round", "semolina flour");
    System.out.println(n.getCookPrep());
    Spaghetti a = new Spaghetti();
    System.out.println(a.getCookPrep());


  }

}

class Spaghetti extends Noodle {

  Spaghetti() {

    super(30.0, 0.2, "round", "semolina flour");

  }

  public String getCookPrep() {

    return "Boil spaghetti for 8 - 12 minutes and add sauce, cheese, or oil and garlic.";

  }

}


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


Ответы

Ответ 1



Например, для этого public static void main(String[] args) { Noodle n = new Noodle(30.0, 0.2, "round", "semolina flour"); Spaghetti a = new Spaghetti(); printCookPrep(n); printCookPrep(a); } public static void printCookPrep(Noodle n){ System.out.println(n.getCookPrep()); }

Ответ 2



Например, у вас имеется база данных сотрудников фирмы. В ней есть абстрактный класс "Сотрудник" (который имеет поля имя и зарплата, а также методы доступа к ним), и от него наследуются более конкретные "Менеджер", "Программист", "Уборщик" и т.д., которые имеют свои более специфические состояния и поведения. И вот задача: вывести список всех сотрудников и их зарплату в один файл. Для абстрактных классов можно создавать объектные переменные, но такие переменные должны ссылаться на объект неабстрактного класса. Если заранее собирать в список всех сотрудников, то такая задача решится за один обход коллекции. public abstract class Employee { private String name; private Integer pay; public void setName(String aName) { name = aName; } public String getName() { return name; } public void setPay(int value) { pay = value; } public Integer getPay() { return pay; } } public class Coder extends Employee { private String position; public Coder(String name, int pay, String _position) { setName(name); setPay(pay); setPosition(_position); } public void setPosition(String value) { position = value; } public String getPosition() { return position; } } public class Manager extends Employee { public Manager(String name, int pay) { setName(name); setPay(pay); } } public class TEST { public static void main(String[] args) { LinkedList employees = new LinkedList(); // Нанимаем менеджера employees.add(new Manager("John", 25000)); // Нанимаем программиста Coder coder1 = new Coder("Nick", 30000, "Junior"); employees.add(coder1); // Нанимаем ещё менеджера Manager manager1 = new Manager("Cameron", 25000); employees.add(manager1); // Выводим список всех сотрудников for (Employee current : employees) { System.out.println(current.getName() + " - " + current.getPay().toString()); } } } Вот пример реализации принципа полиморфизма, к объектам подкласса можно обращаться из ссылочных переменных их суперкласса. НО здесь например нельзя будет из коллекции вызвать метод setPosition для объекта класса Coder, так как класс Employee не имеет о нём понятия. employees.get(1).setPosition("Middle"); //error: cannot find symbol // правильное решение, однако для этого необходимо проверять, является ли данный объект коллекции объектом требуемого класса, например используя instanceof Coder myCoder = employees.get(1); myCoder.setPosition("Middle"); Могу посоветовать книгу Кей Хорстманна "Java Библиотека профессонала" том 1. В главе о "Наследование" об этом рассказывается подробнее и с примерами кода.

Хранение наследников в массиве типа базового класса

#cpp #наследование


Товарищи, подскажите, пожалуйста, одну вещь.

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

SuperClass ptrSuper[100];
for (int i = 0; i < 100; i++)
{
    if (i / 2)
        ptrSuper[i] = new Naslednik1(....);
    else
        ptrSuper[i] = new Naslednik2(....);
};


Как здесь можно поступить?
    


Ответы

Ответ 1



Это не ответ на ваш вопрос, т.к на него уже ответили, но просто некоторая информация для размышления. Если бы вариант вашего кода выглядел следующим образом, то эта запись также возможна, однако, в этом случае будет происходит нежелательное явление под названием срезка / slicing : SuperClass ptrSuper[100]; for (int i = 0; i < 100; i++) { if (i % 2) ptrSuper[i] = Naslednik1(....); else ptrSuper[i] = Naslednik2(....); }; Суть явления срезки заключается в том, что на деле вместо пересвязывания указателей (а это то, чего мы хотели на самом деле) происходит неочевидное полное копирование объектов, причем в данном случае копирование только части SuperClass. Т.е во всех созданных объектах лишняя информация, которая составляет "суть" классов Naslednik1, Naslednik2, будет безвозвратно утеряна. Другие примеры срезки можно изучить здесь.

Ответ 2



Смотрите, как я понял у вас есть иерарархия классов. Например SuperClass и от него наследуется два класса Derived1 и Derived2: class SuperClass { ... }; class Derived1 : public SuperClass { }; class Derived2 : public SuperClass { }; и вам необходим некий контейнер, в котором вы хотите хранить объекты этих классов. Скажу сразу тут желательно использовать указатели... Почему? Есть замечательная книжка Скотта Мейерса, там как раз очень хорошо объясняется в одном из правил, что прозойдёт "срезка", впрочем http://hashcode.ru/users/751/mikillskegg уже писал про это тут. дак вот, я бы хранил эти объекты так std::vector< *SuperClass > OurContainer; OurContainer.push_back( new Derived1 ); OurContainer.push_back( new Derived2 ); ... в конце не забываем удалять объекты их кучи, "пробагемся" по вектору с помощью итератора и удаляем каждый указатель.

Ответ 3



if (i%2) ptrSuper[i] = new Naslednik1(....); else ptrSuper[i] = new Naslednik2(....); в элементы с четными индексами (0,2 ... 98) попадут Naslednik1, а с нечетными (1 ... 99) Naslednik2 Это, если вы с описанием наследования ничего не напутали.

Ответ 4



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

Ответ 5



Два момента: Используйте std::vector вместо стандартного массива Используйте std::shared_ptr вместо сырого указателя Используйте фабричный метод, вместо размещения логики создания экземпляра нужного типа в месте помещения созданного экземпляра в вектор Вот пример кода: // Может быть пока и не нужно, но значительно проще, когда // за процесс создания экземпляра отвечает отдельный метод. // Так проще об этом думать, и развивать std::shared_ptr CreateSuperClass(int index) { return index%2 ? std::make_shared(new Derived1) : std::make_shared(new Derived2); } std::vector> mySuperclasses; for(int n = 0; n < size; n++) { mySuperClasses.push_back(CreateSuperClass(n)); } Преимущества: Не нужно задумываться о процессе освобождения элементов вектора благодаря std::shared_ptr Процесс создания вынесен в отдельный метод, что упрощает чтение и позволяет изменять процесс создания не трогая дополнительные участки кода

пятница, 24 января 2020 г.

Суть наследования в php

#php #ооп #наследование


Добрый день. Прошу помощи разобраться в сути наследования языка php.

В мануале есть такой пример:
http://php.net/manual/ru/language.oop5.late-static-bindings.php

foo();
        static::foo();
    }
}

class B extends A {
   /* foo() будет скопирован в В, следовательно его область действия по прежнему А,
      и вызов будет успешен*/
}

class C extends A {
    private function foo() {
        /* исходный метод заменен; область действия нового метода С */
    }
}

$b = new B();
$b->test();
$c = new C();
$c->test();   //не верно
?>


Меня смущает комментарий 


  /* foo() будет скопирован в В, следовательно его область действия по
  прежнему А,
        и вызов будет успешен*/


Что значит скопирован? Скопирован всмысле отнаследован? Но ведь невозможно наследовать
приватные методы и свойства.
И правильно ли я понимаю что вызвать $b->foo() нельзя как раз потому, что метод 
приватный? Но почему тогда из метода test доступно this->foo(), ведь this указывает
нам на экземпляр текущего обьекта, а не класса? А объект у нас b.
Спасибо.
    


Ответы

Ответ 1



При наследовании - унаследуются метод test() и метод foo(), у каждого будет область видимости А, хоть и класс В, следовательно когда вы обращаетесь к методу test() в классе В - он может обратиться к foo() т.к. у них одна область видимости. Но в случае с классом С - у вас переобъявляется метод foo() а следовательно у него область видимости уже становится С и метод с областью видимости А не может к нему обращаться, так как это приватный метод. А значит чтоб вы могли использовать метод test() - его тоже нужно переопределить в классе С

четверг, 23 января 2020 г.

Наследование от функции

#javascript #наследование #ecmascript_2015


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

class Smth extends Function {
  // Что здесь сделать
}

(new Smth)() // чтобы тут вернулось не undefined?


PS: Этот вопрос на английском.
    


Ответы

Ответ 1



Взяв в качестве базового вот этот ответ, я немного его доработал и вот что получилось. Дорабатывал я две вещи. Во-первых, передавать функцию в конструктор базового класса - не самое красивое решение; потому я переадресую реальный вызов методу call. Обычно этот метод вызывает основную функцию - но у меня получилось наоборот (сигнатура удачно совпала). Во-вторых, надо обеспечить адекватное значение для this (желательно чтобы это был сам объект-функция, потому что IDE будет думать к контекстных подсказках именно так). При этом надо не потерять реальный this (я его передаю первым аргументом). Ну и плюс надо убрать не имеющую смысла информацию из вывода toString() - никому не интересно видеть там замыкание вида "взять аргументы и передать дальше". Получилось вот так: class ExtendableFunction extends Function { constructor() { super() function f(...args) { return f.call(this, ...args); } Object.setPrototypeOf(f, this.constructor.prototype); return f } call(context, ...args) { throw "Not implemented" } toString() { return this.constructor.name + "(...)"; } }; class HelloWorld extends ExtendableFunction { call() { console.log("Hello, world!", this, arguments) } }; Но мое чувство прекрасного все еще не радо. Вспомните, как часто вы делали обертки над функциями, которые вызывают вложенную функцию через apply. Или как часто вы bindили функцию к объекту... Теперь во всех этих цепочках будет на 1 обертку больше - ведь эти методы будут применяться сначала к f - а потом уже к call. Наконец, если мы уже пишем функтор-обертку, то, вероятно, нам будет проще определять для него не метод call - а метод apply. Поэтому в финальной версии я "спрямил" метод bind, а методы call и apply сделал по умолчанию вызывающими друг друга - чтобы наследник мог переопределить любого. class ExtendableFunction extends Function { constructor() { super() function f(...args) { return f.call(this, ...args); } Object.setPrototypeOf(f, this.constructor.prototype); return f } call(context, ...args) { return this.apply(context, args) } apply(context, args) { return this.call(context, ...args) } bind(context, ...args) { return this.call.bind(this, context, ...args) } toString() { return this.constructor.name + "(...)"; } }; class HelloWorld extends ExtendableFunction { call(context, ..args) { console.log("Hello, world!", this, context, args) } }; class HelloWorld2 extends ExtendableFunction { apply(context, args) { console.log("Hello, world!", this, context, args) } };

среда, 22 января 2020 г.

“Множественное наследование” c#

#c_sharp #наследование


Есть иерархия классов, появилась ситуация в которой не могу придумать нормального
решения. (стрелками показано наследование, красным цветом - желаемое, но невозможное )

  

Классы U, S, A, W используют одну и ту же реализацию функции F

А классы SAI, AAI, WAI другую, но общую между собой. Именно это и не получается реализовать
так как нет возможности создать дополнительный класс AI реализовать в нем F  и тогда
наследоваться от него. Получается что придется просто копировать реализацию  F для
SAI, AAI, WAI, что есть плохо. 

Какой есть способ красиво решить эту проблему??

Спасибо!
    


Ответы

Ответ 1



Множественное наследование от нескольких классов невозможно. Используйте интерфейс IFunctionF и два вспомогательных класса F1 и F2, каждый из которых имеет свою реализацию IFunctionF. И далее от F1 и F2 наследуйте ваши семейства U,A,S,W от F1 и SAI, AAI, WAI от F2. public interface F { int F(); } class F1:F { public virtual int F() { return 2; } } class F2 : F { public virtual int F() { return 3; } } class S : F1 { void a() { F(); } } class AAI : F2 { void a() { F(); } }

Ответ 2



C# Extension methods (методы расширения) возможно помогут вам с переиспользованием кода (но, не факт, т.к. вы не сможете использовать приватные члены классов). Пример: Interface IFoo {} public static class IFooExtensions { public static int F(this IFoo foo) { //.... } } //........... int value = (myObj as IFoo).F();

Нужно ли писать пустой виртуальный деструктор?

#cpp #наследование #language_lawyer #неопределенное_поведение


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

Но что если лично мне вообще не нужен деструктор ни в одном из классов?
Являются ли автоматически сгенерированные деструкторы взаимозаменяемыми?
Или я всё равно обязан добавить виртуальный деструктор в родительский класс?

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

В таком случае кажется странным необходимость добавлять виртуальную функцию, которая
будет вызываться, если эта функция заведомо является nopом.

https://ideone.com/8ldKsU

struct A {
  int x;
  A(unsigned x) : x(x) {}
};

struct B : A
{
  B() : A(7) {}
};

int main()
{
  A *a = new B();
  delete a;

  return 0;
}

    


Ответы

Ответ 1



Ответ на этот вопрос по сути совпадает с этим ответом. Виртуальным деструктор может стать только при наследовании или явном указании, больше никак. Компилятор генерирует деструктор, который должен удалить все объекты, т.е. «пустой» это не верно в общем случае. В стандарте нет исключений для этого случая, код в вопросе даёт неопределённое поведение согласно стандарту. Дополнительно, пустой с точки зрения исходного кода деструктор на деле не является пустым, в него вкладывается логика по деаллокации экземпляра класса. Поэтому деструкторы разных классов по сути являются разными, не взаимозаменяемыми.

Ответ 2



Как известно, при необходимости удаления через указатель на родительский класс, класс должен иметь виртуальный деструктор, чтобы оператор delete вызвал верный деструктор дочернего класса. Если дело дошло до виртуальных функций и до удаления объекта по указателю на базовый класс, то рекомендуется писать виртуальные деструкторы, хотя бы и пустые. Потом меньше возни будет их добавлять. Опять же любая программа имеет тенденцию стать библиотекой и в таком качестве следующие пользователи начинают производить свои классы от Ваших классов. Тут и пригодится виртуальный деструктор. Что касается приведенного примера, то в нем нет ни виртуальных функций, ни НЕОБХОДИМОСТИ удалять объект класса по по указателю на базовый класс. В качестве иллюстрации того, что в С++ МОЖНО обойтись и без виртуальных деструкторов такой пример имеет право на существование. Но в реальном коде с более-менее развитой системой иерархии классов виртуальные деструкторы must have.

Вопрос по сериализации объектов в java

#java #исключения #наследование #сериализация #десериализация


Есть классы, расположенные в определённой иерархии наследования:

public class Program {
    public Program(){
        System.out.println("Текст");
    }
}

public class Wild extends Program{
    public Wild(){
        System.out.println("НеТекст");
    }
}

public class BasicProgramists extends Wild{
    public BasicProgramists(){
        System.out.println("Бесполезная программа");
    }
}

import java.io.*;

public class BasicUser extends BasicProgramists implements Serializable {
    public BasicUser(){
        System.out.println("Использование бесполезной программы");
    }

    public static void main(String[] args) {
        BasicUser user = new BasicUser();
        System.out.println();
        try {
            ObjectOutputStream or = new ObjectOutputStream(new FileOutputStream("Progg.ser"));
            or.writeObject(user);
            or.close();
        }
        catch (IOException ex){
            ex.printStackTrace();
        }

        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("Progg.ser"));
            BasicUser user1 = (BasicUser) is.readObject();
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
    }
}


После компиляции получаем вот такой результат:


До отступа - результат работы конструктора BasicUser

После отступа - результат работы десериализации

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


Ответы

Ответ 1



Сериализация в java не использует конструкторы для создания объектов. Поэтому контруктор не был вызван. Но почему вызвались кострукторы родительских классов? Очевидно, т.к. они не реализовали Serializable, то механизм сериализации о них ничего не знал и поэтому инициализация происходила обычным способом - через конструктор. В сказаном легко убедиться реализовав Serializable классом Program.

Ответ 2



В дополнение к предыдущему ответу Если нужно, чтобы все конструкторы отрабатывали Немного допилил ваш код import java.io.*; class Program { Program() { System.out.println("Текст"); } } class Wild extends Program { Wild() { System.out.println("НеТекст"); } } class BasicProgrammers extends Wild { BasicProgrammers() { System.out.println("Бесполезная программа"); } } class BasicUser extends BasicProgrammers { BasicUser() { System.out.println("Использование бесполезной программы"); } } class SuperUser extends BasicUser implements Serializable { public static void main(String[] args) { SuperUser superUser = new SuperUser(); System.out.println(); try { ObjectOutputStream or = new ObjectOutputStream(new FileOutputStream("Progg.ser")); or.writeObject(superUser); or.close(); } catch (IOException ex) { ex.printStackTrace(); } try { ObjectInputStream is = new ObjectInputStream(new FileInputStream("Progg.ser")); SuperUser superUser1 = (SuperUser) is.readObject(); } catch (Exception ex) { ex.printStackTrace(); } } } Вывод: // Текст // НеТекст // Бесполезная программа // Использование бесполезной программы // // Текст // НеТекст // Бесполезная программа // Использование бесполезной программы // // Process finished with exit code 0

Логика xml-gui-атрибутов

#xml #наследование #android #gui


Казалось бы, в написании GUI с помощью XML нет ничего сложного. Ищем в Reference
классы, наследуемые от View, записываем их в иерархическом порядке, и инициализируем
нужные свойства, которые опять же прописаны в документации каждого класса.
Но, например, свойство android:layout_weight не является параметром TextView, однако
добавляется туда, если его предком является TextView.
  
  

Мой вопрос: по какому принципу происходит подобное наследование, как отличать наследуемые
параметры от ненаследуемых, и существуют ли ещё какие-нибудь неочевидные моменты в
 написании GUI с помощью XML?    


Ответы

Ответ 1



При добавлении вьюшки куда-нибудь из кода, вы используете специфичные для лэйаута LayoutParams: // грубо relativeLayout.addView(myView, new RelativeLayout.LayoutParams(...)); Т.е. кроме параметров самого view вам нужно использовать еще параметры лэйаута контейнера, в который вы его добавляете. (в случае одноаргументного addView(View view) используются дефолтные параметры) Парсер xml-ек при генерации байт-кода сделает то же самое: если вьюшка находится в RelativeLayout, в ее тэге могут быть аттрибуты для RelativeLayout.LayoutParams. Если вы их там указали - он создаст LayoutParams с вашими атрибутами, если нет - будет использовать дефолтные. Итак: Наследования никакого нет, просто логика описания лэйаутов в xml и в коде немного отличается. Все параметры LayoutParams любого ViewGroup (LinearLayout, RelativeLayout) в xml могут добавлятья в тэг наследника. Нет железного способа отличить параметры child от параметров лэйаута parent "на ходу". Но и надобности тоже нет. Параметры задокументированы (LinearLayout.LayoutParams) Выбирая лэйаут, вы решаете какую-то задачу, а значит, вы знаете, какие параметры лэйаута вы хотите использовать, вряд ли вы их спутаете с параметрами вьюшки. Через пару месяцев работы с лэйаутами вы будете знать их наизусть :)

Ответ 2



Ответ: компоненты, такие как TexView имеют свои атрибуты + атрибуты, наследуемые от лэйаутов(менеджеров компоновки) таких как LinearLayout в которых они размещены(являются дочерними по отношению к ним)