Architecture problem where access to construction methods must be controlled

7

I have a tree class 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 previous ones can not be access 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 creating Arvore , the only method that can be accessed is AdicionarTronco() .

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

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

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

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

So I thought about controlling access to methods through interfaces, and I thought:

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

public interface ITronco
{
    IGalho AdicionarGalho();
}

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

Then, 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 have managed to resolve the issue of not being able to get back to the methods, but by Arvore I still have access to AdicionarGalho() , AdicionarFolha() and AdicionarFruto() methods.

However, in the end I need to access the ImprimirArvore() method.

How can I resolve this?

    
asked by anonymous 15.06.2015 / 23:46

3 answers

3

This type of problem can be solved by using the Composite Design Pattern

  

Composite is a software design pattern 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 hierarchy of classes to which it belongs.

Let's start by writing an abstract class that will serve as the basis for all composite members:

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

Add Member - Adds members to this Member. At the lower composite levels an Exception is thrown out because its addition (Leaf and Fruit) is not allowed

Print - Prints the name of this Member

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 that have members (Tree, Trunk, and Branch). It will be an abstract class and inherit from Member .

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

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: Sheet 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 constructor, if you want to give a specific name to the fruit (Banana for example).

The member classes that are composite are missing: Tree , Tree and Tree

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 Composite Member and pass their name to the base class. To the Tree class another constructor was added to give the tree a specific name.

Having all 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:

  

-Macieira
  --Tronco
  --- Twig
  ---- Sheet
  ---- Sheet
  ---- apple 1
  ---- apple 2
  ---- apple 3
  --- Twig
  ---- Sheet
  ---- Sheet
  ---- apple 4
  ---- apple 5

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

    
16.06.2015 / 13:52
3

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

• Make methods AdicionarTronco , AdicionarGalho , AdicionarFolha , and AdicionarFruto private 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 private variable of type Boolean in the scope of the class that will indicate if the 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 InvalidOperationException exception, because according to the documentation:

  

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

But you can use a custom exception as needed.

    
16.06.2015 / 00:06
1

In the spirit of the comment I posted on @MarcusVinicius response , you can create a ArvoreFactoryFactoryFactoryFactory ( but obviously you could choose a less obdurate 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);
    }
}

Of course, the biggest drawback of this approach is that you need to write a quadratic code number in the number of intermediate tree-building steps; you could alleviate this with some sort of automatic code generation.

The biggest advantage, on the other hand, is that this allows you to have a tree built in half (and pass it from one side to another, and store in your own methods) at the same time that errors like calling methods in the wrong order are detected at compile time, not at runtime.

    
16.06.2015 / 14:55