4

Currently, I'm working on a system that enables users to add Tag's to available TagTypes on specific pages. For instance, we would have a TagType called "Installer" and the user wants his name associated with it (his name then would be the Tag).

A requirement of this system is that administrators can add constraints to TagTypes. Once a Tag is created on an existing TagType, the containing constraint checks the Tag's value.

Our wish is to make it as easy as possible to implement new types of constraints in the future. For instance, we would have a RegularExpression-constraint, a List-constraint (the value of a Tag should be one of the values in a list), an int-range constraint, etcetera. What would be the best way of facilitating this need? Not only does the constraint need checking once a Tag has been created (or edited), also, the back-end for administrators creating new constraints needs to be adjustable in an easy manner.

We got to this point by implementing something bad:

class TagTypeConstraint{}
class TTConstraintList : TagTypeConstraint{}
class TTConstraintIntRange : TagTypeConstraint{}
class TTConstraintRegex : TagTypeConstraint{}

This already has the problem, besides the awful maintainability, that abstract methods in TagTypeConstraint are becoming useless as the inheriting classes want other information passed to it (one wants a string, the other one wants two ints, the last one wants a string again). Maybe a future constraint will want a list of integers. I'm thinking there needs to be a good level of abstraction for our back-end to be easily extendable but how to achieve that when methods of inheriting classes will be asking for different parameters?

Edit: I've made some progressions on how to tackle. My TagTypeConstraint class is now abstract, having abstract methods like + SetConstraint(object[] values) and + IsValid(object[] values) . These abstract methods get implemented by the inheriting classes and in those classes, the object[] variables are cast to appropriate types. If the casts fail, I will throw a ConstraintException. Also, I've added a static function to TagTypeConstraint that acts like a (simple) factory, removing the need to call the implementations of TagTypeConstraint in layers like the GUI. Any comments on this would be appreciated.

Current (updated) structure:

Tag

  • string _content;
  • TagType _tagType;

TagType

  • string _name;
  • TagTypeDataType _dataType;

TagTypeDataType

  • string _name;
  • Constraint _constraint;

abstract Constraint

  • static Constraint CreateConstraint(ConstraintType type)
  • abstract void SetConstraint(object[] values)
  • abstract bool IsValid(object[] values)
  • abstract ConstraintType GetConstraintType()

StringListConstraint

  • List _constraintValues;
  • ConstraintType _constraintType;
  • override void SetConstraint(object[] values)
  • override bool IsValid(object[] values)
  • override ConstraintType GetConstraintType()
  • abstract methods in TagTypeConstraint are becoming useless as the inheriting classes want other information passed to it (one wants a string, the other one wants two int's, the last one wants a string again). Contrary to what people will tell you, sometimes you do need divide instances of a type into different groups and treat each one differently. It's done very commonly in statically-typed functional languages but it's still done in traditional OOP using the Visitor pattern. The catch is that you'll have to update the code each time you add a new one... – Doval Oct 17 '14 at 11:24
  • ...but you already can't add a new TagType arbitrarily because if you allow both adding arbitrary TagTypes and arbitrary constraints, you can end up with TagTypes with conflicting constraints. So any time you add a new tag type you're already going to have to go back and look at the existing ones anyways. – Doval Oct 17 '14 at 11:26
  • @Doval: thanks for commenting. I'm not sure I follow why there would be conflicts. 1 TagType can have 1 Constraint at a time. – Adimeus Oct 17 '14 at 11:45
  • Suppose one tag says the user's name should be blue, and a different tag says the user's name should be red. When someone has both tags, which color is it? You can't add arbitrary tags unless the constraints can never conflict or you add some sort of precedence to take care of conflicts, which means you can't also add arbitrary constraints without looking at the rest of the system. Just because a design makes adding to the system easy (e.g. add a new subclass), it doesn't mean you can add new things blindly. The tags and constraints need to be coherent as a whole. – Doval Oct 17 '14 at 11:54
  • I think your first method you showed, before your progressions, was the best. I think you are vastly underestimating the power in simplicity here. I think that method is actually the most maintainable. – Ben Lee Oct 21 '14 at 2:44

Your Answer

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

Browse other questions tagged or ask your own question.