Architectural problem where access to construction methods should be controlled

Asked

Viewed 117 times

8

I have a class for tree that will have the addition of trunks, branches, leaves and fruits.

I need the class to give access to certain methods only after others and that the previous ones cannot be accessed again.

Example:

public class Arvore 
{ 
    public List<Membro> Membros { get; set; }

    public Arvore AdicionarTronco()
    {
        Membros.Add(new Tronco());
        return this;
    }

    public Arvore AdicionarGalho()
    {
        Membros.Add(new Galho());
        return this;
    }

    public Arvore AdicionarFolha()
    {
        Membros.Add(new Folha());
        return this;
    }

    public Arvore AdicionarFruto()
    {
        Membros.Add(new Fruto());
        return this;
    }

    public void ImprimirArvore() { ... }
}

So the problem is that after the creation of Arvore, the only method that can be accessed is AdicionarTronco().

After AdicionarTronco(), only AdicionarGalho() may be accessed, and AdicionarTronco() can no longer be accessed.

Finally, AdicionarFolha() and AdicionarFruto() may be accessed, but may not access other methods.

I need to give the following example of functionality for the class:

(new Arvore())
    .AdicionarTronco()
    .AdicionarGalho()
    .AdicionarFolha()
    .AdicionarFruto()
    .ImprimirArvore();

For that I thought then of controlling the access to the methods by means of interfaces, and I thought:

public interface IArvore
{
    ITronco AdicionarTronco();
    void ImprimirArvore();
}

public interface ITronco
{
    IGalho AdicionarGalho();
}

public interface IGalho
{
    IGalho AdicionarFolha();
    IGalho AdicionarFruto();
}

Hence, make the class Arvore descend from the interfaces:

public class Arvore : IArvore, ITronco, IGalho
{
    public List<Membro> Membros { get; set; }

    public ITronco AdicionarTronco()
    {
        Membros.Add(new Tronco());
        return this;
    }

    public IGalho AdicionarGalho()
    {
        Membros.Add(new Galho());
        return this;
    }

    public IGalho AdicionarFolha()
    {
        Membros.Add(new Folha());
        return this;
    }

    public IGalho AdicionarFruto()
    {
        Membros.Add(new Fruto());
        return this;
    }

    public void ImprimirArvore() { ... }
}

But I still managed to solve little.
I managed to resolve the issue of not being able to return to the methods, but by Arvore I still have access to the methods AdicionarGalho(), AdicionarFolha() and AdicionarFruto().

Still, in the end I must have access to the method ImprimirArvore().

How can I fix this?

  • I imagine you’re not really modeling trees in C#. Do you mind giving details about the domain to which this solution would apply?

  • @ctgPi is just a college issue that I was having trouble understanding.

3 answers

4


This type of problem can be solved by Composite Design Pattern

Composite is a software design standard used to represent an object that is composed of objects similar to it. In this pattern, the composite object has a set of other objects that are in the same class hierarchy to which it belongs.

Let us begin by writing an abstract class that will serve as a basis for all members of the composite:

The constructor receives the name of this Member(Tree, Trunk, etc) and declares two virtual methods:

Addendum - Add members to this Member. At the lower levels of the composite an Exception is issued because it is not allowed to be added (Leaf and Fruit)

Print out - Prints this Member’s name

public abstract class Membro
{
    protected readonly string _nome ;

    protected Membro(string nome)
    {
        _nome = nome;
    }

    public virtual string Nome
    {
        get { return _nome; }
    }

    public virtual void AdicionarMembro(Membro membro)
    {
        throw new InvalidOperationException("Não podem ser adicionados membros ao membro " + Nome);
    }

    public virtual void Imprimir(int nivel)
    {
        Console.WriteLine(new String('-', nivel) + Nome);
    }
}

We need a class for Members who have members (Tree, Trunk and Branch). It will be an abstract class and will inherit from Member.

The class declares a list where the Members who compose it will be stored. The methods AdicionarMembro() and Imprimir() were rewritten, the first in order to add the new members to the list, the second in order to print also the names of the child members.

public abstract class MembroComposto : Membro
{
    protected IList<Membro> _membros;
    protected MembroComposto(string nome) : base(nome)
    {
        _membros = new List<Membro>();
    }

    public override void AdicionarMembro(Membro membro)
    {
        _membros.Add(membro);
    }

    public override void Imprimir(int nivel)
    {
        Console.WriteLine(new String('-', nivel) + Nome);
        foreach (var membro in _membros)
        {
            membro.Imprimir(nivel + 1);
        }
    }
}

These two classes are the basis for the implementation of Composite Design Pattern

We can now begin to write the concrete classes of the members of our composite.

Let’s start with the simple members: Leaf and Fruit

public class Folha : Membro
{
    public Folha() 
        : base("Folha")
    {
    }
}

public class Fruto : Membro
{
    public Fruto(string nome)
        :base(nome)
    {

    }
    public Fruto()
        : base("Fruto")
    {
    }
}  

They inherit from Member and simply pass their name to the base class.
In the case of Fruit, I added another builder, if you want to give a specific name to the fruit(Banana for example).

Missing to write the classes of members that are composite: Tree, Log and Twig

public class Arvore : MembroComposto
{
    public Arvore(string nome)
        :base(nome)
    {

    }
    public Arvore() : base("Arvore")
    {
    }
}

public class Tronco : MembroComposto
{
    public Tronco()
        : base("Tronco")
    {
    }
}

public class Galho : MembroComposto
{
    public Galho()
        : base("Galho")
    {
    }
}

They simply inherit from Membrocompost and rename it to the base class. To the Tree another constructor has been added to allow giving a specific name to the tree.

Having all the classes we will use them to compose our composite: an Apple tree.

static void Main(string[] args)
{
    //Criar algumas folhas
    Membro folha1 = new Folha();
    Membro folha2 = new Folha();
    Membro folha3 = new Folha();
    Membro folha4 = new Folha();

    //Criar algumas frutas
    Membro maca1 = new Fruto("maçã 1");
    Membro maca2 = new Fruto("maçã 2");
    Membro maca3 = new Fruto("maçã 3");
    Membro maca4 = new Fruto("maçã 4");
    Membro maca5 = new Fruto("maçã 5");

    //Criar dois galhos
    MembroComposto galho1 = new Galho();
    MembroComposto galho2 = new Galho();

    //Atribuir maçãs e folhas aos galhos
    galho1.AdicionarMembro(folha1);
    galho1.AdicionarMembro(folha2);
    galho1.AdicionarMembro(maca1);
    galho1.AdicionarMembro(maca2);
    galho1.AdicionarMembro(maca3);

    galho2.AdicionarMembro(folha3);
    galho2.AdicionarMembro(folha4);
    galho2.AdicionarMembro(maca4);
    galho2.AdicionarMembro(maca5);

    //Criar o tronco da maceira
    MembroComposto tronco = new Tronco();

    //Adicionar os galhos ao tronco
    tronco.AdicionarMembro(galho1);
    tronco.AdicionarMembro(galho2);

    //Criar a macieira
    MembroComposto macieira = new Arvore("Macieira");

    //Adicionar o tronco
    macieira.AdicionarMembro(tronco);

    //Imprimir a arvore
    maciera.Imprimir(1);
    Console.ReadKey();
}

Output:

-Apple tree
--Log
---Twig
----Leaf
----Leaf
----mace 1
----mace 2
----mace 3
---Twig
----Leaf
----Leaf
----mace 4
----apple 5

All this may not answer your question directly but, in my opinion, it is the right approach to this type of model.

  • Thank you very much! From your example I managed to solve the problem. Grateful!

4

I see two ways to force the execution order of class methods Arvore:

• Making methods private AdicionarTronco, AdicionarGalho, AdicionarFolha and AdicionarFruto and call them only in a public method Construir.

private ITronco AdicionarTronco()
{       
    ...
}

public IGalho AdicionarGalho()
{       
    ...
}

public IGalho AdicionarFolha()
{
    ...
}

public IGalho AdicionarFruto()
{
    ...
}

public void Construir()
{
    AdicionarTronco();
    AdicionarGalho();
    AdicionarFolha();
    AdicionarFruto();
}

Or

• For each method, create a Boolean-type private variable in the scope of the class that will indicate whether previous methods were called. For example:

public class Arvore
{
    public List<Membro> Membros { get; set; }
    private boolean adicionouTronco;
    private boolean adicionouGalho;
    private boolean adicionouFolha;
    private boolean adicionouFruto; 

    public ITronco AdicionarTronco()
    {       
        Membros.Add(new Tronco());
        adicionouTronco = true;
        return this;
    }

    public IGalho AdicionarGalho()
    {       
        if(!adicionouTronco)
            throw new InvalidOperationException("Adicione o tronco antes de adicionar o galho");

        Membros.Add(new Galho());
        adicionouGalho = true;
        return this;
    }

    public IGalho AdicionarFolha()
    {
        if(!adicionouTronco)
            throw new InvalidOperationException("Adicione o tronco antes de adicionar o galho");

        if(!adicionouGalho)
            throw new InvalidOperationException("Adicione o galho antes de adicionar a folha");

        Membros.Add(new Folha());
        adicionouFolha = true;
        return this;
    }

    public IGalho AdicionarFruto()
    {
        if(!adicionouTronco)
            throw new InvalidOperationException("Adicione o tronco antes de adicionar o galho");

        if(!adicionouGalho)
            throw new InvalidOperationException("Adicione o galho antes de adicionar a folha");

        if(!adicionouFolha)
            throw new InvalidOperationException("Adicione a folha antes de adicionar a fruto");

        Membros.Add(new Fruto());
        adicionouFruto = true;
        return this;
    }

    public void ImprimirArvore() { ... }
}

In this example I used the exception Invalidoperationexception, because according to the documentation:

The exception that is thrown when a method call is invalid for the current state of the object.

But you can use a custom exception as your need.

  • +1, although the second solution is a anti-pattern. It is difficult to answer without knowing exactly the problem that @Jamestk wants to solve, but also could not solve via currying? You start with a function f(tronco, galho, folha, fruto); a part of the system receives f and returns a closure g(galho, folha, fruto), and so on and so on, until you arrive at a closure j(), that can be called directly to build the object you want (I don’t know much about C#, so don’t put that as an answer).

2

In the spirit of the comment I posted on reply by @Marcusvinicius, you can create a ArvoreFactoryFactoryFactoryFactory (but obviously you could choose a less weird name, ArvoreSemTronco):

public class ArvoreSemTronco {
    public ArvoreSemGalho AdicionarTronco(Tronco tronco) {
        return ArvoreSemGalho(tronco);
    }
}

public class ArvoreSemGalho {
    Tronco Tronco;

    public ArvoreSemGalho(Tronco tronco) {
        Tronco = tronco;
    }

    public ArvoreSemFolha AdicionarGalho(Galho galho) {
        return ArvoreSemFolha(Tronco, galho);
    }
}

public class ArvoreSemFolha {
    Tronco Tronco;
    Galho Galho;

    public ArvoreSemGalho(Tronco tronco, Galho galho) {
        Tronco = tronco;
        Galho = galho;
    }

    public ArvoreSemFruto AdicionarFolha(Folha folha) {
        return ArvoreSemFruto(Tronco, Galho, folha);
    }
}

public class ArvoreSemFruto {
    Tronco Tronco;
    Galho Galho;
    Folha Folha;

    public ArvoreSemFruto(Tronco tronco, Galho galho, Folha folha) {
        Tronco = tronco;
        Galho = galho;
        Folha = folha
    }

    public Arvore AdicionarFolha(Fruto fruto) {
        return Arvore(Tronco, Galho, Folha, fruto);
    }
}

Obviously, the biggest drawback of this approach is that you need to write an amount of quadratic code in the number of intermediate tree building steps; you could ease that with some kind of automatic code generation.

The largest head start, on the other hand, this allows you to have a half-built tree (and pass it from side to side, and store it in your own methods) at the same time as errors like calling methods in the wrong order are detected at compile time, not at runtime.

  • The way it is it is not possible to add more than one Member. For this approach to work in each class, the method AdicionarXXX(), should return to the class itself and would have to have a method called Build() returning the next class. On the other hand it is necessary to implement the way to save and get the tree with all its members.

  • The question speaks "After AdicionarTronco(), only AdicionarGalho() may be accessed, and AdicionarTronco() can no longer be accessed."; so I limited myself to putting only one trunk, for example, but looking here it seems that yes, for other objects there can be more than one (and then it is necessary to do something like the Build()).

  • Anyway, the idea is essentially this - if it’s to include more than one leaf or more of a fruit, then the final class would have a List<Folha> and a List<Fruto> and the methods AdicionarXXX() would return a new object with the additional leaf or fruit. It is not obvious to me if the final class could not be itself Arvore - provided that AdicionarXXX() return a new tree, instead of changing the existing tree and return void, and other methods of Arvore had similar semantics of immutability.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.