How do I model this relationship so that it is valid by construction?

Imagine a device class that represents a physical real world mobile device, with fields like Enabled, Platform, Model IMEI, etc. Then, an operation class, which is something that needs to be done to a certain device, like DeviceEnablementOperation or LockOperation. This is a ‘one device to many operations’ relationship, and an operation cannot exist without being related to an existing device. Can I model this in such a way that the system will not allow certain types of operations to devices with certain platforms? For example, an Android device would not allow a DeviceEnablementOperation attached to it, while a Windows Phone device would.

Our current modeling started out considering only Android devices. At the time, we created the concept of operations as a poor man’s event source, so that we could track what was done against a certain device via our API. Later on, we started supporting iOS devices too, and we introduced the Platform field using an enumeration class to differentiate them, like this:

public enum PlatformType : short
{
    /// <summary>
    /// Device is an Android.
    /// </summary>
    Android = 0,

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

    //// Can not place the name iOS.

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

At that point in time, the requirements were being met correctly, but now we need to restrict certain types of operations to certain platforms. The current model does not automatically restrict this, so the current implementation considers a hardcoded list of supported operations for each platform, and uses this to reject the creation of some operations to certain devices.

What I’d like to do is change the modelling so that this restriction is more robustly enforced. I thought about using inheritance and creating AndroidDevice, IosDevice and WindowsPhoneDevice classes, but I still fail to see how I could restrict the relationship with operations themselves to cover this requirement. A device has operations, but not all devices support all operations.

The modelling for the operations themselves is very straightforward inheritance, like this:

public abstract class Operation
{
    protected Operation()
    {
        this.Logs = new List<OperationLog>();
    }

    public DateTime? CreationDate { get; set; }

    public virtual Device Device { get; set; }

    public int DeviceId { get; set; }

    public OperationStatus GeneralStatus { get; set; }

    public int Id { get; set; }

    public DateTime? LastUpdate { get; set; }

    public ICollection<OperationLog> Logs { get; internal set; }

    public DateTime? ReceiveDate { get; set; }
}

And then derived operation types, like the ones I mentioned:

public sealed class LockOperation : Operation
{
    /// <summary>
    /// Gets or sets a value indicating whether the device must be locked or not.
    /// </summary>
    public bool Lock { get; set; }

    /// <summary>
    /// Gets or sets the device's lock password.
    /// </summary>
    public string Password { get; set; }
}

and

public sealed class EnablementOperation : Operation
{
    /// <summary>
    /// Gets or sets a value indicating whether the device should be enabled or disabled.
    /// </summary>
    public bool Enabled { get; set; }
}

Is it possible to model this to better reflect the new requirement that I want? I’d somehow need a way to tell if a given operation is supported for a given device, and only allow the relationship to exist if the operation was supported.

I suppose I could try something using generics and inheritance on both the device and operation classes, for example:

public abstract class Device<TOperation>
{
    public ICollection<TOperation> Operations { get; set; }
    ...
}

public sealed class AndroidDevice : Device<AndroidOperation> {...}

public abstract class Operation {...}

public abstract class AndroidOperation {...}

public sealed LockOperation : AndroidOperation {...}

But this particular approach would need multiple inheritance to work, and since I’m using C# it is not possible. I could then try using interfaces to differentiate operations, but them I suspect it would be a problem when mapping this to a database later using an ORM.

I also can’t really break everything into their own completely isolated classes, because I need the general concept of “devices that have operations”, regardless of platform or operation type. That’s why the base classes exist in our current model.

4

If you worry about ORM persistence, then here’s a possible solution:

Each operation declares its supported platform, similar to interface approach but use flags property instead (please note I change your enum to flags and add an option for unknown type)

[Flags]
public enum PlatformType : short
{
    Unknown = 0,
    /// <summary>
    /// Device is an Android.
    /// </summary>
    Android = 1,

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

    //// Can not place the name iOS.

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

public abstract class Operation
{
    public string Name { get; set; }
    public abstract PlatformType SupportedPlatforms { get; }

    //Other properties
}

public sealed class DeviceEnablementOperation : Operation
{
    public override PlatformType SupportedPlatforms
    {
        get
        {
            return PlatformType.Windows | PlatformType.Ios;
        }
    }

    //Other properties
}

The abstract devices class will contain list of operations and ensure to accept only operations it supports (in constructor and AddOperation() method). It will throw exception immediately when violated so that developer can recognize quickly:

public abstract class Device
{
    public PlatformType Platform { get; private set; }
    private ICollection<Operation> _operations;

    protected Device(PlatformType platform)
    {
        Platform = platform;
        _operations = new List<Operation>();
    }

    protected Device(PlatformType platform, ICollection<Operation> operations)
    {
        Platform = platform;

        var violatedOperation = operations.FirstOrDefault(o => !OperationSupportedByThisDevice(o));
        if(violatedOperation != null)
        {
            throw new ApplicationException(string.Format("Operation {0} not supported by platform {1}", violatedOperation.Name, Platform));
        }

        _operations = operations;
    }

    public void AddOperation(Operation operation)
    {
        if(!OperationSupportedByThisDevice(operation))
        {
            throw new ApplicationException(string.Format("Operation {0} not supported by platform {1}", operation.Name, Platform));
        }

        _operations.Add(operation);
    }

    private bool OperationSupportedByThisDevice(Operation operation)
    {
        return operation.SupportedPlatforms.HasFlag(Platform);
    }
}

public class AndroidDevice : Device
{
    public AndroidDevice() : base(PlatformType.Android)
    {
    }

    public AndroidDevice(ICollection<Operation> operations) : base(PlatformType.Android, operations)
    {
    }
}

So if an android device is created like this, it will throw exception since DeviceEnablementOperation is not supported by android device:

var androidDevice = new AndroidDevice(new List<Operation> { new DeviceEnablementOperation() });

If you worry about the flags usage for PlatformType for device (.i.e. an device can be mistakenly assigned with multiple platforms), then create a separate flags enum for operation, but you get the idea

5

How about adding interfaces for platform specific Operations? ie.

public interface IAndroidOperation : IOperation {}
public interface IIoSOperation : IOperation {}

Then have the operations implement the interfaces they are good for

public class LockOperation : IAndroidOperation, IIoSOperation {}

Then have the device only allow adding of the correct interface

public class AndroidDevice : Device
{
    public List<IAndroidOperation> Operations {get;,set;}
}

Update:

you are right the exposure of the generic Operations is non trivial. There are a number of approaches. I have included a simple one below, which is not perhaps ideal. but does avoid having to make your own IEnumberable

using System.Linq;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestProject2
{
    public interface IOperation 
    {
        void DoStuff();
    }
    public interface IAndroidOperation : IOperation { }
    public interface IoSOperation : IOperation { }

    public class LockOperation : IAndroidOperation
    {
        public void DoStuff(){}
    }

    public class BendOperation : IoSOperation
    {
        public void DoStuff() { }
    }
    public interface IDevice
    {
        IEnumerable<IOperation> GetOperations();
    }
    public abstract class Device<T> : IDevice where T : IOperation
    {
        public abstract List<T> Operations { get; set; }

        public IEnumerable<IOperation> GetOperations()
        {
            return this.Operations.Select(o => (IOperation)o);
        }
    }

    public class IoSDevice : Device<IoSOperation>
    {
        public override List<IoSOperation> Operations { get; set; }
    }

    public class AndroidDevice : Device<IAndroidOperation>
    {
        public override List<IAndroidOperation> Operations { get; set; }
    }

    public class TestMe
    {
        [TestMethod]
        public void test()
        {
            List<IDevice> devices = new List<IDevice>();
            AndroidDevice a = new AndroidDevice();
            IoSDevice i = new IoSDevice();

            devices.Add(a);
            devices.Add(i);

            LockOperation l = new LockOperation();
            BendOperation b = new BendOperation();

            a.Operations = new List<IAndroidOperation>();
            i.Operations = new List<IoSOperation>();

            a.Operations.Add(l); //ok
            //a.Operations.Add(b); //compilation error
            i.Operations.Add(b); // ok

            foreach (IDevice d in devices)
            {
                foreach (IOperation o in d.GetOperations())
                {
                    o.DoStuff();
                }
            }

        }
    }
}

6

I think interfaces in the form of the wrapper pattern are the way to go.

If you have an IPlatform interface and IAndroid, IOS (pun intended), etc. that extend it, you can write operations as wrapper objects following the wrapepr pattern.

Disclaimer: this is pseudo code, it’s been too long since I did C#, you get the idea

Here’s an operation that works on all devices.

class BreakDisplayOperation implements IPlatform {

BreakDisplayOperation (IPlatform platform) { // this is the  constructor

One that only works on IWindows as in your .

class DeviceEnablementOperation implements IWindows {

DeviceEnablementOperation (IWindows platform) { // constructor

The idea behind this is to delegate all method calls of the interface to the object received in the constructor, except for those that you want to manipulate by the operation.

IWindows (or IPlatform) probably includes a getter method for enable

bool isEnabled(); // or do this via get keyword, which C# has, if I remember correctly.

DeviceEnablementOperation overrides this method an returns a value it defines itself, instead of receiving that value by delegating the call to the wrapped IWindows object, like so

class DeviceEnablementOperation implements IWindows {

bool isEnabled()
{
    return true;
}

}

again, other things that are not manipulated by the operation are just delegated to the wrapepd object (assuming a member with same name as the constructor parameter:

class DeviceEnablementOperation implements IWindows {

    bool isEnabled() // modified
    {
        return true;
    }

    IMEI getIMEI() // delegated
    {
        return platform.getIMEI()
    }
}

In the above examples, you’d have to do the delegation for all Operations again and again. To avoid this boilerplate, create super classes that do all delegations, then each specific Operation can override methods as necessary.


This all works because the interface inheritance branches, making individual platforms incompatible types because they are siblings in the hierarchy.

I know this abstracts the problem in a different way as you are currently doing it. It’s a bit like Platform, Device and Operation all merge into the same thing.

You can apply many operations by wrapping the wrappers.


To get a list of all operations, add the getOperations(); method.

The super class of all Platforms will return a new empty list object. The super class of all Operations will again delegate that call to the wrapped object (which will eventually be a Platform), add itself to the list and return the list. (for possible other wrappers to ad themselves)


The problems of this approach:

  • somebody could implement both interface to merge them, breakign the separation
  • an IPlatform wrapper will be of that type, to use a specific wrapper again, a cast is necessary. You can work around this by duplicating the general purpose wrappers specifically typed for each platform. Not sure if generics could help you with this, but I have the feeling they do.

3

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