2

What is the best practice for specifying which classes are allowed to be stored in a class property? For example if I had a DTO with a

  • property that should allow ClassA, ClassB, ClassC,
  • another property that allows ClassC, ClassD, ClassE,
  • another property that allows ClassA, ClassC, ClassE,

how should I define these classes?

For example,

public class MyDto
{
    public Vehicle Vehicle { get; set; }
    public Machine Machine { get; set; }
}

[Vehicle]
[Machine]
public class Car { }

[Vehicle]
[Machine]
public class Plane { }

[Machine]
public class Robot { }

UPDATE

If possible I'd like to avoid using marker interfaces since this is considered a code smell?

  • Correct me if my following understanding is not correct. Are Machine and Vehicle interfaces? Classes Car and Plane implement both of them and class Robot implements only Machine ? – Chetan Ranpariya Feb 21 '17 at 15:10
  • They're attributes, since empty interfaces are considered a code smell – Ernest Feb 21 '17 at 15:28
  • 1
    Why would you have an empty interface? You want the properties to be of certain type which does not have any behavior or properties? – Chetan Ranpariya Feb 21 '17 at 15:44
  • C# has the where generic type constraint. Note that the words "code smell" are really not a good enough reason to use or not use a particular technique. Evaluate each technique on its merits (or lack thereof). – Robert Harvey Feb 21 '17 at 15:47
  • Yeah, do ClassA, ClassB, and ClassC have something in common? If not, what's the point of having a property that can only contain instances of these three classes? If they do have something in common, why don't they all implement an interface which describes their common behavior? – Tanner Swett Feb 21 '17 at 15:53
3

You would define an interface which is implemented by the set of classes which you will allow, and then set the type of the property as that interface.

Presumably the classes in question have some interface in common. If they have nothing in common, then the real question is why you want to store unrelated objects in the same property.

Marker interfaces are indeed considered a code smell, but a code smell is (according to wikipedia):

any symptom in the source code of a program that possibly indicates a deeper problem

So the marker interface are not the problem you have to work around. Rather, they indicate you have a deeper problem, which is indicated by your need to store unrelated objects in the same slot.

  • An empty interface? Isn't that considered a code smell – Ernest Feb 21 '17 at 15:28
  • @Ernest: Presumably the classes in question have some interface in common, otherwise it would not make sense to store them in the same property in the first place. If they have nothing in common, then the smell is not the empty interfaces, but rather why you want to store unrelated objects in the same property. – JacquesB Feb 21 '17 at 15:57
  • The DTOs are used to generate JSON, some of the FHIR specifications I'm implementing contain properties which allow multiple sets of types but vary from one to another. But thank you, that makes sense, I'll need to revise how I need to structure these DTOs. – Ernest Feb 22 '17 at 17:41
0

There can be couple of approaches to this.

You can have interfaces created for Machine and Vehicle type and have property types accordingly in MyDto class.

public interface IMachine
{
    //properties and methods related to Machine
}

public interface IVehicle : IMachine
{
    //properties and methods specific to Vehicle.
}

public class Car : IVehicle
{
    // Implementation of all the members of IMachine and IVehicle
}

public class Plane : IVehicle
{
    // Implementation of all the members of IMachine and IVehicle
}

public class Robot : IMachine
{
    // Implementation of all the members of IMachine
}

public class MyDto
{
    public IVehicle Vehicle {get;set;}
    public IMachine Machine {get;set;}
}

This way Vehicle property of MyDto will only accept object of classes implementing interface IVehicle (means objects of classes Car and Plane) and property Machine will accept accept object of classes implementing interface IMachine (means objects of classes Robot and Car and Plane to as they also implement interfaceIMachine` indirectly).

Another approach would be to create base class VehicleBase for Vehicle implementing IVehicle1 interface and inherit it in Car and Plane. And change type of Vehicle property to VehicleBase.

public abstract class VehicleBase : IVehicle
{
    // Implement members of IVehicle and IMachine as abstract or virtual
}

public class Car : VehicleBase
{
    // Implement abstract members and override virtual members
}

public class Plane : VehicleBase
{
    // Implement abstract members and override virtual members
}

public class MyDto
{
    public VehicleBase Vehicle {get;set;}
    public IMachine {get;set;}
}
0

What you are looking for is commonly called a sum type.

Your first property would have the sum type ClassA + ClassB + ClassC, your second property ClassC + ClassD + ClassE, and your third property ClassA + ClassC + ClassE.

E.g. in Haskell, the types would look something like this:

data ABC = A ClassA | B ClassB | C ClassC
data CDE = C ClassC | D ClassD | E ClassE
data ACE = A ClassA | C ClassC | E ClassE

And in Rust, the types would look something like this:

enum ABC { A(ClassA), B(ClassB), C(ClassC) }
enum CDE { C(ClassC), D(ClassD), E(ClassE) }
enum ACE { A(ClassA), C(ClassC), E(ClassE) }

Swift would be similar:

enum ABC {
  case A(ClassA)
  case B(ClassB)
  case C(ClassC)
}

enum CDE {
  case C(ClassC)
  case D(ClassD)
  case E(ClassE)
}

enum ACE {
  case A(ClassA)
  case C(ClassC)
  case E(ClassE)
}

Scala doesn't natively support sum types, but you can easily emulate them using sealed inheritance hierarchies:

sealed trait ABC
object ABC {
  final case class A(a: ClassA) extends ABC
  final case class B(b: ClassB) extends ABC
  final case class C(c: ClassC) extends ABC
}

sealed trait CDE
object CDE {
  final case class C(c: ClassC) extends CDE
  final case class D(d: ClassD) extends CDE
  final case class E(e: ClassE) extends CDE
}

sealed trait ACE
object ACE {
  final case class A(a: ClassA) extends ACE
  final case class C(c: ClassC) extends ACE
  final case class E(e: ClassE) extends ACE
}

(You can abstract the boilerplate away using a macro.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.