Design for an interface implementation that provides additional functionality

There is a design problem that I came upon while implementing an interface:

Let’s say there is a Device interface that promises to provide functionalities PerformA() and GetB(). This interface will be implemented for multiple models of a device. What happens if one model has an additional functionality CheckC() which doesn’t have equivalents in other implementations?

I came up with different solutions, none of which seems to comply with interface design guidelines:

  • To add CheckC() method to the interface and leave one of its
    implementations empty:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>interface ISomeDevice
{
void PerformA();
int GetB();
bool CheckC();
}
class DeviceModel1 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
bool res;
// assign res a value based on some validation
return res;
}
}
class DeviceModel2 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
return true; // without checking anything
}
}
</code>
<code>interface ISomeDevice { void PerformA(); int GetB(); bool CheckC(); } class DeviceModel1 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } public bool CheckC() { bool res; // assign res a value based on some validation return res; } } class DeviceModel2 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } public bool CheckC() { return true; // without checking anything } } </code>
interface ISomeDevice
{
    void PerformA();
    int GetB();
    bool CheckC();
}

class DeviceModel1 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        bool res;
        // assign res a value based on some validation
        return res;
    }
}

class DeviceModel2 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        return true; // without checking anything
    }
}

This solution seems incorrect as a class implements an interface without truly implementing all the demanded methods.

  • To leave out CheckC() method from the interface and to use explicit cast in order to call it:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>interface ISomeDevice
{
void PerformA();
int GetB();
}
class DeviceModel1 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
bool res;
// assign res a value based on some validation
return res;
}
}
class DeviceModel2 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
}
class DeviceManager
{
private ISomeDevice myDevice;
public void ManageDevice(bool newDeviceModel)
{
myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
myDevice.PerformA();
int b = myDevice.GetB();
if (newDeviceModel)
{
DeviceModel1 newDevice = myDevice as DeviceModel1;
bool c = newDevice.CheckC();
}
}
}
</code>
<code>interface ISomeDevice { void PerformA(); int GetB(); } class DeviceModel1 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } public bool CheckC() { bool res; // assign res a value based on some validation return res; } } class DeviceModel2 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } } class DeviceManager { private ISomeDevice myDevice; public void ManageDevice(bool newDeviceModel) { myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2(); myDevice.PerformA(); int b = myDevice.GetB(); if (newDeviceModel) { DeviceModel1 newDevice = myDevice as DeviceModel1; bool c = newDevice.CheckC(); } } } </code>
interface ISomeDevice
{
    void PerformA();
    int GetB();
}

class DeviceModel1 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        bool res;
        // assign res a value based on some validation
        return res;
    }
}

class DeviceModel2 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
}

class DeviceManager
{
    private ISomeDevice myDevice;
    public void ManageDevice(bool newDeviceModel)
    {
        myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
        myDevice.PerformA();
        int b = myDevice.GetB();
        if (newDeviceModel)
        {
            DeviceModel1 newDevice = myDevice as DeviceModel1;
            bool c = newDevice.CheckC();
        }
    }
}

This solution seems to make the interface inconsistent.

  • For the device that supports CheckC(): to add the logic of CheckC() into the logic of another method that is present in the interface. This solution is not always possible.

So, what is the correct design to be used in such cases? Maybe creating an interface should be abandoned altogether in favor of another design?

11

Consider this solution:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>interface IDevice
{
void PerformA();
int GetB();
}
interface INewDevice : IDevice
{
bool CheckC();
}
class DeviceModel1 : INewDevice
{
...
}
class DeviceModel2 : IDevice
{
...
}
class DeviceManager
{
private IDevice myDevice;
public void ManageDevice(bool newDeviceModel)
{
myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
myDevice.PerformA();
int b = myDevice.GetB();
if (newDeviceModel)
{
INewDevice newDevice = myDevice as INewDevice;
bool c = newDevice.CheckC();
}
}
}
</code>
<code>interface IDevice { void PerformA(); int GetB(); } interface INewDevice : IDevice { bool CheckC(); } class DeviceModel1 : INewDevice { ... } class DeviceModel2 : IDevice { ... } class DeviceManager { private IDevice myDevice; public void ManageDevice(bool newDeviceModel) { myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2(); myDevice.PerformA(); int b = myDevice.GetB(); if (newDeviceModel) { INewDevice newDevice = myDevice as INewDevice; bool c = newDevice.CheckC(); } } } </code>
interface IDevice
{
    void PerformA();
    int GetB();
}

interface INewDevice : IDevice
{
    bool CheckC();
}

class DeviceModel1 : INewDevice
{
    ...
}


class DeviceModel2 : IDevice
{
    ...
}

class DeviceManager
{
    private IDevice myDevice;
    public void ManageDevice(bool newDeviceModel)
    {
        myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
        myDevice.PerformA();
        int b = myDevice.GetB();
        if (newDeviceModel)
        {
            INewDevice newDevice = myDevice as INewDevice;
            bool c = newDevice.CheckC();
        }
    }
}

Wherever you don’t care about the difference between an IDevice and an INewDevice, you can use the methods common to both. If you do need behavior specific to new devices, you cast to the new interface, so you don’t tie yourself down to any particular implementation.

If the casting bothers you, see this question for ways of creating a new type that may contain a value of type A or B (or C, or …) and provides a type-safe way of taking actions depending on what the type of the value actually is. I especially like Joey’s answer for its use of named parameters and lambdas. You could for example make collections that hold Either<IDevice, INewDevice> and then you can use the Match method to avoid any casting. E.g.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>IList<Either<IDevice, INewDevice>> devices = // get a list of devices
foreach (var device in devices) {
device.Match(
Left: oldDevice => // things to do if it's an old device
Right: newDevice => // things to do if it's a new device
);
}
</code>
<code>IList<Either<IDevice, INewDevice>> devices = // get a list of devices foreach (var device in devices) { device.Match( Left: oldDevice => // things to do if it's an old device Right: newDevice => // things to do if it's a new device ); } </code>
IList<Either<IDevice, INewDevice>> devices = // get a list of devices
foreach (var device in devices) {
    device.Match(
        Left: oldDevice => // things to do if it's an old device
        Right: newDevice => // things to do if it's a new device
    );
}

There’s no risk of doing a wrong cast using this approach.

It depends a lot, really (now that’s a surprise).
Here are a few things to consider:

What is the contract of the interface?
If the interface should allow performA and getB, but device2 requires checkC to be called, then it does not adhere to the interface. If the interface defines that C must be checkable, then device1 does not adhere. If your algorithm uses that check, it might be the latter.

Can you define a reasonable default behavior?
If it’s just a check you could always return true.

Is it an optional operation?
Define it to be optional. The interface might specify that throwing an exception is allowed. Add another method CanCheckC that returns whether the operation is supported. Or add TryCheckC that returns true iff a check was performed and returns the result in an out-parameter.

Is it a new type of interface?
If, in general, devices can’t check C but some class of device can, maybe this is a new interface. So add a new interface IDeviceWithCCheck that provides the additional operations and use type checks in your algorithm before you cast.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa

Design for an interface implementation that provides additional functionality

There is a design problem that I came upon while implementing an interface:

Let’s say there is a Device interface that promises to provide functionalities PerformA() and GetB(). This interface will be implemented for multiple models of a device. What happens if one model has an additional functionality CheckC() which doesn’t have equivalents in other implementations?

I came up with different solutions, none of which seems to comply with interface design guidelines:

  • To add CheckC() method to the interface and leave one of its
    implementations empty:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>interface ISomeDevice
{
void PerformA();
int GetB();
bool CheckC();
}
class DeviceModel1 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
bool res;
// assign res a value based on some validation
return res;
}
}
class DeviceModel2 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
return true; // without checking anything
}
}
</code>
<code>interface ISomeDevice { void PerformA(); int GetB(); bool CheckC(); } class DeviceModel1 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } public bool CheckC() { bool res; // assign res a value based on some validation return res; } } class DeviceModel2 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } public bool CheckC() { return true; // without checking anything } } </code>
interface ISomeDevice
{
    void PerformA();
    int GetB();
    bool CheckC();
}

class DeviceModel1 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        bool res;
        // assign res a value based on some validation
        return res;
    }
}

class DeviceModel2 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        return true; // without checking anything
    }
}

This solution seems incorrect as a class implements an interface without truly implementing all the demanded methods.

  • To leave out CheckC() method from the interface and to use explicit cast in order to call it:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>interface ISomeDevice
{
void PerformA();
int GetB();
}
class DeviceModel1 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
bool res;
// assign res a value based on some validation
return res;
}
}
class DeviceModel2 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
}
class DeviceManager
{
private ISomeDevice myDevice;
public void ManageDevice(bool newDeviceModel)
{
myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
myDevice.PerformA();
int b = myDevice.GetB();
if (newDeviceModel)
{
DeviceModel1 newDevice = myDevice as DeviceModel1;
bool c = newDevice.CheckC();
}
}
}
</code>
<code>interface ISomeDevice { void PerformA(); int GetB(); } class DeviceModel1 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } public bool CheckC() { bool res; // assign res a value based on some validation return res; } } class DeviceModel2 : ISomeDevice { public void PerformA() { // do stuff } public int GetB() { return 1; } } class DeviceManager { private ISomeDevice myDevice; public void ManageDevice(bool newDeviceModel) { myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2(); myDevice.PerformA(); int b = myDevice.GetB(); if (newDeviceModel) { DeviceModel1 newDevice = myDevice as DeviceModel1; bool c = newDevice.CheckC(); } } } </code>
interface ISomeDevice
{
    void PerformA();
    int GetB();
}

class DeviceModel1 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
    public bool CheckC() {
        bool res;
        // assign res a value based on some validation
        return res;
    }
}

class DeviceModel2 : ISomeDevice
{
    public void PerformA() { // do stuff }
    public int GetB() { return 1; }
}

class DeviceManager
{
    private ISomeDevice myDevice;
    public void ManageDevice(bool newDeviceModel)
    {
        myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
        myDevice.PerformA();
        int b = myDevice.GetB();
        if (newDeviceModel)
        {
            DeviceModel1 newDevice = myDevice as DeviceModel1;
            bool c = newDevice.CheckC();
        }
    }
}

This solution seems to make the interface inconsistent.

  • For the device that supports CheckC(): to add the logic of CheckC() into the logic of another method that is present in the interface. This solution is not always possible.

So, what is the correct design to be used in such cases? Maybe creating an interface should be abandoned altogether in favor of another design?

11

Consider this solution:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>interface IDevice
{
void PerformA();
int GetB();
}
interface INewDevice : IDevice
{
bool CheckC();
}
class DeviceModel1 : INewDevice
{
...
}
class DeviceModel2 : IDevice
{
...
}
class DeviceManager
{
private IDevice myDevice;
public void ManageDevice(bool newDeviceModel)
{
myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
myDevice.PerformA();
int b = myDevice.GetB();
if (newDeviceModel)
{
INewDevice newDevice = myDevice as INewDevice;
bool c = newDevice.CheckC();
}
}
}
</code>
<code>interface IDevice { void PerformA(); int GetB(); } interface INewDevice : IDevice { bool CheckC(); } class DeviceModel1 : INewDevice { ... } class DeviceModel2 : IDevice { ... } class DeviceManager { private IDevice myDevice; public void ManageDevice(bool newDeviceModel) { myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2(); myDevice.PerformA(); int b = myDevice.GetB(); if (newDeviceModel) { INewDevice newDevice = myDevice as INewDevice; bool c = newDevice.CheckC(); } } } </code>
interface IDevice
{
    void PerformA();
    int GetB();
}

interface INewDevice : IDevice
{
    bool CheckC();
}

class DeviceModel1 : INewDevice
{
    ...
}


class DeviceModel2 : IDevice
{
    ...
}

class DeviceManager
{
    private IDevice myDevice;
    public void ManageDevice(bool newDeviceModel)
    {
        myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
        myDevice.PerformA();
        int b = myDevice.GetB();
        if (newDeviceModel)
        {
            INewDevice newDevice = myDevice as INewDevice;
            bool c = newDevice.CheckC();
        }
    }
}

Wherever you don’t care about the difference between an IDevice and an INewDevice, you can use the methods common to both. If you do need behavior specific to new devices, you cast to the new interface, so you don’t tie yourself down to any particular implementation.

If the casting bothers you, see this question for ways of creating a new type that may contain a value of type A or B (or C, or …) and provides a type-safe way of taking actions depending on what the type of the value actually is. I especially like Joey’s answer for its use of named parameters and lambdas. You could for example make collections that hold Either<IDevice, INewDevice> and then you can use the Match method to avoid any casting. E.g.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>IList<Either<IDevice, INewDevice>> devices = // get a list of devices
foreach (var device in devices) {
device.Match(
Left: oldDevice => // things to do if it's an old device
Right: newDevice => // things to do if it's a new device
);
}
</code>
<code>IList<Either<IDevice, INewDevice>> devices = // get a list of devices foreach (var device in devices) { device.Match( Left: oldDevice => // things to do if it's an old device Right: newDevice => // things to do if it's a new device ); } </code>
IList<Either<IDevice, INewDevice>> devices = // get a list of devices
foreach (var device in devices) {
    device.Match(
        Left: oldDevice => // things to do if it's an old device
        Right: newDevice => // things to do if it's a new device
    );
}

There’s no risk of doing a wrong cast using this approach.

It depends a lot, really (now that’s a surprise).
Here are a few things to consider:

What is the contract of the interface?
If the interface should allow performA and getB, but device2 requires checkC to be called, then it does not adhere to the interface. If the interface defines that C must be checkable, then device1 does not adhere. If your algorithm uses that check, it might be the latter.

Can you define a reasonable default behavior?
If it’s just a check you could always return true.

Is it an optional operation?
Define it to be optional. The interface might specify that throwing an exception is allowed. Add another method CanCheckC that returns whether the operation is supported. Or add TryCheckC that returns true iff a check was performed and returns the result in an out-parameter.

Is it a new type of interface?
If, in general, devices can’t check C but some class of device can, maybe this is a new interface. So add a new interface IDeviceWithCCheck that provides the additional operations and use type checks in your algorithm before you cast.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật