Adam B Adam B - 7 months ago 10
Java Question

Design issue: need instances of an enum member as (property, value) pair

This question is a spinoff of my previously answered question. Use of Enum when members are the same as another class's public variables. In implementing the answer provided, I was faced with another design problem.

I am writing a program to simulate evolution. I have an enum that stores a list of possible properties that a protein can code for. Proteins are created from a string of amino acids (i.e. "W", "A", etc). Right now I have a Dictionary that links a Property (enum member) with a character. Similarly, I have the same setup for the Type of protein.

public class Protein {
Property property;
float value;
Type type;

enum Property {
SIZE,
ID,
}
enum Type {
STRUCTURAL,
NEURAL,
}


static Dictionary<string, Property> aminoToProperty
= new Dictionary<string, Property> {
{"F", Property.SIZE},
{"K", Property.ID},
};

static Dictionary<string, Type> aminoToType
= new Dictionary<string, Property> {
{"A", Type.STRUCTURAL},
{"B", Type.NEURAL},
};


}


From another class I am creating proteins based on a long string of characters, then combining them to form structures. Several proteins can add up to create all sorts of properties and values for the structure.

Structure structure = new Structure();
Protein protein = new Protein (aminos) // didn't show this method for simplicity
switch (protein.property) {

case Protein.Property.SIZE:
structure.size += protein.value;
break;

ONE FOR EVERY PROPERTY...

}

SAME THING FOR EVERY TYPE...


My problems:

1) when I want to add a new property to the mix, i need to first add an entry into the enum, then into the dictionary. This makes me think I am violating some kind of best practices.

2) I need to keep a case in my switch for every single property and type, is this unavoidable?

3) Is there a way that I can guarantee that in the two dictionaries, I don't accidentally use the same letter twice? Does it make sense to even have two dictionaries?

4) Perhaps I am approaching this problem completely the wrong way.

Note: In the older linked question I had stored values along with the enums. But here I need to have multiple instances with differing values, so I don't think that solution applies.

Sorry if this is confusing but I'm really stuck on how to best design this to make it future proof. I am writing in c# but I think this question applies to most OO programming languages.

Answer

First off, forgive me if I'm not familiar with the problem domain; my example may reflect that.

I think you're instincts serve you well as the enum solution seems to violate the open/closed principle.

Another way to solve this problem without the need for touching multiple places when adding a new protein property and type is to make ProteinType and ProteinProperty abstract base classes that accept a Structure and operate on it.

The Protein can then aggregate both of the types, accept a Structure and delegate the Structure to the ProteinType and ProteinProperty instances so that the Structure can be modified by the most derived types.

Another thing the can be automated is the lookup of an amino to a ProteinType and ProteinProperty using reflection.

This all may seem like overkill, but the end result is that you are now free to define ProteinType and ProteinProperty classes in isolation without having to worry about modifying other areas of the code.

Here's an example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            var structure = new Structure();
            var protein = new Protein("F", "B", 0.5F);
            protien.ApplyToStructure(structure);
        }
    }

    public class Protein
    {
        private readonly float _value;
        private readonly ProteinProperty _proteinProperty;
        private readonly ProteinType _proteinType;

        public Protein(string propertyAmino, string typeAmino, float value)
        {
            _proteinProperty = ProteinProperty.FromAmino(propertyAmino);
            _proteinType = ProteinType.FromAmino(typeAmino);
            _value = value;
        }

        public void ApplyToStructure(Structure structure)
        {
            _proteinProperty.ApplyToStructure(structure, _value);
            _proteinType.ApplyToStructure(structure, _value);
        }
    }

    public class Structure
    {
        public float Size { get; set; }
        public float Id { get; set; }
    }

    public abstract class ProteinType
    {
        private static readonly Dictionary<string, ProteinType> AllProteinTypes = typeof (ProteinType)
            .GetNestedTypes(BindingFlags.Instance | BindingFlags.NonPublic)
            .Where(nestedType => typeof (ProteinType).IsAssignableFrom(nestedType))
            .Select(nestedType => (ProteinType) Activator.CreateInstance(nestedType))
            .ToDictionary(proteinType => proteinType.Amino);

        public static ProteinType FromAmino(string amino)
        {
            ProteinType proteinType;
            if (!AllProteinTypes.TryGetValue(amino, out proteinType))
            {
                throw new ArgumentException("Invalid amino");
            }

            return proteinType;
        }

        public abstract string Amino { get; }

        public abstract void ApplyToStructure(Structure structure, float value);

        private sealed class StructuralProteinType : ProteinType
        {
            public override string Amino { get; } = "A";

            public override void ApplyToStructure(Structure structure, float value)
            {
                // do what you need to do to Structure
            }
        }

        private sealed class NeuralProteinType : ProteinType
        {
            public override string Amino { get; } = "B";

            public override void ApplyToStructure(Structure structure, float value)
            {
                // do what you need to do to Structure
            }
        }
    }

    public abstract class ProteinProperty
    {
        private static readonly Dictionary<string, ProteinProperty> AllProteinProperties = typeof(ProteinProperty)
            .GetNestedTypes(BindingFlags.Instance | BindingFlags.NonPublic)
            .Where(nestedType => typeof(ProteinProperty).IsAssignableFrom(nestedType))
            .Select(nestedType => (ProteinProperty)Activator.CreateInstance(nestedType))
            .ToDictionary(proteinProperty => proteinProperty.Amino);

        public static ProteinProperty FromAmino(string amino)
        {
            ProteinProperty proteinProperty;
            if (!AllProteinProperties.TryGetValue(amino, out proteinProperty))
            {
                throw new ArgumentException("Invalid amino");
            }

            return proteinProperty;
        }

        public abstract string Amino { get; }

        public abstract void ApplyToStructure(Structure structure, float value);

        private sealed class SizeProteinProperty : ProteinProperty
        {
            public override string Amino { get; } = "F";

            public override void ApplyToStructure(Structure structure, float value)
            {
                structure.Size += value;
            }
        }

        private sealed class IdProteinProperty : ProteinProperty
        {
            public override string Amino { get; } = "K";

            public override void ApplyToStructure(Structure structure, float value)
            {
                // do what you need to do to Structure
            }
        }
    }
}

Edit

After reading your question again and trying to wrap my head around the problem domain, I wondering if there's even a distinction between ProteinProperty and ProteinType...seems they are just representations of an amino character that has an effect on the Structure. Also you indicated that aminos are a string of characters.

If this is true, this is how I would replace ProteinProperty and ProtienType with a single base clase: Amino. Additionally, you can have the Amino base class' ApplyToStructure() method take the entire Protein instance so you can access other stuff on it if you need to in the derived Amino classes.

Finally, you can create a collection of Amino instances based on an amino string and apply them to the Structure in a loop.

Here's the updated example:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            var structure = new Structure();
            var protein = new Protein(0.5F, "FKABAKB");
            protien.ApplyToStructure(structure);
        }
    }

    public class Protein
    {
        private readonly ReadOnlyCollection<Amino> _aminos;

        public Protein(float value, string aminos)
        {
            Value = value;
            _aminos = aminos
                .Select(Amino.FromAminoCharater)
                .ToList()
                .AsReadOnly();
        }

        public float Value { get; }

        public void ApplyToStructure(Structure structure)
        {
            foreach (var amino in _aminos)
            {
                amino.ApplyToStructure(structure, this);
            }
        }
    }

    public class Structure
    {
        public float Size { get; set; }
        public float Id { get; set; }
    }

    public abstract class Amino
    {
        private static readonly Dictionary<char, Amino> AllProteinProperties = typeof(Amino)
            .GetNestedTypes(BindingFlags.Instance | BindingFlags.NonPublic)
            .Where(nestedType => typeof(Amino).IsAssignableFrom(nestedType))
            .Select(nestedType => (Amino)Activator.CreateInstance(nestedType))
            .ToDictionary(proteinProperty => proteinProperty.AminoCharacter);

        public static Amino FromAminoCharater(char aminoCharacter)
        {
            Amino amino;
            if (!AllProteinProperties.TryGetValue(aminoCharacter, out amino))
            {
                throw new ArgumentException("Invalid amino");
            }

            return amino;
        }

        public abstract char AminoCharacter { get; }

        public abstract void ApplyToStructure(Structure structure, Protein protein);

        private sealed class SizeProperty : Amino
        {
            public override char AminoCharacter { get; } = 'F';

            public override void ApplyToStructure(Structure structure, Protein protein)
            {
                structure.Size += protein.Value;
            }
        }

        private sealed class IdProperty : Amino
        {
            public override char AminoCharacter { get; } = 'K';

            public override void ApplyToStructure(Structure structure, Protein protein)
            {
                // do what you need to do to Structure
            }
        }

        private sealed class StructuralType : Amino
        {
            public override char AminoCharacter { get; } = 'A';

            public override void ApplyToStructure(Structure structure, Protein protein)
            {
                // do what you need to do to Structure
            }
        }

        private sealed class NeuralType : Amino
        {
            public override char AminoCharacter { get; } = 'B';

            public override void ApplyToStructure(Structure structure, Protein protein)
            {
                // do what you need to do to Structure
            }
        }
    }
}

Also note this addresses the concern of accidentally introducing duplicates. If you define an Amino class with a letter already taken up by another, your app will crash and burn immediately instead of having a subtle bug.