การสืบทอดหลายรายการใน C #


212

เนื่องจากการสืบทอดหลายครั้งไม่ดี (ทำให้แหล่งซับซ้อนมากขึ้น) 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 เมื่อฉันแก้ไขหนึ่งในอินเตอร์เฟส


แก้ไข

บางทีอาจเป็นการดีกว่าที่จะพิจารณาตัวอย่างที่ใช้งานได้จริง:

คุณมีคลาสที่มีอยู่ (เช่นไคลเอนต์ TCP แบบข้อความที่อ้างอิงตาม ITextTcpClient) ซึ่งคุณใช้อยู่แล้วในที่ต่างๆในโครงการของคุณ ตอนนี้คุณรู้สึกว่าจำเป็นที่จะต้องสร้างองค์ประกอบของชั้นเรียนของคุณเพื่อให้สามารถเข้าถึงได้ง่ายสำหรับนักพัฒนาแบบฟอร์ม windows

เท่าที่ฉันรู้ว่าคุณมีสองวิธีในการทำเช่นนี้:

  1. เขียนคลาสใหม่ที่สืบทอดจากคอมโพเนนต์และใช้อินเทอร์เฟซของคลาส TextTcpClient โดยใช้อินสแตนซ์ของคลาสเองดังแสดงด้วย FirstAndSecond

  2. เขียนคลาสใหม่ที่สืบทอดมาจาก TextTcpClient และนำ IComponent มาใช้อย่างใด (ยังไม่ได้ลองเลย)

ในทั้งสองกรณีคุณจำเป็นต้องทำงานตามวิธีการและไม่ใช่ต่อชั้นเรียน เมื่อคุณรู้ว่าเราจะต้องใช้วิธีการทั้งหมดของ TextTcpClient และส่วนประกอบมันจะเป็นทางออกที่ง่ายที่สุดในการรวมสองสิ่งเหล่านี้เข้าเป็นหนึ่งคลาส

เพื่อหลีกเลี่ยงความขัดแย้งสิ่งนี้สามารถทำได้โดยการสร้างรหัสซึ่งผลลัพธ์สามารถเปลี่ยนแปลงได้ในภายหลัง แต่การพิมพ์ด้วยมือนั้นเป็นความเจ็บปวดอย่างแท้จริงในตูด


เท่าที่มันไม่ได้เป็นเพียงมรดกหลายอย่างในการปลอมตัวมันมีความซับซ้อนน้อยลงอย่างไร
harpo

เมื่อคิดถึงวิธีการขยายใหม่ใน 3.5 และวิธีการทำงาน (การสร้างการเรียกสมาชิกคงที่) สิ่งนี้อาจเป็นหนึ่งในวิวัฒนาการภาษา NET ต่อไป
Larry

บางครั้งฉันสงสัยว่าทำไมผู้คนไม่เพียงแค่ทำ ... คลาส A: คลาส B: คลาส C?
Chibueze Opata

@NazarMerza: ลิงก์มีการเปลี่ยนแปลง ตอนนี้: ปัญหาเกี่ยวกับการสืบทอดหลายรายการ
Craig McQueen

9
อย่าให้โฆษณาชวนเชื่อหลอกคุณ ตัวอย่างที่ดีของคุณแสดงให้เห็นว่าการสืบทอดหลาย ๆ อย่างมีประโยชน์และส่วนต่อประสานเป็นวิธีแก้ปัญหาสำหรับการขาดมัน
Kemal Erdogan

คำตอบ:


125

เนื่องจากการสืบทอดหลายครั้งไม่ดี (ทำให้แหล่งซับซ้อนมากขึ้น) C # ไม่ได้จัดรูปแบบดังกล่าวโดยตรง แต่บางครั้งมันจะมีประโยชน์ที่จะมีความสามารถนี้

C # และ. net CLR ยังไม่ได้ใช้ MI เพราะพวกเขายังไม่ได้ข้อสรุปว่าจะใช้งานระหว่าง C #, VB.net และภาษาอื่น ๆ ได้อย่างไรไม่ใช่เพราะ "มันจะทำให้ซอร์สซับซ้อนขึ้น"

MI เป็นแนวคิดที่มีประโยชน์คำถามที่ไม่ได้รับคำตอบมีดังนี้: - "คุณจะทำอย่างไรเมื่อคุณมีคลาสพื้นฐานหลายคลาสในซูเปอร์คลาสที่แตกต่างกัน?

Perl เป็นภาษาเดียวที่ฉันเคยทำงานกับที่ MI ทำงานและทำงานได้ดี .Net อาจจะแนะนำในวันหนึ่ง แต่ยังไม่ได้ CLR รองรับ MI แล้ว แต่อย่างที่ฉันได้กล่าวไปแล้ว

จนกว่าคุณจะติดกับวัตถุพร็อกซีและหลายอินเทอร์เฟซแทน :(


39
CLR ไม่รองรับการใช้งานหลายมรดกเพียงหลายมรดกสืบทอด (ซึ่งยังได้รับการสนับสนุนใน C #)
Jordão

4
@ Jordão: เพื่อความสมบูรณ์: เป็นไปได้ที่คอมไพเลอร์จะสร้าง MI สำหรับประเภทของพวกเขาใน CLR มันมี caveats มันไม่ได้เป็นไปตาม CLS สำหรับข้อมูลเพิ่มเติมดูที่นี่ (2004) บทความblogs.msdn.com/b/csharpfaq/archive/2004/03/07/…
dvdvorle

2
@MrHappy: บทความที่น่าสนใจมาก ฉันได้ทำการตรวจสอบลักษณะนิสัยของC # แล้วลองดู
Jordão

10
@ MandeepJanjua ฉันไม่ได้เรียกร้องอะไรเลยฉันพูดว่า 'อาจจะแนะนำได้' ความจริงยังคงอยู่ว่ามาตรฐาน CLMA ของ ECMA ให้บริการเครื่องจักร IL สำหรับการสืบทอดหลายอย่าง
IanNorton

4
FYI หลายการสืบทอดไม่เลวและอย่าสร้างรหัสที่ซับซ้อน แค่คิดว่าฉันจะพูดถึงมัน
Dmitri Nesteruk

214

ลองใช้การจัดองค์ประกอบภาพแทนการลองจำลองการสืบทอดหลายแบบ คุณสามารถใช้อินเทอร์เฟซเพื่อกำหนดว่าคลาสใดที่ประกอบขึ้นเป็นองค์ประกอบเช่น: 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();
    }
}

13
นั่นคือประเด็น - ความคิดเช่นนี้จะทำให้การจัดองค์ประกอบง่ายขึ้น
Jon Skeet

9
ใช่ แต่มีกรณีการใช้งานที่คุณต้องการวิธีการเป็นส่วนหนึ่งของวัตถุหลัก
David Pierre

9
น่าเสียดายที่ข้อมูลตัวแปรสมาชิกไม่สามารถเข้าถึงได้ในวิธีการขยายดังนั้นคุณต้องเปิดเผยเป็นสาธารณะภายในหรือ (ug) แม้ว่าฉันคิดว่าการจัดองค์ประกอบตามสัญญาเป็นวิธีที่ดีที่สุดในการแก้ปัญหาการสืบทอดหลายแบบ
cfeduke

4
คำตอบที่ยอดเยี่ยม! กระชับง่ายต่อการเข้าใจภาพประกอบที่มีประโยชน์มาก ขอบคุณ!
AJ

3
เราอาจต้องการตรวจสอบว่าmyCarพวงมาลัยซ้ายเสร็จก่อนโทรStopหรือไม่ มันอาจเกลือกกลิ้งถ้าStopนำไปใช้ในขณะที่myCarความเร็วมากเกินไป : D
Devraj Gadhavi

16

ฉันสร้างC-post-compilerที่ทำให้สิ่งนี้:

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> { }

คุณสามารถเรียกใช้ post-compiler เป็น Visual Studio post-build-event:

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

6

คุณสามารถมีคลาสฐานนามธรรมหนึ่งคลาสที่ใช้ทั้ง IFirst และ ISecond แล้วสืบทอดจากฐานนั้น


นี่อาจเป็นทางออกที่ดีที่สุด แต่ไม่จำเป็นต้องเป็นความคิดที่ดีที่สุด: p
leppie

1
คุณจะไม่ต้องแก้ไขคลาสนามธรรมเมื่อคุณเพิ่มวิธีการใน interafces?
Rik

Rik: คุณขี้เกียจแค่ไหนเมื่อคุณต้องทำครั้งเดียว?
leppie

3
@leppie - "ทุกครั้งที่ฉันเพิ่มวิธีการหนึ่งในอินเทอร์เฟซที่ฉันต้องการเปลี่ยนคลาส FirstAndSecond เช่นกัน" คำถามเดิมส่วนนี้ไม่ได้ระบุไว้ในโซลูชันนี้ใช่หรือไม่
Rik

2
คุณจะต้องแก้ไขคลาสนามธรรม แต่คุณไม่ต้องแก้ไขคลาสอื่น ๆ ที่ขึ้นอยู่กับมัน เจ้าชู้หยุดอยู่ตรงนั้นแทนที่จะดำเนินการต่อเพื่อรวบรวมคลาสทั้งหมด
Joel Coehoorn

3

MI ไม่เลวทุกคนที่ใช้ (อย่างจริงจัง) ใช้มันรักมันและมันไม่ซับซ้อนรหัส! อย่างน้อยไม่เกินโครงสร้างอื่น ๆ อาจทำให้รหัสซับซ้อน รหัสที่ไม่ถูกต้องคือรหัสที่ไม่ดีโดยไม่คำนึงว่า MI อยู่ในรูปภาพหรือไม่

อย่างไรก็ตามฉันมีวิธีแก้ปัญหาเล็ก ๆ น้อย ๆ ที่ดีสำหรับการสืบทอดหลายอย่างที่ฉันต้องการแบ่งปัน http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blogหรือคุณสามารถติดตามลิงก์ใน sig ของฉัน ... :)


เป็นไปได้หรือไม่ที่จะมีการสืบทอดหลายทาง วิธีแก้ปัญหาที่ฉันรู้เกี่ยวกับปัญหาของการสืบทอดหลายครั้งนั้นเกี่ยวข้องกับการปลดเปลื้องที่ไม่ได้รักษาเอกลักษณ์ (ถ้าmyFooเป็นประเภทFooที่สืบทอดมาจากMooและGooทั้งสองซึ่งสืบทอดมาจากBooนั้น(Boo)(Moo)myFooและ(Boo)(Goo)myFooจะไม่เท่ากัน) คุณตระหนักถึงวิธีการอนุรักษ์เอกลักษณ์หรือไม่?
supercat

1
คุณสามารถใช้ web.archive.org หรือคล้ายกันเพื่อติดตามลิงก์ แต่กลับกลายเป็นการอภิปรายอย่างละเอียดมากขึ้นเกี่ยวกับวิธีการแก้ปัญหาที่นำเสนอในคำถามเดิมที่นี่
MikeBeaton

2

ในการใช้งานของฉันเองฉันพบว่าการใช้คลาส / อินเตอร์เฟสสำหรับ MI แม้ว่า "รูปแบบที่ดี" มักจะมีความซับซ้อนมากกว่าเนื่องจากคุณต้องตั้งค่าการสืบทอดหลาย ๆ อย่างสำหรับการเรียกใช้ฟังก์ชันที่จำเป็นเพียงไม่กี่ครั้งและในกรณีของฉัน จำเป็นต้องทำอย่างแท้จริงหลายสิบครั้งซ้ำซ้อน

แต่มันง่ายกว่าที่จะเพียงแค่สร้าง "ฟังก์ชั่นคงที่ที่เรียกใช้ฟังก์ชันที่เรียกใช้ฟังก์ชัน" ในรูปแบบโมดูลาร์ที่แตกต่างกันแทน OOP วิธีแก้ปัญหาที่ฉันกำลังทำอยู่คือ "ระบบสะกดคำ" สำหรับเกม RPG ที่เอฟเฟกต์จำเป็นต้องใช้ฟังก์ชั่นมิกซ์แอนด์แมทช์อย่างหนักเพื่อให้คาถาที่หลากหลายโดยไม่ต้องเขียนโค้ดใหม่เหมือนตัวอย่างที่บ่งบอก

ตอนนี้ฟังก์ชั่นส่วนใหญ่คงที่เพราะฉันไม่จำเป็นต้องมีอินสแตนซ์สำหรับการสะกดตรรกะในขณะที่การสืบทอดคลาสไม่สามารถใช้คำหลักเสมือนหรือนามธรรมในขณะที่คงที่ การเชื่อมต่อไม่สามารถใช้งานได้เลย

การเข้ารหัสดูเหมือนเร็วขึ้นและทำความสะอาดด้วยวิธี IMO นี้ หากคุณกำลังทำฟังก์ชั่นอยู่และไม่ต้องการคุณสมบัติที่สืบทอดมาให้ใช้ฟังก์ชั่น


2

ด้วย C # 8 ตอนนี้คุณมีมรดกหลายอย่างผ่านการใช้งานเริ่มต้นของสมาชิกอินเตอร์เฟส:

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
}

4
ใช่ แต่โปรดสังเกตว่าข้างต้นคุณจะไม่สามารถทำได้new ConsoleLogger().Log(someEception)- มันจะไม่ทำงานคุณจะต้องโยนวัตถุของคุณไปที่การILoggerใช้วิธีการเริ่มต้นอย่างชัดเจน ดังนั้นประโยชน์ของมันจึงค่อนข้าง จำกัด
Dmitri Nesteruk

1

หากคุณสามารถใช้ชีวิตด้วยข้อ จำกัด ที่วิธีการของ 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();
}

ดังนั้นแนวคิดพื้นฐานคือคุณกำหนดการใช้งานที่จำเป็นในอินเทอร์เฟซ ... สิ่งที่จำเป็นนี้ควรสนับสนุนการใช้งานที่ยืดหยุ่นในวิธีการส่วนขยาย ทุกครั้งที่คุณต้อง "เพิ่มวิธีการในอินเทอร์เฟซ" แทนคุณเพิ่มวิธีการส่วนขยาย


1

ใช่การใช้ส่วนต่อประสานนั้นเป็นเรื่องที่ยุ่งยากเพราะเมื่อใดก็ตามที่เราเพิ่มวิธีการในชั้นเรียนเราต้องเพิ่มลายเซ็นในส่วนต่อประสาน นอกจากนี้ถ้าเรามีคลาสที่มีวิธีการมากมาย แต่ไม่มีส่วนต่อประสานสำหรับมัน? เราต้องสร้างส่วนต่อประสานสำหรับคลาสทั้งหมดที่เราต้องการสืบทอดด้วยตนเอง และสิ่งที่แย่ที่สุดคือเราต้องใช้วิธีการทั้งหมดในส่วนต่อประสานในชั้นเรียนของเด็กถ้าชั้นเด็กจะรับช่วงจากหลายส่วนต่อประสาน

โดยต่อไปนี้รูปแบบการออกแบบซุ้มเราสามารถจำลองการสืบทอดจากหลายชั้นเรียนโดยใช้accessors ประกาศคลาสเป็นคุณสมบัติที่มี {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 จะสามารถเข้าถึงวิธีการและคุณสมบัติทั้งหมดของ Class Father and Mother, การจำลองการสืบทอดหลายแบบ, การสืบทอดอินสแตนซ์ของคลาสพาเรนต์ ไม่เหมือนกัน แต่มันใช้งานได้จริง


2
ฉันไม่เห็นด้วยกับวรรคแรก คุณเพิ่มลายเซ็นของเมธอดที่คุณต้องการในคลาส EVERY ไปยังอินเทอร์เฟซเท่านั้น แต่คุณสามารถเพิ่มวิธีการเพิ่มเติมเป็นคลาสใดก็ได้ตามที่คุณต้องการ นอกจากนี้ยังมีการคลิกขวาแยกอินเทอร์เฟซซึ่งทำให้งานของการแยกอินเทอร์เฟซง่าย ในที่สุดตัวอย่างของคุณไม่ได้รับมรดก แต่อย่างใด แต่ก็เป็นตัวอย่างที่ดีของการแต่งเพลง อนึ่งมันจะมีข้อดีมากกว่านี้ถ้าคุณใช้อินเตอร์เฟสเพื่อสาธิต DI / IOC โดยใช้ constructor / property injection ในขณะที่ฉันจะไม่ลงคะแนนฉันไม่คิดว่ามันเป็นคำตอบที่ดีขอโทษ
ฟรานซิส Rodgers

1
มองย้อนกลับไปที่เธรดนี้หนึ่งปีต่อมาฉันยอมรับว่าคุณสามารถเพิ่มวิธีการได้มากเท่าที่คุณต้องการในชั้นเรียนโดยไม่ต้องเพิ่มลายเซ็นในอินเทอร์เฟซอย่างไรก็ตามสิ่งนี้จะทำให้อินเทอร์เฟซไม่สมบูรณ์ นอกจากนี้ฉันไม่สามารถค้นหาคลิกขวา - แยกส่วนติดต่อใน IDE ของฉันได้บางทีฉันอาจขาดหายไป แต่ข้อกังวลที่ใหญ่กว่าของฉันคือเมื่อคุณระบุลายเซ็นในอินเทอร์เฟซคลาสที่สืบทอดจะต้องใช้ลายเซ็นนั้น ฉันคิดว่านี่เป็นงานซ้ำซ้อนและอาจนำไปสู่การทำซ้ำรหัส
โยคี

EXTRACT INTERFACE: คลิกขวาที่ลายเซ็นคลาสจากนั้นแตกอินเตอร์เฟส ... ในกระบวนการเดียวกัน VS2015 ยกเว้นคุณต้องคลิกขวาจากนั้นเลือกQuick Actions and Refactorings...สิ่งนี้เป็นสิ่งที่ต้องรู้มันจะช่วยให้คุณประหยัดเวลาได้มาก
Chef_Code

1

เราทุกคนดูเหมือนจะมุ่งหน้าลงเส้นทางอินเทอร์เฟซด้วยสิ่งนี้ แต่ความเป็นไปได้อื่น ๆ ที่ชัดเจนที่นี่คือทำสิ่งที่ OOP ควรจะทำและสร้างแผนภูมิการรับมรดกของคุณ ... (นี่ไม่ใช่สิ่งที่การออกแบบชั้นเรียนทั้งหมด เกี่ยวกับ?)

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

โครงสร้างนี้มีบล็อกโค้ดที่นำมาใช้ซ้ำได้และแน่นอนว่าควรเขียนโค้ด OOP อย่างไร

หากวิธีการเฉพาะนี้ไม่เหมาะกับใบเสร็จเราเพียงแค่สร้างคลาสใหม่ตามวัตถุที่ต้องการ ...

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

0

การสืบทอดหลายครั้งเป็นหนึ่งในสิ่งเหล่านั้นที่ทำให้เกิดปัญหามากกว่าการแก้ไข ใน C ++ มันเหมาะกับรูปแบบของการให้เชือกคุณพอที่จะแขวนตัวเอง แต่ Java และ C # เลือกที่จะไปเส้นทางที่ปลอดภัยกว่าที่จะไม่ให้ตัวเลือกแก่คุณ ปัญหาที่ใหญ่ที่สุดคือสิ่งที่ต้องทำถ้าคุณรับหลายคลาสที่มีวิธีการที่มีลายเซ็นเดียวกันกับที่สืบทอดไม่ได้ใช้ ควรเลือกวิธีการเรียนแบบใด หรือว่าไม่ควรรวบรวม โดยทั่วไปมีวิธีอื่นที่จะใช้สิ่งต่าง ๆ ส่วนใหญ่ที่ไม่ต้องพึ่งพามรดกหลายรายการ


8
โปรดอย่าตัดสิน MI ด้วย C ++ เหมือนกับการตัดสิน OOP โดย PHP หรือรถยนต์โดย Pintos ปัญหานั้นแก้ไขได้ง่ายในไอเฟลเมื่อคุณรับช่วงเรียนคุณต้องระบุวิธีที่คุณต้องการรับช่วงและคุณสามารถเปลี่ยนชื่อพวกเขาได้ ไม่มีความคลุมเครืออยู่ที่นั่นและไม่แปลกใจเช่นกัน
Jörg W Mittag

2
@mP: ไม่, Eiffel แสดงการสืบทอดหลายอย่างแท้จริง การเปลี่ยนชื่อไม่ได้หมายถึงการสูญเสียห่วงโซ่การสืบทอด
Abel

0

ถ้า X สืบทอดจาก Y นั่นจะมีเอฟเฟกต์มุมฉากสองอย่าง:

  1. Y จะให้ฟังก์ชันการทำงานเริ่มต้นสำหรับ X ดังนั้นรหัสสำหรับ X จะต้องรวมเนื้อหาที่แตกต่างจาก Y
  2. เกือบทุกที่ที่คาดว่าจะมี Y อาจใช้ X แทน

แม้ว่าการสืบทอดจะให้คุณสมบัติทั้งสองอย่าง แต่ก็ไม่ยากที่จะจินตนาการถึงสถานการณ์ที่อาจใช้งานได้โดยไม่ต้องใช้คุณสมบัติอื่น ไม่มี. net language ที่ฉันรู้จักมีวิธีนำไปใช้โดยตรงโดยไม่ต้องใช้ที่สองถึงแม้ว่าจะสามารถรับฟังก์ชั่นดังกล่าวได้โดยการกำหนด class base ที่ไม่เคยใช้โดยตรงและมีหนึ่งคลาสขึ้นไปที่สืบทอดโดยตรงจากมัน ใหม่ (ชั้นเรียนดังกล่าวสามารถแบ่งปันรหัสทั้งหมดของพวกเขา แต่จะไม่สามารถทดแทนกันได้) อย่างไรก็ตามภาษาที่สอดคล้องกับ CLR ใด ๆ จะอนุญาตให้ใช้อินเทอร์เฟซที่ให้คุณสมบัติที่สองของอินเทอร์เฟซ (การทดแทน) โดยไม่มีการใช้ครั้งแรก


0

ฉันรู้ว่าฉันรู้แม้ว่ามันจะไม่ได้รับอนุญาตและอื่น ๆ บางครั้งคุณจำเป็นต้องใช้มันเพื่อ:

class a {}
class b : a {}
class c : b {}

เหมือนในกรณีของฉันฉันต้องการที่จะทำคลาสนี้ b: แบบฟอร์ม (ครับ windows.forms) คลาส c: b {}

สาเหตุครึ่งหนึ่งของฟังก์ชั่นเหมือนกันและด้วยอินเตอร์เฟสที่คุณต้องเขียนใหม่ทั้งหมด


1
ตัวอย่างของคุณไม่ได้อธิบายการสืบทอดหลายแบบดังนั้นคุณพยายามแก้ไขปัญหาอะไร ตัวอย่างมรดกที่แท้จริงหลายรายการจะแสดงclass a : b, c(การนำหลุมสัญญาที่จำเป็นไปใช้) บางทีตัวอย่างของคุณเรียบง่ายหรือไม่
M.Babcock

0

เนื่องจากคำถามของการสืบทอดหลายรายการ (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 มีความซับซ้อนเพียงพอและจำนวนของวิธีการเข้าถึงส่วนตัว / ที่ได้รับการป้องกันอยู่ในระดับปานกลางรูปแบบนี้อาจช่วยในการเอาชนะการขาดมรดกหลายรายการ


0

นี่เป็นไปตามคำตอบของ 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; }
}

ตอนนี้วัตถุใด ๆ ที่รู้วิธีรับบุคคลสามารถใช้ IGetPerson ได้และจะมีเมธอด GetAgeViaPerson () และ GetNameViaPerson () โดยอัตโนมัติ จากจุดนี้โดยทั่วไปรหัสบุคคลทั้งหมดจะเข้าสู่ IGetPerson ไม่ใช่ IPerson นอกเหนือจาก ivars ใหม่ซึ่งต้องเข้าไปทั้งคู่ และในการใช้รหัสดังกล่าวคุณไม่ต้องกังวลว่าวัตถุ IGetPerson ของคุณจะเป็น IPerson จริงหรือไม่


0

ตอนนี้เป็นpartialคลาสรางที่เป็นไปได้ซึ่งแต่ละคลาสสามารถสืบทอดคลาสด้วยตัวเองทำให้วัตถุสุดท้ายสืบทอดคลาสพื้นฐานทั้งหมด คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับมันนี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.