Darf Zon
Darf Zon

Reputation: 6378

How to maintain a communication between two classes?

for sure this is not the best title. I'm creating a system to generate math problems. The developer must implement two interfaces:

These are the interfaces:

public abstract class Problem
{
}

public abstract class Configuration
{
}

enter image description here

And here is one example for the BinaryProblem.

public class BinaryProblem : Problem
{
    public decimal X { get; set; }
    public decimal Y { get; set; }

    public BinaryProblem(decimal x, decimal y)
    {
        this.X = x;  // Number 1
        this.Y = y;  // Number 2
    }
}

public class BinaryProblemConfiguration : Configuration
{
    // Range for X
    public int XMin { get; set; }
    public int XMax { get; set; }

    // Range for Y
    public int YMin { get; set; }
    public int YMax { get; set; }

    public BinaryProblemConfiguration() { }
}

Can you see that line between Problem and Configuration? I need to put many Modules which implements those two interfaces.

So, I need a way to generate them. I was thinking in create an abstract class where contains:

And implement this abstract class in each problem type.

My question is: Is this a good way to start to solve this solution or what other solution do I have to do?

EDIT: One example which I was trying to is:

This is my abstract class, I mean my idea is when you instanciate this class, you specify the generic values:

public interface IProblemFactory
{
    Problem CreateProblem();
}

public abstract class ProblemBaseFactory<TProblem, TConfiguration> : IProblemFactory
    where TProblem : Problem
    where TConfiguration : Configuration
{
    private const int SEED = 100;
    protected TConfiguration _config;
    protected static Random _random;

    public ProblemBaseFactory(TConfiguration config)
    {
        _config = config;

        if (_random == null) _random = new Random(SEED);
    }

    public void SetSeed(int newSeed)
    {
        _random = new Random(newSeed);
    }

    public Problem CreateProblem()
    {
        return CreateProblem(_config);
    }

    public abstract TProblem CreateProblem(TConfiguration config);
}

public class BinaryProblemFactory : ProblemBaseFactory<BinaryProblem, BinaryProblemConfiguration>
{
    public override BinaryProblem CreateProblem(BinaryProblemConfiguration config)
    {
        var x = GenerateValueInRange(_config.Range1);
        var y = GenerateValueInRange(_config.Range2);
        return new BinaryProblem(x, y, Operators.Addition); 
    }

    private decimal GenerateValueInRange(Range<int> range)
    {
        return _random.Next(range.MinValue, range.MaxValue);
    } 
}

Upvotes: 0

Views: 1697

Answers (2)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236268

I think it's good to aggregate values that used together. In your case object Range will be useful. Something like (without verification min <= max):

public class Range<T>
{
    public Range(T min, T max)        
    {
        Min = min;
        Max = max;
    }

    public T Min { get; private set; }
    public T Max { get; private set; }
}

After that BinaryProblemConfiguration will look like this:

public class BinaryProblemConfiguration
{
    public Range<int> XRange { get; set; }
    public Range<int> YRange { get; set; }
}

What you actually implementing is products factory. So I Intermediary is not very descriptive name for it:

public interface IProblemFactory
{
    Problem CreateProblem();
}

Create factory for each type of products. Each factory knows what type of product it creates and what type of configuration it needs. E.g:

public class BinaryProblemFactory : IProblemFactory
{
    private BinaryProblemConfiguration _config;
    private Random _random;

    public BinaryProblemFactory(BinaryProblemConfiguration config)
    {
        _config = config;
        // or you can use const seed here
        _random = new Random(DateTime.Now.Millisecond); 
    }

    public override Problem CreateProblem()
    {
        var x = GenerateValueInRange(_config.XRange);
        var y = GenerateValueInRange(_config.YRange);
        return new BinaryProblem(x, y);
    }

    private decimal GenerateValueInRange(Range<int> range)
    {
        return _random.Next(range.Min, range.Max);
    }
}

Factory creation:

BinaryProblemConfiguration config = new BinaryProblemConfiguration();
config.XRange = new Range<int>(0, 100);
config.YRange = new Range<int>(-50, 50);

IProblemFactory problemFactory = new BinaryProblemFactory(config);

Pass created problem factory somewhere as IProblemFactory:

Problem problem = problemFactory.CreateProblem();

Also there no special need for configuration object. I think that creator of product should have knowledge how to create product. So I'd rather go with properties XRange and YRange added to factory:

 BinaryProblemFactory binaryProblemFactory = new BinaryProblemFactory();
 binaryProblemFactory.XRange = new Range<int>(0, 100);
 binaryProblemFactory.YRange = new Range<int>(-50, 50);

Upvotes: 2

walther
walther

Reputation: 13600

Not really sure, if I understood your question, but I'll try to give my opinion on this anyway. Hope it helps.

I believe you chose the wrong approach. - you have two classes, but they don't communicate at all (even though they should be) - yes, you've created abstract classes to derive from, but I don't see a reason in this particular case WHY. They don't define anything, they don't enforce any logic to deriving classes. When you create an interface, you want somebody, who will derive from it, to implement it. You can't really implement an empty class/interface.

Firstly, I'd suggest to fix this problem. Create interfaces that require certain methods or properties when you want to implement them. This way you should be able to develop your main application more easily, because you will know, that whatever problem some other developer creates, he will need to implement your interface with certain methods you can then use. If you allow to implement an empty interface, you can't be really sure, what the results will be and what to expect. You can't rely on them to behave nicely (or at least you shouldn't).

Secondly, not sure if you know it, but when you use modules (Problem + Config as a one module), place each in a separate .cs file. That way you can have virtually infinite number of them and still be able to maintain your app easily.

Thirdly, I'd suggest some relation (and communication) between these classes using some interface like this:

public interface IProblem
{
   IProblemConfiguration Config;

   // other needed methods or properties
}

Upvotes: 0

Related Questions