Множественное наследование, полиморфизм и новые способы программирования
-
07-07-2019 - |
Вопрос
Раз и навсегда я хочу прояснить эту несколько субъективную и спорную область программирования.
Множественное наследование
В моей нынешней рабочей среде у меня есть разработчики на C ++ и C #, которые происходят из совершенно разных миров и, следовательно, имеют разные мнения о макете программирования.
Будучи самим разработчиком на C # и Java, я никогда не приходил в такое состояние, когда мне действительно нужно было использовать множественное наследование, но разработчики C ++ вокруг меня склонны выдавать комментарии типа " Это был бы идеальный способ использовать множественное наследование. наследование & Quot; р>
Конечно, я склонен не соглашаться.
Мой вопрос
В каком сценарии множественное наследование будет лучшим или более простым способом решения проблемы, чем использование интерфейсов и простое наследование?
И всегда ли вы можете решить преимущества множественного наследования, используя вместо этого переменные-члены?
Решение
За исключением того, что вы, похоже, предлагаете использовать множественное наследование.
Наследование используется по-разному. Открытое наследование определяет отношение IS-A, а наследование с любыми модификаторами доступа может обеспечить поведение дочернего класса, которое затем может быть переопределено. В C # и Java (с которыми я более знаком) интерфейсы предоставляют интерфейс IS-A без поведения. (В C ++ вы можете создать интерфейс, определив класс без членов данных и все функции чисто виртуальные.)
Итак, если вы предоставляете интерфейс и помещаете переменную-член для обеспечения поведения, вы делаете полное наследование в стиле C ++. Вы просто разбиваете его на компоненты и делаете больше хлопот, чтобы это сделать. Описывать это как решение проблемы множественного наследования является неискренним, поскольку выполнение этого с двумя интерфейсами и двумя переменными-членами является множественным наследованием. Это становится еще более неловким, если вы собираетесь изменить поведение полиморфным способом, поскольку вам нужны параллельные иерархии наследования или более сложная диспетчеризация переменной-члена.
Есть проблемы с множественным наследованием в C ++, правда, но есть и такие применения, которые не представляют проблем. Первое хорошее применение, которое я увидел, было «mix-in»: маленькие классы, чтобы добавить определенное поведение. В Java есть множество интерфейсов для них, но вы, как ожидается, сами справитесь с поведением.
Если вы хотите узнать больше о множественном наследовании, я бы предложил попробовать язык, такой как Common Lisp, где множественное наследование обычно используется, и с ним никто не сталкивается.
Другие советы
Многократное наследование - это хорошая возможность. Те, кто писал код с помощью MI, естественно, атакуют проблему под другим углом. Однако то, что «проще» для одного программиста это может быть «сложнее»; для другого.
Информацию о MI для C ++ см. В статье Херба Саттера :
Объединение модулей / библиотек
Многие классы разработаны, чтобы быть базовыми классы; то есть использовать их вы намеревался унаследовать от них. естественное следствие: что если вы хотите написать класс, который расширяет два библиотеки, и вы обязаны наследовать от класса в каждом? Так как у вас обычно нет возможности изменение кода библиотеки (если вы купил библиотеку у сторонний поставщик, или это модуль производится другой командой проекта внутри вашей компании), MI необходимо.
Простота (полиморфного) использования
Есть примеры, когда разрешается МИ значительно упрощает использование того же объект полиморфно в разных пути. Один хороший пример можно найти в C ++ PL3 14.2.2, который демонстрирует MI-дизайн для классов исключений, где наиболее производный класс исключений может иметь полиморфный IS-A отношения с множеством прямой базы классы.
Ознакомьтесь с этой темой: Должен ли C # включать множественное наследование? р>
При интенсивном использовании двух языков, где один имеет (Python) множественное наследование, а другой - нет (C #), я могу честно сказать, что я никогда не использовал или имел потребность в MI.
В большинстве случаев я предпочитаю интерфейсы + композицию как обычному, так и / или множественному наследованию. Конечно, в некоторых случаях вам действительно нужно наследование, особенно в C #, но MI? Никогда.
Изменить . Было бы проще ответить, если бы вы показали пример, когда ваши программисты на C ++ защищали MI, а вы - нет.
Правка . Подумав немного об этом, я понял, что разница между статически типизированным языком, таким как C # или C ++, и языком с утиной типизацией, таким как Python, может быть хорошей причиной, которую мне никогда не приходилось использовать MI в Python, просто из-за его динамического характера. Но, тем не менее, я никогда не нуждался в этом в C #.
Я не часто использовал множественное наследование, но иногда это бывает удобно, потому что оно просто хорошо работает и снижает затраты на обслуживание. C ++ FAQ Lite имеют несколько хороших сценариев.
#include <afx.h>
#include <afxtempl.h>
#include "StdAfx.h"
class Employee
{
char name[30];
public:
Employee() {}
Employee( const char* nm )
{
strcpy( name, nm ) ;
}
char* getName() const;
virtual double computePay() const = 0;
virtual ~Employee()
{
}
};
class WageEmployee : public virtual Employee
{
double wage;
double hours;
public:
WageEmployee( const char* nm );
void setWage( double wg ){wage = wg; }
void setHours( double hrs ){hours = hrs;}
double computePay() const /* Implicitly virtual */
{
return wage * hours;
}
};
class SalesPerson : public WageEmployee
{
double commission;
double salesMade;
public:
SalesPerson( const char* nm );
void setCommission( double comm )
{
commission = comm;
}
void setSales( double sales )
{
salesMade = sales;
}
double computePay() const /* Implicitly virtual */
{
return WageEmployee::computePay() + commission * salesMade;
}
};
class Manager : public virtual Employee
{
double weeklySalary;
public:
Manager( const char* nm );
void setSalary( double salary ){weeklySalary = salary; }
double computePay() const /* Implicitly virtual */
{
return weeklySalary;
}
};
class SalesManager : public SalesPerson, public Manager
{
public:
SalesManager::SalesManager( const char* nm )
:Employee(nm),Manager(nm),SalesPerson(nm)
{
}
double computePay() const /* Implicitly virtual */
{
return Manager::computePay() + SalesPerson::computePay();
}
};
typedef CTypedPtrList < CPtrList, Employee* > CEmployeeList;
class EmployeeList
{
CEmployeeList List;
public:
EmployeeList() {}
CEmployeeList& GetElements() { return List; }
void Add( Employee* newEmp )
{
List.AddTail( newEmp ) ;
}
virtual ~EmployeeList()
{
POSITION pos = List.GetHeadPosition() ;
while ( pos != NULL )
{
delete List.GetNext(pos) ;
}
List.RemoveAll() ;
}
};
WageEmployee::WageEmployee( const char* nm )
:Employee( nm )
{
wage = 0.0;
hours = 0.0;
}
SalesPerson::SalesPerson( const char* nm )
:WageEmployee( nm )
{
commission = 0.0;
salesMade = 0.0;
}
Manager::Manager( const char* nm )
:Employee( nm )
{
weeklySalary = 0.0;
}
void main( int argc, char *argv[] )
{
int ans = 0 ;
EmployeeList myDept;
WageEmployee* wagePtr;
SalesPerson* salePtr;
Manager* mgrPtr;
SalesManager* smPtr;
wagePtr = new WageEmployee("Alan Wage");
salePtr = new SalesPerson("Brian Sale");
mgrPtr = new Manager("Clive Manager");
smPtr = new SalesManager("David SaleManager");
wagePtr->setWage( 10.0 );
wagePtr->setHours( 35.0 );
salePtr->setWage( 5.0 );
salePtr->setHours( 35.0 );
salePtr->setCommission( 0.05 );
salePtr->setSales( 100.0 );
mgrPtr->setSalary( 600.0 ) ;
smPtr->setSalary( 670.0 ) ;
smPtr->setCommission( 0.01 );
smPtr->setSales( 100.0 );
myDept.Add( wagePtr );
myDept.Add( salePtr );
myDept.Add( mgrPtr );
myDept.Add( smPtr );
double payroll = 0.0 ;
Employee* person ;
POSITION pos = myDept.GetElements().GetHeadPosition() ;
while ( pos != NULL )
{
person = (Employee* )myDept.GetElements().GetNext(pos) ;
payroll += person->computePay();
}
ExitProcess( ans ) ;
}
Выше приведен Visual C ++ с множественным наследованием.
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
namespace ConsolePoly
{
interface Employee
{
string Name { get; set; }
double computePay();
}
class WageEmployee : Employee
{
private string iName = "";
virtual public string Name { get { return iName; } set { iName = value; } }
public double wage { get; set; }
public double hours { get; set; }
public WageEmployee(string nm)
{
Name = nm;
wage = 0.0;
hours = 0.0;
}
virtual public double computePay() { return wage * hours; } /* Implicitly virtual c++ */
}
class SalesPerson : WageEmployee
{
public double commission { get; set; }
public double salesMade { get; set; }
public SalesPerson(string nm)
: base(nm)
{
commission = 0.0;
salesMade = 0.0;
}
override public double computePay()
{
return base.computePay() + commission * salesMade;
} /* Implicitly virtual c++ */
}
class Manager : Employee
{
private string iName = "";
virtual public string Name { get { return iName; } set { iName = value; } }
public double weeklySalary { get; set; }
public Manager(string nm)
{
Name = nm;
weeklySalary = 0.0;
}
public Manager()
{
weeklySalary = 0.0;
}
public double computePay() { return weeklySalary; } /* Implicitly virtual c++ */
}
class SalesManager : Manager, /*SalesPerson,*/ Employee
{
public SalesManager(string nm)
{
Name = nm;
//SalesPerson(nm);
//commission = 0.01;
//salesMade = 100.0;
}
public double computePay() { return base.computePay();/*Manager.computePay()+SalesPerson.computePay();*/ }
}
class EmployeeList
{
private List<Employee> list = new List<Employee>();
public List<Employee> GetElements() { return list; }
public void Add(Employee newEmp) { list.Add(newEmp);}
public EmployeeList() {}
}
class poly
{
public poly()
{
}
public virtual void generate()
{
EmployeeList myDept = new EmployeeList();
WageEmployee wagePtr = new WageEmployee("Alan Wage");
SalesPerson salePtr = new SalesPerson("Brian Sale");
Manager mgrPtr = new Manager("Clive Manager");
SalesManager salemanPtr = new SalesManager("David SaleMan");
wagePtr.wage=10.0;
wagePtr.hours=35;
salePtr.wage= 5.0 ;
salePtr.hours= 35.0 ;
salePtr.commission= 0.05 ;
salePtr.salesMade= 100.0 ;
mgrPtr.weeklySalary=600.0;
salemanPtr.weeklySalary = 670;
myDept.Add( wagePtr );
myDept.Add( salePtr );
myDept.Add( mgrPtr );
myDept.Add( salemanPtr );
double payroll = 0.0 ;
List<Employee> list = myDept.GetElements();
foreach (Employee person in list)
{
Console.WriteLine("personName( \"{0}\" )\tcomputePay( \"{1}\" )", person.Name, person.computePay());
payroll += person.computePay();
}
Console.WriteLine("computePay( \"{0}\" )\n", payroll.ToString() );
}
static void Main(string[] args)
{
try
{
new poly().generate(); //new poly(args[0], args[1]).generate();
}
catch (IndexOutOfRangeException ioe)
{
System.Console.Error.WriteLine(ioe);
}
}
}
}
Но в C # SalesManager множественное наследование не поддерживается языком.
Лично я счастлив потерять множественное наследство.