Liệu nó có ý nghĩa để xác định một giao diện nếu tôi đã có một lớp trừu tượng?


12

Tôi có một lớp với một số chức năng mặc định / chia sẻ. tôi sử dụngabstract class cho nó:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

Như bạn thấy, tôi cũng có giao diện ITypeNameMapper . Liệu nó có ý nghĩa để xác định giao diện này nếu tôi đã có một lớp trừu tượng TypeNameMapperhoặc abstract classlà vừa đủ?

TypeDefinition trong ví dụ tối thiểu này là trừu tượng quá.


4
Chỉ cần FYI - trong OOD, kiểm tra các loại trong mã là một dấu hiệu cho thấy hệ thống phân cấp lớp của bạn không chính xác - nó đang bảo bạn tạo các lớp dẫn xuất từ ​​loại bạn đang kiểm tra. Vì vậy, trong trường hợp này, bạn muốn có một giao diện ITypeMapper chỉ định phương thức Map () và sau đó triển khai giao diện đó trong cả hai lớp TypeDefDef và ClassDefDef. Bằng cách đó, ClassAssembler của bạn chỉ cần lặp qua danh sách ITypeMappers, gọi Map () trên mỗi loại và nhận chuỗi mong muốn từ cả hai loại vì mỗi loại đều có cách triển khai riêng.
Rodney P. Barbati

1
Sử dụng một lớp cơ sở thay cho giao diện, trừ khi bạn thực sự phải, được gọi là "ghi cơ sở". Nếu nó có thể được thực hiện với một giao diện, hãy làm nó với một giao diện. Các lớp trừu tượng sau đó trở thành một bổ sung hữu ích. artima.com/intv/dotnet.html Cụ thể, việc từ xa yêu cầu bạn xuất phát từ MarshalByRefObject để nếu bạn muốn được từ xa, bạn không thể lấy từ bất cứ điều gì khác.
Ben

@ RodneyP.Barbati sẽ không hoạt động nếu tôi muốn có nhiều người lập bản đồ cho cùng loại mà tôi có thể chuyển đổi tùy theo tình huống
Konrad

1
@Ben đốt cơ sở đó là thú vị. Cảm ơn đá quý!
Konrad

@Konrad Điều đó không chính xác - không có gì ngăn cản bạn thực hiện giao diện bằng cách gọi một triển khai giao diện khác, do đó, mỗi loại có thể có một triển khai có thể cắm mà bạn thể hiện thông qua triển khai trong loại.
Rodney P. Barbati

Câu trả lời:


31

Có, vì C # không cho phép nhiều kế thừa trừ giao diện.

Vì vậy, nếu tôi có một lớp vừa là TypeNameMapper và SomethingelseMapper, tôi có thể làm:

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}

4
@Liath: Trách nhiệm duy nhất thực sự là một yếu tố góp phần giải thích tại sao cần thừa kế. Hãy xem xét ba đặc điểm thể: ICanFly, ICanRun, ICanSwim. Bạn cần tạo 8 lớp sinh vật, mỗi lớp cho mỗi sự kết hợp của các đặc điểm (YYY, YYN, YNY, ..., NNY, NNN). SRP chỉ ra rằng bạn thực hiện từng hành vi (bay, chạy, bơi) một lần và sau đó có thể thực hiện lại chúng trên 8 sinh vật. Thành phần thậm chí còn tốt hơn ở đây, nhưng bỏ qua thành phần trong một giây, bạn sẽ thấy sự cần thiết phải kế thừa / thực hiện nhiều hành vi có thể sử dụng lại riêng biệt do SRP.
Flater

4
@Konrad đó là những gì tôi muốn nói. Nếu bạn viết giao diện, và không bao giờ cần nó, thì chi phí là 3 LoC. Nếu bạn không viết giao diện và thấy bạn cần nó, chi phí có thể sẽ tái cấu trúc toàn bộ ứng dụng của bạn.
Ewan

3
(1) Thực hiện giao diện là kế thừa? (2) Cả hai câu hỏi và câu trả lời phải đủ điều kiện. "Nó phụ thuộc." (3) Nhiều kế thừa cần một nhãn dán cảnh báo trên đó. (4) Tôi có thể chỉ cho bạn thấy những interfacesơ suất chính thức điên rồ của K .. các triển khai dự phòng cần có trong cơ sở - đang phát triển độc lập! Yêu thích của tôi: mỗi lớp 1 phương thức thực hiện 1 phương thức riêng của mình interface, mỗi lớp chỉ có một thể hiện duy nhất. ... vân vân, vân vân và vân vân
radarbob

3
Có một hệ số ma sát vô hình trong mã. Bạn cảm thấy nó khi buộc phải đọc các giao diện thừa. Sau đó, nó cho thấy bản thân nóng lên giống như tàu con thoi lao vào bầu khí quyển khi một giao diện triển khai tạo ra lớp trừu tượng. Đó là Twilight Zone và đây là tàu con thoi Challenger. Chấp nhận số phận của mình, tôi nhìn plasma đang hoành hành với sự ngạc nhiên, sợ hãi. Ma sát không ngừng xé toạc mặt tiền của sự hoàn hảo, khiến người khổng lồ tan vỡ vào sản xuất với một tiếng sấm sét.
radarbob

4
@radarbob Thơ mộng. ^ _ ^ Tôi đồng ý; trong khi chi phí giao diện thấp, nó không tồn tại. Không quá nhiều trong việc viết chúng, nhưng trong một tình huống gỡ lỗi, 1-2 bước nữa để có được hành vi thực tế, có thể tăng lên nhanh chóng khi bạn có được một nửa tá mức độ trừu tượng sâu. Trong một thư viện, nó thường có giá trị nó. Nhưng trong một ứng dụng, tái cấu trúc tốt hơn so với khái quát hóa sớm.
Erroratz

1

Các giao diện và các lớp trừu tượng phục vụ các mục đích khác nhau:

  • Giao diện xác định API và thuộc về các máy khách chứ không phải các cài đặt.
  • Nếu các lớp chia sẻ triển khai thì bạn có thể được hưởng lợi từ một lớp trừu tượng .

Trong ví dụ của bạn, interface ITypeNameMapperxác định nhu cầu của khách hàng và abstract class TypeNameMapperkhông thêm bất kỳ giá trị nào.


2
Các lớp trừu tượng cũng thuộc về khách hàng. Tôi không biết ý của bạn là gì Tất cả mọi thứ publicthuộc về khách hàng. TypeNameMapperđang thêm rất nhiều giá trị vì nó tiết kiệm cho người thực hiện khỏi việc viết một số logic thông thường.
Konrad

2
Việc một giao diện được trình bày dưới dạng interfacehay không chỉ có liên quan trong đó C # cấm toàn bộ thừa kế.
Ded

0

Toàn bộ khái niệm về giao diện đã được tạo ra để hỗ trợ một nhóm các lớp có API chia sẻ.

Điều này rõ ràng đang nói rằng bất kỳ việc sử dụng giao diện nào cũng ngụ ý rằng có (hoặc dự kiến ​​là) nhiều hơn một lần thực hiện đặc tả của nó.

Các khung DI đã làm vấy bẩn vùng biển ở đây vì nhiều trong số chúng yêu cầu giao diện mặc dù sẽ chỉ có một triển khai - với tôi đây là chi phí vô lý đối với hầu hết các trường hợp chỉ là một phương thức gọi mới phức tạp hơn và chậm hơn, nhưng nó nó là cái gì

Câu trả lời nằm trong chính câu hỏi. Nếu bạn có một lớp trừu tượng, bạn đang chuẩn bị cho việc tạo ra nhiều hơn một lớp dẫn xuất với một API chung. Do đó, việc sử dụng một giao diện được chỉ định rõ ràng.


2
"Nếu bạn có một lớp trừu tượng, ... việc sử dụng một giao diện được chỉ định rõ ràng." - Kết luận của tôi là ngược lại: Nếu bạn có một lớp trừu tượng, thì hãy sử dụng nó như thể nó là một giao diện (nếu DI không chơi với nó, hãy xem xét một cách triển khai khác). Chỉ khi nó thực sự không hoạt động, hãy tạo giao diện - bạn có thể làm điều đó bất cứ lúc nào sau đó.
maaartinus

1
Ditto, @maaartinus. Và thậm chí không "... như thể đó là một giao diện." Các thành viên công cộng của một lớp một giao diện. Ngoài ra ditto cho "bạn có thể làm điều đó bất cứ lúc nào sau"; điều này đi vào trung tâm của các nguyên tắc cơ bản thiết kế 101.
radarbob

@maaartinus: Nếu một người tạo giao diện ngay từ đầu, nhưng sử dụng các tham chiếu của loại lớp trừu tượng ở khắp mọi nơi, sự tồn tại của giao diện sẽ không giúp được gì. Tuy nhiên, nếu mã máy khách sử dụng loại giao diện thay vì loại lớp trừu tượng bất cứ khi nào có thể, điều đó sẽ tạo điều kiện thuận lợi cho mọi thứ nếu cần thiết để tạo ra một triển khai thực sự khác với lớp trừu tượng. Thay đổi mã máy khách tại thời điểm đó để sử dụng giao diện sẽ khó khăn hơn nhiều so với việc thiết kế mã máy khách để làm như vậy ngay từ đầu.
supercat

1
@supercat Tôi có thể đồng ý giả sử bạn đang viết thư viện. Nếu đó là một ứng dụng, thì tôi không thể thấy bất kỳ vấn đề nào thay thế tất cả các lần xuất hiện cùng một lúc. Eclipse của tôi có thể làm điều đó (Java, không phải C #), nhưng nếu không thể, nó giống như find ... -exec perl -pi -e ...và có thể sửa các lỗi biên dịch tầm thường. Quan điểm của tôi là: Lợi thế của việc không có một thứ (hiện tại) vô dụng lớn hơn nhiều so với khoản tiết kiệm trong tương lai có thể.
maaartinus

Câu đầu tiên sẽ đúng, nếu bạn đánh dấu interfacelà mã (bạn đang nói về C # - interfaces, không phải giao diện trừu tượng, như bất kỳ tập hợp lớp / hàm / v.v.) nào và kết thúc nó "... nhưng vẫn ngoài vòng pháp luật đầy đủ di sản." Không cần interfaces nếu bạn có MI.
Ded

0

Nếu chúng tôi muốn trả lời một cách rõ ràng câu hỏi, tác giả nói "Liệu có ý nghĩa gì khi xác định giao diện này nếu tôi đã có một lớp trừu tượng TypeNameMapper hoặc lớp trừu tượng là vừa đủ?"

Câu trả lời là có và không - Có, bạn nên tạo giao diện mặc dù bạn đã có lớp cơ sở trừu tượng (vì bạn không nên tham khảo lớp cơ sở trừu tượng trong bất kỳ mã máy khách nào) và không, vì bạn không nên tạo lớp cơ sở trừu tượng trong trường hợp không có giao diện.

Việc bạn chưa nghĩ đủ về API mà bạn đang cố gắng xây dựng là điều hiển nhiên. Hãy đến với API trước, sau đó cung cấp triển khai một phần nếu muốn. Thực tế là lớp cơ sở trừu tượng của bạn không kiểm tra kiểu mã là đủ để cho bạn biết đó không phải là sự trừu tượng đúng.

Như trong hầu hết mọi thứ ở OOD, khi bạn đang ở trong rãnh và mô hình đối tượng của bạn được thực hiện tốt, mã của bạn sẽ thông báo cho bạn về những gì tiếp theo. Bắt đầu với một giao diện, thực hiện giao diện đó trong các lớp bạn cần. Nếu bạn thấy mình viết mã tương tự, hãy trích xuất nó thành một lớp cơ sở trừu tượng - đó là giao diện quan trọng, lớp cơ sở trừu tượng chỉ là một trình trợ giúp và có thể có nhiều hơn một.


-1

Điều này khá khó để trả lời mà không biết phần còn lại của ứng dụng.

Giả sử bạn đang sử dụng một số loại mô hình DI và đã thiết kế mã của bạn có thể mở rộng nhất có thể (để bạn có thể thêm mới TypeNameMapperdễ dàng) tôi sẽ nói có.

Lý do:

  • Bạn thực sự có được nó miễn phí, vì lớp cơ sở thực hiện interfacemà bạn không cần phải lo lắng về nó trong bất kỳ triển khai con nào
  • Hầu hết các khung IoC và thư viện Mocking đều mong đợi interfaces. Mặc dù nhiều người trong số họ làm việc với các lớp trừu tượng, không phải lúc nào cũng được và tôi là một người tin tưởng lớn khi đi theo con đường giống như 99% người dùng thư viện khác nếu có thể.

Lý do chống lại:

  • Nói đúng ra (như bạn đã chỉ ra) bạn KHÔNG THỰC SỰ cần phải
  • Nếu class/ interfacethay đổi có thêm một chút chi phí

Tất cả những điều được xem xét tôi sẽ nói rằng bạn nên tạo ra interface. Các lý do chống lại khá không đáng kể, nhưng, trong khi có thể sử dụng các tóm tắt trong chế nhạo và IoC, nó thường dễ dàng hơn nhiều với các giao diện. Cuối cùng, tôi sẽ không chỉ trích mã của đồng nghiệp nếu họ đi theo con đường khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.