ทำไมต้องใช้อินเทอร์เฟซอย่างชัดเจน?


122

ดังนั้นกรณีการใช้งานที่ดีสำหรับการใช้งานอินเทอร์เฟซอย่างชัดเจนคืออะไร?

เป็นเพียงเพื่อให้คนที่ใช้คลาสไม่ต้องดูวิธีการ / คุณสมบัติทั้งหมดใน Intellisense?

คำตอบ:


146

หากคุณใช้สองอินเทอร์เฟซทั้งด้วยวิธีการเดียวกันและการใช้งานที่แตกต่างกันคุณจะต้องนำไปใช้อย่างชัดเจน

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

ใช่นี่เป็นกรณีหนึ่งที่ EIMI แก้ไขได้ และประเด็นอื่น ๆ จะครอบคลุมคำตอบของ "Michael B"
เข้ารหัส

10
ตัวอย่างที่ยอดเยี่ยม ชอบอินเทอร์เฟซ / ชื่อคลาส! :-)
Brian Rogers

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

4
@Mike อินเทอร์เฟซอาจเป็นของ API บางตัวหรือ API สองตัวที่แตกต่างกัน บางทีความรักอาจจะเกินจริงไปหน่อย แต่อย่างน้อยฉันก็ดีใจที่มีการใช้งานอย่างชัดเจน
TobiMcNamobi

@BrianRogers และชื่อวิธีการด้วย ;-)
Sнаđошƒаӽ

66

การซ่อนสมาชิกที่ไม่ต้องการนั้นมีประโยชน์ ตัวอย่างเช่นหากคุณใช้ทั้งสองอย่างIComparable<T>และIComparableมักจะดีกว่าในการซ่อนการIComparableโอเวอร์โหลดเพื่อไม่ให้ผู้คนรู้สึกว่าคุณสามารถเปรียบเทียบวัตถุประเภทต่างๆได้ ในทำนองเดียวกันอินเทอร์เฟซบางอย่างไม่สอดคล้องกับ CLS IConvertibleดังนั้นหากคุณไม่ใช้อินเทอร์เฟซอย่างชัดเจนผู้ใช้ปลายทางของภาษาที่ต้องปฏิบัติตามข้อกำหนด CLS จะไม่สามารถใช้ออบเจ็กต์ของคุณได้ (ซึ่งจะเป็นหายนะมากถ้าผู้ใช้ BCL ไม่ได้ซ่อนสมาชิก IConvertible ของดั้งเดิม :))

ข้อสังเกตที่น่าสนใจอีกประการหนึ่งคือโดยปกติการใช้โครงสร้างดังกล่าวหมายความว่าโครงสร้างที่ใช้อินเทอร์เฟซอย่างชัดเจนสามารถเรียกใช้โดยการชกมวยกับประเภทอินเทอร์เฟซเท่านั้น คุณสามารถหลีกเลี่ยงสิ่งนี้ได้โดยใช้ข้อ จำกัด ทั่วไป ::

void SomeMethod<T>(T obj) where T:IConvertible

จะไม่ใส่กล่อง int เมื่อคุณส่งผ่านไป


1
คุณพิมพ์ผิดในข้อ จำกัด ของคุณ รหัสด้านบนใช้งานได้ จำเป็นต้องอยู่ในการประกาศเริ่มต้นของลายเซ็นวิธีการในอินเทอร์เฟซ โพสต์ต้นฉบับไม่ได้ระบุสิ่งนี้ นอกจากนี้รูปแบบที่เหมาะสมคือ "โมฆะ SomeMehtod <T> (T obj) โดยที่ T: IConvertible โปรดทราบว่ามีเครื่องหมายจุดคู่พิเศษอยู่ระหว่าง") "และ" โดยที่ "ซึ่งไม่ควรอยู่ที่นั่น, +1 เพื่อการใช้งานอย่างชาญฉลาดของ ยาสามัญเลี่ยงชกมวยแพง
Zack Jannsen

1
สวัสดี Michael B. เหตุใดในการใช้งาน string ใน. NET จึงมีการใช้งาน IComparable แบบสาธารณะ: public int CompareTo (Object value) {if (value == null) {return 1; } if (! (ค่าคือ String)) {โยน ArgumentException ใหม่ (Environment.GetResourceString ("Arg_MustBeString")); } คืนค่า String.Compare (ค่านี้, (String), StringComparison.CurrentCulture); } ขอบคุณ!
zzfima

stringอยู่รอบ ๆ ก่อนชื่อสามัญและการปฏิบัตินี้อยู่ในสมัย เมื่อ. net 2 เข้ามาพวกเขาไม่ต้องการทำลายอินเทอร์เฟซสาธารณะstringดังนั้นพวกเขาจึงทิ้งมันไว้เหมือนเดิมโดยมีระบบป้องกันอยู่
Michael B

37

เหตุผลเพิ่มเติมบางประการในการติดตั้งอินเทอร์เฟซอย่างชัดเจน:

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

รหัสทำความสะอาด : จะมีข้อผิดพลาดของคอมไพเลอร์หากCloneวิธีการถูกลบออกจาก ICloneable อย่างไรก็ตามหากคุณใช้วิธีนี้โดยปริยายคุณสามารถจบลงด้วยเมธอดสาธารณะ 'orphaned' ที่ไม่ได้ใช้

การพิมพ์ที่ชัดเจน : เพื่อแสดงเรื่องราวของ supercat ด้วยตัวอย่างนี่จะเป็นโค้ดตัวอย่างที่ฉันต้องการการใช้งานICloneableอย่างชัดเจนอนุญาตให้Clone()พิมพ์อย่างชัดเจนเมื่อคุณเรียกมันโดยตรงว่าเป็นMyObjectสมาชิกอินสแตนซ์:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

interface ICloneable<out T> { T Clone(); T self {get;} }เพื่อที่หนึ่งฉันฉันไม่ต้องการ โปรดทราบว่าไม่มีICloneable<T>ข้อ จำกัดใด ๆใน T. ในขณะที่โดยทั่วไปวัตถุสามารถโคลนได้อย่างปลอดภัยหากฐานของมันสามารถเป็นได้ แต่ก็อาจต้องการได้มาจากคลาสฐานซึ่งสามารถโคลนวัตถุของคลาสที่ไม่สามารถทำได้อย่างปลอดภัย เพื่อให้เป็นเช่นนั้นฉันไม่แนะนำให้คลาสที่สืบทอดได้เปิดเผยวิธีการโคลนสาธารณะ มีคลาสที่สืบทอดได้แทนด้วยprotectedวิธีการโคลนนิ่งและคลาสที่ปิดผนึกซึ่งได้มาจากคลาสเหล่านั้นและเปิดเผยการโคลนนิ่งสาธารณะ
supercat

แน่ใจว่าจะดีกว่ายกเว้นว่าไม่มี ICloneable เวอร์ชัน covariant ใน BCL ดังนั้นคุณต้องสร้างขึ้นมาใช่ไหม
Wiebe Tijsma

ทั้ง 3 ตัวอย่างขึ้นอยู่กับสถานการณ์ที่ไม่น่าจะเป็นไปได้และแนวทางปฏิบัติที่ดีที่สุดสำหรับการหยุดทำงาน
MickyD

13

อีกเทคนิคหนึ่งที่มีประโยชน์คือการใช้วิธีการแบบสาธารณะของฟังก์ชันจะส่งคืนค่าที่เฉพาะเจาะจงมากกว่าที่ระบุไว้ในอินเทอร์เฟซ

ตัวอย่างเช่นออบเจ็กต์สามารถใช้งานICloneableได้ แต่ยังคงมีCloneเมธอดที่เปิดเผยต่อสาธารณะส่งคืนประเภทของมันเอง

ในทำนองเดียวกันIAutomobileFactoryอาจมีManufactureเมธอดที่ส่งคืน an Automobileแต่ a FordExplorerFactoryซึ่งใช้งานIAutomobileFactoryอาจมีManufactureวิธีการส่งคืน a FordExplorer(ซึ่งมาจากAutomobile) รหัสที่รู้ว่าFordExplorerFactoryสามารถใช้ - FordExplorerคุณสมบัติเฉพาะบนวัตถุที่ส่งคืนโดยFordExplorerFactoryไม่ต้องพิมพ์แคสต์ในขณะที่รหัสที่รู้เพียงว่ามันมีบางประเภทIAutomobileFactoryก็จะจัดการกับการกลับมาเป็นAutomobileไฟล์.


2
+1 ... นี่เป็นการใช้งานอินเทอร์เฟซที่ชัดเจนที่ฉันต้องการแม้ว่าตัวอย่างโค้ดขนาดเล็กอาจจะชัดเจนกว่าเรื่องนี้เล็กน้อย :)
Wiebe Tijsma

7

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

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}

60
ตัวอย่างที่เกี่ยวข้องกับ OO ที่แปลกประหลาดที่สุดที่ฉันเคยเห็น:public class Animal : Cat, Dog
mbx

38
@mbx: ถ้า Animal ใช้ Parrot ด้วยก็คงจะเป็นสัตว์ Polly-morphing
RenniePet

3
ฉันจำตัวการ์ตูนที่เป็นแมวที่ปลายด้านหนึ่งและสุนัขที่ปลายอีกด้านหนึ่ง ;-)
George Birbilis

2
มีทีวีซีรีส์ในยุค 80 .. "Manimal" .. ที่ผู้ชายสามารถแปรเปลี่ยนเป็น .. โอ้ไม่เป็นไร
bkwdesign

Morkies ดูเหมือนแมวและเป็นแมวนินจาเหมือนกัน
samis

6

สามารถทำให้อินเทอร์เฟซสาธารณะสะอาดขึ้นเพื่อใช้อินเทอร์เฟซอย่างชัดเจนกล่าวคือFileคลาสของคุณอาจนำไปใช้IDisposableอย่างชัดเจนและให้วิธีการสาธารณะClose()ซึ่งอาจเหมาะสมกับผู้บริโภคมากกว่าDispose()

F # นำเสนอการใช้งานอินเทอร์เฟซที่ชัดเจนเท่านั้นดังนั้นคุณจึงต้องแคสต์ไปยังอินเทอร์เฟซเฉพาะเพื่อเข้าถึงฟังก์ชันการทำงานซึ่งทำให้มีการใช้อินเทอร์เฟซที่ชัดเจนมาก (ไม่มีการเล่นสำนวน)


ฉันคิดว่า VB เวอร์ชันส่วนใหญ่รองรับเฉพาะข้อกำหนดอินเทอร์เฟซที่ชัดเจนเท่านั้น
Gabe

1
@Gabe - มันละเอียดกว่าสำหรับ VB - การตั้งชื่อและการเข้าถึงของสมาชิกที่ใช้อินเทอร์เฟซนั้นแยกจากการระบุว่าเป็นส่วนหนึ่งของการนำไปใช้งาน ดังนั้นใน VB และดูคำตอบของ @ Iain (คำตอบยอดนิยมในปัจจุบัน) คุณสามารถใช้ IDoItFast และ IDoItSlow กับสมาชิกสาธารณะ "GoFast" และ "GoSlow" ตามลำดับ
Damien_The_Unbeliever

2
ฉันไม่ชอบตัวอย่างของคุณ (IMHO สิ่งเดียวที่ควรซ่อนDisposeคือสิ่งที่ไม่ต้องการการล้างข้อมูล) IList<T>.Addเป็นตัวอย่างที่ดีกว่าจะเป็นสิ่งที่ชอบการดำเนินงานคอลเลกชันที่ไม่เปลี่ยนรูปของ
supercat

5

หากคุณมีอินเทอร์เฟซภายในและคุณไม่ต้องการใช้สมาชิกในชั้นเรียนของคุณแบบสาธารณะคุณจะต้องนำไปใช้อย่างชัดเจน การใช้งานโดยปริยายจำเป็นต้องเปิดเผยต่อสาธารณะ


โอเคอธิบายได้ว่าทำไมโปรเจ็กต์ถึงไม่คอมไพล์ด้วยการนำไปใช้โดยปริยาย
pauloya

4

เหตุผลสำหรับการดำเนินงานอย่างชัดเจนก็คือสำหรับการบำรุงรักษา

เมื่อชั้นเรียน "ไม่ว่าง" ใช่เกิดขึ้นเราทุกคนไม่มีความหรูหราในการปรับโครงสร้างโค้ดของสมาชิกในทีมคนอื่น ๆ - จากนั้นการมีการนำไปใช้อย่างชัดเจนทำให้ชัดเจนว่ามีวิธีการในการตอบสนองสัญญาอินเทอร์เฟซ

ดังนั้นจึงปรับปรุง "ความสามารถในการอ่าน" ของโค้ด


IMHO สำคัญกว่าในการตัดสินใจว่าชั้นเรียนควรหรือไม่ควรเปิดเผยวิธีการนี้กับลูกค้า ที่ผลักดันว่าจะชัดเจนหรือโดยปริยาย หากต้องการบันทึกว่ามีวิธีการหลายอย่างอยู่ด้วยกันในกรณีนี้เนื่องจากเป็นไปตามสัญญานั่น#regionคือสิ่งที่มีไว้สำหรับด้วยสตริงหัวเรื่องที่เหมาะสม และแสดงความคิดเห็นเกี่ยวกับวิธีการ
ToolmakerSteve

1

มีตัวอย่างที่แตกต่างออกไปSystem.Collections.Immutableซึ่งผู้เขียนเลือกที่จะใช้เทคนิคนี้เพื่อรักษา API ที่คุ้นเคยสำหรับประเภทคอลเลกชันในขณะที่ขูดส่วนต่างๆของอินเทอร์เฟซที่ไม่มีความหมายสำหรับประเภทใหม่

รูปธรรมImmutableList<T>การดำเนินการIList<T>และทำให้ICollection<T>( เพื่อที่จะช่วยให้ImmutableList<T>การใช้งานได้ง่ายมากขึ้นด้วยรหัสเดิม) ยังvoid ICollection<T>.Add(T item)ทำให้รู้สึกไม่สำหรับImmutableList<T>: ตั้งแต่การเพิ่มองค์ประกอบไปที่รายการที่ไม่เปลี่ยนรูปจะต้องไม่เปลี่ยนรายการที่มีอยู่ImmutableList<T>นอกจากนี้ยังมาจากการIImmutableList<T>ที่มีIImmutableList<T> Add(T item)สามารถใช้สำหรับการ รายการที่ไม่เปลี่ยนรูป

ดังนั้นในกรณีของAddการนำไปใช้ในImmutableList<T>ท้ายที่สุดมีดังนี้:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

0

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

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object

1
"วิธีการทั้งหมดเป็นแบบส่วนตัวโดยอัตโนมัติ" ซึ่งไม่ถูกต้องในทางเทคนิค b / c หากเป็นแบบส่วนตัวจริงก็จะเรียกไม่ได้เลยว่าแคสต์หรือไม่
samis

0

นี่คือวิธีที่เราสามารถสร้าง Explicit Interface: ถ้าเรามี 2 อินเทอร์เฟซและทั้งสองอินเทอร์เฟซมีเมธอดเดียวกันและคลาสเดียวจะสืบทอดอินเทอร์เฟซทั้ง 2 นี้ดังนั้นเมื่อเราเรียกวิธีอินเตอร์เฟสหนึ่งคอมไพเลอร์จะสับสนว่าจะเรียกเมธอดใดจึงจะสามารถ จัดการปัญหานี้โดยใช้ Explicit Interface นี่คือตัวอย่างหนึ่งที่ฉันให้ไว้ด้านล่าง

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

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