Should I use inheritance to differentiate objects even if they have the same fields?

Consider this simple class that models a real world mobile device:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>/// <summary>
/// Model that represents a device.
/// </summary>
public class Device
{
public DateTime CreationDate { get; set; }
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the device's hardware identifier.
/// </summary>
/// <remarks>Commonly filled with data like device's IMEI.</remarks>
public string HardwareId { get; set; }
public int Id { get; set; }
public DateTime? LastCommunication { get; set; }
}
</code>
<code>/// <summary> /// Model that represents a device. /// </summary> public class Device { public DateTime CreationDate { get; set; } public bool Enabled { get; set; } /// <summary> /// Gets or sets the device's hardware identifier. /// </summary> /// <remarks>Commonly filled with data like device's IMEI.</remarks> public string HardwareId { get; set; } public int Id { get; set; } public DateTime? LastCommunication { get; set; } } </code>
/// <summary>
///     Model that represents a device.
/// </summary>
public class Device
{
    public DateTime CreationDate { get; set; }

    public bool Enabled { get; set; }

    /// <summary>
    ///     Gets or sets the device's hardware identifier.
    /// </summary>
    /// <remarks>Commonly filled with data like device's IMEI.</remarks>
    public string HardwareId { get; set; }

    public int Id { get; set; }

    public DateTime? LastCommunication { get; set; }
}

Now suppose a new requirement arrived and I need to start differentiating a device by platform, like Windows Phone, Android and iOS. The most common option I think would be to create an enumeration class:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>/// <summary>
/// Defines a type of device.
/// </summary>
enum PlatformType
{
/// <summary>
/// Device is an Android.
/// </summary>
Android,
/// <summary>
/// Device is an Windows platform.
/// </summary>
Windows,
/// <summary>
/// Device is an iOS platform.
/// </summary>
Ios,
}
</code>
<code>/// <summary> /// Defines a type of device. /// </summary> enum PlatformType { /// <summary> /// Device is an Android. /// </summary> Android, /// <summary> /// Device is an Windows platform. /// </summary> Windows, /// <summary> /// Device is an iOS platform. /// </summary> Ios, } </code>
/// <summary>
///     Defines a type of device.
/// </summary>
enum PlatformType
{
    /// <summary>
    ///     Device is an Android.
    /// </summary>
    Android,

    /// <summary>
    ///     Device is an Windows platform.
    /// </summary>
    Windows,

    /// <summary>
    ///     Device is an iOS platform.
    /// </summary>
    Ios,
}

and then add a new property to the device class:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class Device
{
...
public PlatformType Platform { get; set; }
...
}
</code>
<code>public class Device { ... public PlatformType Platform { get; set; } ... } </code>
public class Device
{
    ...

    public PlatformType Platform { get; set; }

    ...
}

Another approach would be to create a inheritance hierarchy on the device class itself:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class AndroidDevice : Device { }
public class IosDevice : Device { }
public class WindowsPhoneDevice : Device { }
</code>
<code>public class AndroidDevice : Device { } public class IosDevice : Device { } public class WindowsPhoneDevice : Device { } </code>
public class AndroidDevice : Device { }

public class IosDevice : Device { }

public class WindowsPhoneDevice : Device { }

When should I choose one of those approaches over the other? Should I always choose the second one or is there a reason not to?

2

In general I prefer composition over inheritance. That has several reasons:

  • Humans are bad at dealing with complexity. And dealing with high inheritance trees is complexity. I want light structures on my brain, which I could overlook easily. The same goes for dozens of types even derived from one base class.

  • You are able to switch moving parts out. You could have a simple device, which does runOperatingSystem(), instead of making two different device-classes, you make only one and give it an Operating System. If you want to test behavior, you could inject a mockOperatingSystem and see, if it does, what it should.

  • You are able to extend behaviour, simply by injecting more behavioral components.

In terms of abstraction, you are better off, designing a generic device type:
In Python the design would look like the following

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>class Device:
def __init__(self, OS, name):
self.OS=OS
self.name=name
def browseInterNet(self):
self.OS.browseInterNet()
class Android:
def browseInterNet(self):
print("I'm browsing")
</code>
<code>class Device: def __init__(self, OS, name): self.OS=OS self.name=name def browseInterNet(self): self.OS.browseInterNet() class Android: def browseInterNet(self): print("I'm browsing") </code>
class Device:
    def __init__(self, OS, name):
        self.OS=OS
        self.name=name
    def browseInterNet(self):
        self.OS.browseInterNet()

class Android:
    def browseInterNet(self):
        print("I'm browsing")

You have a generic device which runs an operating system. Every userinteraction is delegated to this OS. Perhaps you want to test only the browsing call dependent on any operating systen, you could easily swap it out.

The next step for this design would be, to create a configuration-object, which takes the common parameters (CreationDate, HardwareId and so on). Inject this configuration and the appropriate OS via constructor injection and you are done.

You define common behaviour in a contract (interface), which determines, what you could do with a phone and the operation system deals with the implementation.

Translated to everyday language: If you text with your phone, there is no difference in doing it with an iPhone, Android or Windows in that respect, that you are texting, although the mechanisms from OS to OS differ. How they deal technically with it is uninteresting for the device. The OS runs the texting app, which itself takes care of its implementation. It is all a question of abstracting commonalities.

On the other hand: this is only one way of doing it. It depends on you and your model of the domain.


From the comments:

But you have to agree with me that this would require one to create a lot of wrapper methods to make the API simpler

This depends on what exactly you want to model. To extend the given example of texting:

Say, you simply have some basic jobs, you want the device to do, you define an API for that; in our case simply the method sendSMS(text).You have then the Device, where the message "send text" is called upon. The device in turn delegates that call to the used operating system, which does the actual sending.

If you want to model more than a handful of services your device offers, you have to make bloated API.

This is a sign, that your abstraction level is too low.

The next step would be to abstract the concept of an app out of the system.

In principle, you have a device which interacts with the inner logic of an app, which runs on an operating system. You have input, which is processed and changes the display of the device.

In terms of MVC, e.g. your keyboard is the controller, the display is the view and there is the model within the app, which is modified. And since the view observes the model it reflects any changes.

With such an abstract concept, you are very flexible to build / model a lot of use cases.

I also am not seeing exactly how you would handle the operations concept here. There are still many types of operations, each with differing parameters, that can or can’t be applied to a given device.

As said above:it is all a matter of abstraction. The more power you want, the more abstract your model has to be.

Where would the OS be located now after this abstraction?

Taking the example further, you have to develop several abstractions / patterns, which help in this case.

  • Mediator-Pattern: The operating system acts as a mediator, i.e. it takes signals in form of commands, sends it to the app and takes in response commands to e.g. update the view

  • Command-Pattern: The command pattern is the form of abstraction, which is used to describe the communication flow between components. Say, the user presses A, than this could be abstracted as Keypressed-command with a value of A. Another command would be update display with the value of keypress.value or in this case A.

  • MVC The display as the view, the keyboard as the controller and in between the (app-)model.

Your imagination is your limit.

This is the kind of stuff, OOP was invented for originally: simulating independent components and their interaction

4

I would make this choice based on how much of the implementation is shared between platforms.

If 99% of the code is the same on all platforms, then have a private enum, and in the one or two places where it matters just use a switch(Platform) block and you’re done.

But if you have to make dozens of platform-dependent system calls inside this class, each with different parameters and semantics and error handling conventions, then you’re better off with completely separate classes that merely inherit from a common interface.

The example interface you’ve shown would almost certainly end up in the latter category.

6

The OOP answer is to do the inheritance. I guess the reasoning is to avoid conditional blocks of code such as :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>if(type==PlatformType.Android) { ..}
else ....
</code>
<code>if(type==PlatformType.Android) { ..} else .... </code>
if(type==PlatformType.Android) { ..}
else ....

Which can be replaced with overridden methods on the specific class

However if you just have a data struct with no logic, as may be the case if you have no requirement for extra fields, or are passing to a DB or service. I think the type property is ok.

5

If there is a lot of “common code” (or abstractions that all platforms make use of), another option is composition, rather than inheritance. I believe that anything you can do with inheritance, you can also accomplish through composition (I’m probably going to regret saying “anything”). Inheritance is going to lock you into that base implementation, composition gives you a choice.

Using a contrived example, if you were going to implement a base class that had common functionality such as:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>class BaseFoo
{
public DoThis() {
}
public DoThat() {
}
}
</code>
<code>class BaseFoo { public DoThis() { } public DoThat() { } } </code>
class BaseFoo
{
    public DoThis() {
    }

    public DoThat() {
    }
}

And subclasses:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>class MyFoo : BaseFoo {
}
class YourFoo : BaseFoo {
}
</code>
<code>class MyFoo : BaseFoo { } class YourFoo : BaseFoo { } </code>
class MyFoo : BaseFoo {
}

class YourFoo : BaseFoo {
}

Those subclasses are locked into the behavior of the base class. (Yes, you can make methods virtual and override their behavior, but, for the sake of argument, let’s say that what the base methods do is extremely complex and you don’t want to/can’t re-implement it.)

If you instead had each implement the same interface (representing the Foo abstraction) and composed the classes, they are free do implement the abstraction how they see fit:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>interface IFoo
{
void DoThis();
void DoThat();
}
class MyFoo : IFoo {
private readonly IThisStrategy _thisStrategy;
private readonly IThatStrategy _thatStrategy;
public MyFoo(IThisStrategy thisStrategy, IThatStrategy thatStrategy) {
_thisStrategy = thisStrategy;
_thatStrategy = thatStrategy;
}
public void DoThis() {
_thisStrategy();
}
}
</code>
<code>interface IFoo { void DoThis(); void DoThat(); } class MyFoo : IFoo { private readonly IThisStrategy _thisStrategy; private readonly IThatStrategy _thatStrategy; public MyFoo(IThisStrategy thisStrategy, IThatStrategy thatStrategy) { _thisStrategy = thisStrategy; _thatStrategy = thatStrategy; } public void DoThis() { _thisStrategy(); } } </code>
interface IFoo
{
    void DoThis();
    void DoThat();
}

class MyFoo : IFoo {
    private readonly IThisStrategy _thisStrategy;
    private readonly IThatStrategy _thatStrategy;

    public MyFoo(IThisStrategy thisStrategy, IThatStrategy thatStrategy) {
        _thisStrategy = thisStrategy;
        _thatStrategy = thatStrategy;
    }

    public void DoThis() {
        _thisStrategy();
    }
}

It may take a bit more work to implement the solution through composition, but the benefits include decoupling, flexibility, and reusable components (the strategy implementations). This also has testing benefits as you can unit test individual behaviors that you’re injecting without having to test the object at large. And this also avoids polluting your code with a bunch of switch statements everywhere that later cause a maintenance headache should you ever need to support a new platform or change that platform’s behavior.

I’m a firm believer in composition over inheritance in a great many cases, especially when inheritance is not being used to actually extend a functional base.

Actually none of us can tell you the best way to implement your device type, since we don’t know how you’re going to (re)use your code. The problem is, that some devs will say, that you should use Device as interface, AbstractDeviceImpl as abstract class and Android, etc. as concrete class which would be nice, but do you need it?

I would say the best approach is to find it out on your own by using TDD. Use your code, see what happens. Try to find out your requirements, reading your tests. Do you want it to be prepared for OCP and very abstract? Or will there be no other devices so simply one device abstraction and 3 concrete classes are your friends?

As I think that there won’t be more than those 3 devices, and assume that you only need the type for logging/debugging, I would create a class Device implementing type as read-only string unknown as default. Then create 3 inner classes inheriting Device overriding the type-getter with the corresponding name.

As a matter of fact, using an enum in that way you described it above, is mostly bad OCP violation ending in switch blocks and/or if statements. Adding a new device means, changing the enum and adding a class. That’s actually not what an OOP wants. You can always use enums if you need the value itself, but to distinguish between execution paths, enum with more than two elements might produce ugly code.

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