C #의 다중 상속
다중 상속이 나쁘기 때문에 (소스가 더 복잡 해짐) C #은 이러한 패턴을 직접 제공하지 않습니다. 그러나 때때로이 능력을 갖는 것이 도움이 될 것입니다.
예를 들어 인터페이스와 세 가지 클래스를 사용하여 누락 된 다중 상속 패턴을 구현할 수 있습니다.
public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }
public class First:IFirst
{
public void FirstMethod() { Console.WriteLine("First"); }
}
public class Second:ISecond
{
public void SecondMethod() { Console.WriteLine("Second"); }
}
public class FirstAndSecond: IFirst, ISecond
{
First first = new First();
Second second = new Second();
public void FirstMethod() { first.FirstMethod(); }
public void SecondMethod() { second.SecondMethod(); }
}
인터페이스 중 하나에 메소드를 추가 할 때마다 FirstAndSecond 클래스도 변경해야합니다 .
C ++에서 가능한 것처럼 여러 개의 기존 클래스를 하나의 새 클래스에 삽입하는 방법이 있습니까?
어떤 종류의 코드 생성을 사용하는 솔루션이 있습니까?
또는 다음과 같이 보일 수 있습니다 (상상적인 C # 구문).
public class FirstAndSecond: IFirst from First, ISecond from Second
{ }
따라서 인터페이스 중 하나를 수정할 때 FirstAndSecond 클래스를 업데이트 할 필요가 없습니다.
편집하다
실제적인 예를 고려하는 것이 좋습니다.
프로젝트 내부의 다른 위치에서 이미 사용중인 기존 클래스 (예 : ITextTcpClient 기반의 텍스트 기반 TCP 클라이언트)가 있습니다. 이제 Windows 양식 개발자가 쉽게 액세스 할 수 있도록 클래스 구성 요소를 작성해야합니다.
내가 아는 한 현재 두 가지 방법이 있습니다.
컴포넌트에서 상속 된 새 클래스를 작성하고 FirstAndSecond에 표시된대로 클래스 자체의 인스턴스를 사용하여 TextTcpClient 클래스의 인터페이스를 구현하십시오.
TextTcpClient에서 상속하고 어떻게 든 IComponent를 구현하는 새 클래스를 작성하십시오 (실제로는 아직 시도하지 않았습니다).
두 경우 모두 클래스별로가 아니라 메서드별로 작업해야합니다. TextTcpClient 및 Component의 모든 메소드가 필요하다는 것을 알고 있으므로이 두 가지를 하나의 클래스로 결합하는 것이 가장 쉬운 솔루션입니다.
충돌을 피하기 위해 나중에 코드를 생성하여 결과를 변경할 수 있지만 직접 입력하면 엉덩이에 통증이 생깁니다.
다중 상속이 나쁘기 때문에 (소스가 더 복잡 해짐) C #은 이러한 패턴을 직접 제공하지 않습니다. 그러나 때때로이 능력을 갖는 것이 도움이 될 것입니다.
C #과 .net CLR은 C #, VB.net과 다른 언어 간의 상호 운용 방법을 아직 확정하지 않았기 때문에 MI를 구현하지 않았습니다.
MI는 유용한 개념이며, 답변되지 않은 질문은 다음과 같습니다. ""다른 수퍼 클래스에 여러 개의 공통 기본 클래스가있는 경우 어떻게합니까?
Perl은 MI가 작동하고 잘 작동하는 유일한 언어입니다. .Net은 언젠가 그것을 소개하지만 아직은 아니지만 CLR은 이미 MI를 지원하지만 내가 말했듯이 아직 그 이상의 언어 구성은 없습니다.
그때까지 프록시 객체와 여러 인터페이스가 붙어 있습니다.
단지 사용을 고려 구성을 대신 다중 상속을 시뮬레이션하려고합니다. 당신은 성분, 예를 구성하는 어떤 클래스를 정의하는 인터페이스를 사용할 수 있습니다 ISteerable
유형의 속성이 의미를 SteeringWheel
, IBrakable
유형의 속성 의미 BrakePedal
등
이 작업을 마치면 C # 3.0에 추가 된 확장 메서드 기능을 사용하여 다음 과 같은 암시 적 속성에 대한 호출 메서드를 더욱 단순화 할 수 있습니다 .
public interface ISteerable { SteeringWheel wheel { get; set; } }
public interface IBrakable { BrakePedal brake { get; set; } }
public class Vehicle : ISteerable, IBrakable
{
public SteeringWheel wheel { get; set; }
public BrakePedal brake { get; set; }
public Vehicle() { wheel = new SteeringWheel(); brake = new BrakePedal(); }
}
public static class SteeringExtensions
{
public static void SteerLeft(this ISteerable vehicle)
{
vehicle.wheel.SteerLeft();
}
}
public static class BrakeExtensions
{
public static void Stop(this IBrakable vehicle)
{
vehicle.brake.ApplyUntilStop();
}
}
public class Main
{
Vehicle myCar = new Vehicle();
public void main()
{
myCar.SteerLeft();
myCar.Stop();
}
}
이런 종류의 일을 가능하게 하는 C # 포스트 컴파일러 를 만들었습니다 .
using NRoles;
public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }
public class RFirst : IFirst, Role {
public void FirstMethod() { Console.WriteLine("First"); }
}
public class RSecond : ISecond, Role {
public void SecondMethod() { Console.WriteLine("Second"); }
}
public class FirstAndSecond : Does<RFirst>, Does<RSecond> { }
포스트 컴파일러를 Visual Studio 빌드 후 이벤트로 실행할 수 있습니다.
C : \ some_path \ nroles-v0.1.0-bin \ nutate.exe "$ (TargetPath)"
동일한 어셈블리에서 다음과 같이 사용합니다.
var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();
다른 어셈블리에서는 다음과 같이 사용합니다.
var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();
IFirst와 ISecond를 모두 구현 한 다음 해당 기본 클래스에서 상속하는 하나의 추상 기본 클래스를 가질 수 있습니다.
MI는 나쁘지 않습니다. (심지어) 그것을 사용했던 모든 사람들은 그것을 좋아하고 코드를 복잡하게하지 않습니다! 최소한 다른 구조보다 더 이상 코드를 복잡하게 만들 수 없습니다. 잘못된 코드는 MI가 사진에 있는지 여부에 관계없이 잘못된 코드입니다.
어쨌든 공유하고 싶은 다중 상속에 대한 멋진 작은 솔루션을 얻었습니다. http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog 또는 내 시그의 링크를 따라갈 수 있습니다 ... :)
IFirst 및 ISecond의 방법이 IFirst 및 ISecond의 계약과 만 상호 작용해야한다는 제한 사항을 적용 할 수있는 경우 (예와 같이) 확장 방법으로 요청한 작업을 수행 할 수 있습니다. 실제로는 거의 그렇지 않습니다.
public interface IFirst {}
public interface ISecond {}
public class FirstAndSecond : IFirst, ISecond
{
}
public static MultipleInheritenceExtensions
{
public static void First(this IFirst theFirst)
{
Console.WriteLine("First");
}
public static void Second(this ISecond theSecond)
{
Console.WriteLine("Second");
}
}
///
public void Test()
{
FirstAndSecond fas = new FirstAndSecond();
fas.First();
fas.Second();
}
따라서 기본 아이디어는 인터페이스에서 필요한 구현을 정의한다는 것입니다.이 필수 항목은 확장 메소드의 유연한 구현을 지원해야합니다. 확장 인터페이스를 추가하는 대신 "인터페이스에 메서드를 추가"해야합니다.
클래스에 메소드를 추가 할 때마다 인터페이스에 서명을 추가해야하므로 인터페이스를 사용하는 것은 번거 롭습니다. 또한 이미 많은 메소드가 있지만 인터페이스가없는 클래스가 있다면 어떨까요? 상속하려는 모든 클래스에 대한 인터페이스를 수동으로 만들어야합니다. 그리고 자식 클래스가 여러 인터페이스에서 상속받는 경우 자식 클래스의 인터페이스에서 모든 메소드를 구현해야합니다.
외관 디자인 패턴을 다음으로 우리가 사용하는 여러 클래스에서 상속 시뮬레이션 할 수 있습니다 접근을 . 상속해야하는 클래스 내에서 {get; set;}을 사용하여 클래스를 속성으로 선언하고 모든 공용 속성과 메서드는 해당 클래스에서 가져온 것이며 자식 클래스의 생성자에서 부모 클래스를 인스턴스화합니다.
예를 들면 다음과 같습니다.
namespace OOP
{
class Program
{
static void Main(string[] args)
{
Child somechild = new Child();
somechild.DoHomeWork();
somechild.CheckingAround();
Console.ReadLine();
}
}
public class Father
{
public Father() { }
public void Work()
{
Console.WriteLine("working...");
}
public void Moonlight()
{
Console.WriteLine("moonlighting...");
}
}
public class Mother
{
public Mother() { }
public void Cook()
{
Console.WriteLine("cooking...");
}
public void Clean()
{
Console.WriteLine("cleaning...");
}
}
public class Child
{
public Father MyFather { get; set; }
public Mother MyMother { get; set; }
public Child()
{
MyFather = new Father();
MyMother = new Mother();
}
public void GoToSchool()
{
Console.WriteLine("go to school...");
}
public void DoHomeWork()
{
Console.WriteLine("doing homework...");
}
public void CheckingAround()
{
MyFather.Work();
MyMother.Cook();
}
}
}
이 구조 클래스를 사용하면 Child 클래스는 Parent Father와 Mother 클래스의 모든 메소드와 속성에 액세스하여 다중 상속을 시뮬레이션하고 상위 클래스의 인스턴스를 상속합니다. 동일하지는 않지만 실용적입니다.
필자의 구현에서는 MI에 클래스 / 인터페이스를 사용하지만 "양호한 형식"이지만 몇 가지 필요한 함수 호출에 대해서만 다중 상속을 설정해야하기 때문에 복잡한 오버 헤드가 발생하는 경향이 있음을 발견했습니다. 문자 그대로 수십 번 중복되어야했습니다.
대신 OOP 대체의 일종으로 다른 모듈 종류에서 정적 "기능을 호출하는 기능을 호출하는 기능"을 만드는 것이 더 쉬웠습니다. 내가 작업했던 솔루션은 RPG의 "주문 시스템"으로, 코드를 다시 쓰지 않고도 매우 다양한 철자를 사용하기 위해 함수 호출을 엄청나게 혼합하고 일치 시켜야 하는 경우가있었습니다.
스펠 로직을위한 인스턴스가 반드시 필요하지 않기 때문에 대부분의 함수는 정적 일 수 있지만, 클래스 상속은 정적 인 동안 가상 또는 추상 키워드를 사용할 수도 없습니다. 인터페이스는 전혀 사용할 수 없습니다.
코딩은이 방법으로 IMO보다 더 빠르고 깨끗해 보입니다. 함수를 수행하고 상속 된 속성이 필요하지 않은 경우 함수를 사용하십시오.
다중 상속은 일반적으로 해결하는 것보다 더 많은 문제를 일으키는 것들 중 하나입니다. C ++에서는 충분히 매달릴 수있는 로프를 제공하는 패턴에 적합하지만 Java와 C #은 옵션을 제공하지 않는 안전한 경로를 선택했습니다. 가장 큰 문제는 상속자가 구현하지 않은 동일한 서명을 가진 메소드가있는 여러 클래스를 상속하는 경우 수행 할 작업입니다. 어떤 클래스의 방법을 선택해야합니까? 아니면 컴파일하지 않아야합니까? 일반적으로 다중 상속에 의존하지 않는 대부분의 것을 구현하는 다른 방법이 있습니다.
X가 Y에서 상속 받으면 다소 직교하는 두 가지 효과가 있습니다.
- Y는 X에 기본 기능을 제공하므로 X 코드에는 Y와 다른 항목 만 포함하면됩니다.
- Y가 예상되는 거의 모든 곳에서 X가 대신 사용될 수 있습니다.
상속은 두 가지 기능을 모두 제공하지만 둘 중 어느 것도 다른 용도없이 사용할 수있는 상황을 상상하기 어렵지 않습니다. 내가 아는 .net 언어는 직접 사용하지 않는 기본 클래스를 정의하고 아무것도 추가하지 않고 직접 상속하는 하나 이상의 클래스를 사용하여 이러한 기능을 얻을 수 있지만 직접 사용하는 방법은 없습니다. new (이러한 클래스는 모든 코드를 공유 할 수 있지만 서로 대체 할 수는 없습니다). 그러나 모든 CLR 호환 언어를 사용하면 첫 번째 (멤버 재사용)없이 인터페이스의 두 번째 기능 (대체 가능성)을 제공하는 인터페이스를 사용할 수 있습니다.
나는 그것이 허용되지 않더라도 알고 있지만 언젠가는 실제로 그 사람들을 위해 그것을 필요로합니다.
class a {}
class b : a {}
class c : b {}
내 경우처럼이 클래스 b : Form (yep windows.forms) class c : b {}
함수의 절반이 동일하고 인터페이스 u를 사용하여 모두 다시 작성해야합니다.
다중 상속 (MI)에 대한 질문이 수시로 나타나기 때문에 컴포지션 패턴의 일부 문제를 해결하는 방법을 추가하고 싶습니다.
나는에 구축 IFirst
, ISecond
, First
, Second
, FirstAndSecond
이 문제에서 제시 한 바와 같이, 방법. IFirst
인터페이스 / MI 기본 클래스 수에 관계없이 패턴이 동일하게 유지되므로 샘플 코드를로 줄 입니다.
즉, MI로, 가정 수 있습니다 First
와 Second
같은 기본 클래스에서 모두 파생는 것 BaseClass
, 만 공용 인터페이스 요소를 사용하여BaseClass
및에 구현 BaseClass
에 컨테이너 참조를 추가하여 표현할 수 있습니다 .First
Second
class First : IFirst {
private BaseClass ContainerInstance;
First(BaseClass container) { ContainerInstance = container; }
public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); }
}
...
것들로부터 보호 인터페이스 요소가 때 더 복잡하게 BaseClass
참조하거나 할 때 First
와 Second
약간의 추상적 인 부분을 구현하기 위해 자신의 서브 클래스를 요구, MI에서 추상 클래스가 될 것입니다.
class BaseClass {
protected void DoStuff();
}
abstract class First : IFirst {
public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
protected abstract void DoStuff(); // base class reference in MI
protected abstract void DoSubClassStuff(); // sub class responsibility
}
C #을 사용하면 중첩 클래스가 포함 클래스의 보호 / 개인 요소에 액세스 할 수 있으므로 First
구현 에서 추상 비트를 연결하는 데 사용할 수 있습니다 .
class FirstAndSecond : BaseClass, IFirst, ISecond {
// link interface
private class PartFirst : First {
private FirstAndSecond ContainerInstance;
public PartFirst(FirstAndSecond container) {
ContainerInstance = container;
}
// forwarded references to emulate access as it would be with MI
protected override void DoStuff() { ContainerInstance.DoStuff(); }
protected override void DoSubClassStuff() { ContainerInstance.DoSubClassStuff(); }
}
private IFirst partFirstInstance; // composition object
public FirstMethod() { partFirstInstance.FirstMethod(); } // forwarded implementation
public FirstAndSecond() {
partFirstInstance = new PartFirst(this); // composition in constructor
}
// same stuff for Second
//...
// implementation of DoSubClassStuff
private void DoSubClassStuff() { Console.WriteLine("Private method accessed"); }
}
약간의 상용구가 있지만, FirstMethod 및 SecondMethod의 실제 구현이 충분히 복잡하고 액세스 된 개인 / 보호 된 메소드의 양이 적당하면이 패턴이 다중 상속 부족을 극복하는 데 도움이 될 수 있습니다.
이것은 Lawrence Wenham의 답변을 따르지만 사용 사례에 따라 개선되었거나 아닐 수도 있습니다. 세터가 필요하지 않습니다.
public interface IPerson {
int GetAge();
string GetName();
}
public interface IGetPerson {
IPerson GetPerson();
}
public static class IGetPersonAdditions {
public static int GetAgeViaPerson(this IGetPerson getPerson) { // I prefer to have the "ViaPerson" in the name in case the object has another Age property.
IPerson person = getPerson.GetPersion();
return person.GetAge();
}
public static string GetNameViaPerson(this IGetPerson getPerson) {
return getPerson.GetPerson().GetName();
}
}
public class Person: IPerson, IGetPerson {
private int Age {get;set;}
private string Name {get;set;}
public IPerson GetPerson() {
return this;
}
public int GetAge() { return Age; }
public string GetName() { return Name; }
}
Now any object that knows how to get a person can implement IGetPerson, and it will automatically have the GetAgeViaPerson() and GetNameViaPerson() methods. From this point, basically all Person code goes into IGetPerson, not into IPerson, other than new ivars, which have to go into both. And in using such code, you don't have to be concerned about whether or not your IGetPerson object is itself actually an IPerson.
We all seem to be heading down the interface path with this, but the obvious other possibility, here, is to do what OOP is supposed to do, and build up your inheritance tree... (isn't this what class design is all about?)
class Program
{
static void Main(string[] args)
{
human me = new human();
me.legs = 2;
me.lfType = "Human";
me.name = "Paul";
Console.WriteLine(me.name);
}
}
public abstract class lifeform
{
public string lfType { get; set; }
}
public abstract class mammal : lifeform
{
public int legs { get; set; }
}
public class human : mammal
{
public string name { get; set; }
}
This structure provides reusable blocks of code and, surely, is how OOP code should be written?
If this particular approach doesn't quite fit the bill the we simply create new classes based on the required objects...
class Program
{
static void Main(string[] args)
{
fish shark = new fish();
shark.size = "large";
shark.lfType = "Fish";
shark.name = "Jaws";
Console.WriteLine(shark.name);
human me = new human();
me.legs = 2;
me.lfType = "Human";
me.name = "Paul";
Console.WriteLine(me.name);
}
}
public abstract class lifeform
{
public string lfType { get; set; }
}
public abstract class mammal : lifeform
{
public int legs { get; set; }
}
public class human : mammal
{
public string name { get; set; }
}
public class aquatic : lifeform
{
public string size { get; set; }
}
public class fish : aquatic
{
public string name { get; set; }
}
With C# 8 now you practically have multiple inheritance via default implementation of interface members:
interface ILogger
{
void Log(LogLevel level, string message);
void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}
class ConsoleLogger : ILogger
{
public void Log(LogLevel level, string message) { ... }
// Log(Exception) gets default implementation
}
참고URL : https://stackoverflow.com/questions/178333/multiple-inheritance-in-c-sharp
'IT story' 카테고리의 다른 글
숨김을 반대로 적용하는 방법? (0) | 2020.05.06 |
---|---|
배열과 객체의 후행 쉼표가 사양의 일부입니까? (0) | 2020.05.06 |
리스트의 사전을 만드는 파이썬 (0) | 2020.05.06 |
Python Pandas : 특정 값과 일치하는 열의 색인을 가져옵니다. (0) | 2020.05.06 |
크론과 virtualenv (0) | 2020.05.06 |