How and when to build an object in valid state?

Asked

Viewed 814 times

30

Think of a large class, a complete customer registration for example. It has a huge amount of attributes in it. Many of them need to be initialized in the construction of the object in order for the object to be valid, and it is not known beforehand whether all of them are valid or whether their joint use makes the object valid. The object can be used in several application contexts. We could do:

  1. A constructor with all the necessary parameters and it validates everything and decides whether to complete the construction or not.

  2. A constructor with only the basics to create the object and then let put value on other attributes validating individually.

    How you should proceed with temporary invalidity?

  3. Have a static method that validates everything before calling the constructor to know that it will build correctly.

  4. Create a temporary object of a type defined for this purpose and use it as a basis to create the ultimate object, probably with a constructor who accepts this object and knows what to do.

    This object would accept the attributes incrementally and could validate at a given time to be used on the real object.

  5. Some other way.

I imagine that all can be useful in some specific situation, although some very rare and practically to circumvent some other deficiency.

What are the advantages and disadvantages of each approach and in which scenario to choose each one?

Reference: What good is a builder?

  • What are the advantages/disadvantages of applying each of the 5 approaches to a class of the type you refer to and in that scenario? Or, on the contrary, it’s something more generic where each of the 5 approaches apply or can apply to different types/scenarios, such as Victor Stafusa’s response?

  • @ramaral the first. I find Victor’s answer very good, but still do not know if he answered tangentially the question. Directly did not answer.

  • I noticed your concern to define a context, however I asked to be sure.

  • @ramaral you are intending to give a more aligned answer to the question?

  • 1

    I thought I would, but I couldn’t get anything I thought would be enough to post. My little experience in this area (I’m just a curious one), although I devote some time to thinking about these subjects, does not allow me to arrange my ideas consistently and rationally to formulate a response.

  • 1

    Apart from what is obvious to say regarding hypotheses 1 and 2 I would only add that I would opt for a type class Factory to build the object incrementally. Each of the Factory methods would return an interface whose contract allows to use the object in a valid state and receive an object(interface) corresponding to the state immediately prior to what it will build. I do not know if this fits in with point 4.

  • 1

    Of course, the use of interfaces does not guarantee that the object is used in an invalid state (it is always possible to cast for the "total" object). However, doing so (the cast) will be a premeditated action and "whoever" does so must be aware of the consequences.

Show 2 more comments

3 answers

26


TL;DR

There are several possible alternatives to reducing the complexity of creating an object. First of all, it is necessary to verify that the class is well designed, respecting the principle of sole responsibility and that of high cohesion. The design patterns Façade, Strategy and State can be very useful in this task, in addition to using class specialization.

Then one can consider the use of dependency injection, Service Locator or another form of inversion of control can be an output. If this is not enough, it is convenient to adopt overloads of builders, Factory Method, Factory or Prototype. If this is not enough, you can go to the most complex design patterns Abstract Factory or Builder.

There is no cake recipe to carry out this process. Each case is a case. In certain cases, some of these processes will be certain, others will not. In other cases, it may be that the one that worked on the first one doesn’t work, but the one that was discarded, this time it’s the best. There are cases where you will have to combine several approaches to have a good result and there are cases where there are several equally satisfactory possible approaches. It is necessary a certain tact and vision of the whole to realize which of these would be the best, and a good dose of experimentation also, besides applying refactoring and changes if the project evolves in a way in which what was good before ceases to be now.

The principle of single responsibility and the high cohesion

First, if you have a class with a huge amount of attributes, it’s probably violating the principle of single responsibility and has low cohesion.

The principle of sole responsibility says that the class must have one, only one, and not more than one responsibility. It should serve to model exactly one concept in the project.

Cohesion, on the other hand, is a way of assessing how much a class models its responsibility. A class has high cohesion when it has a single well-defined responsibility and models it completely. The higher the cohesion, the better.

An example where the principle of sole responsibility is violated is in that all-purpose class with a lot of methods for a thousand different purposes. It doesn’t even have to be something so blatant, because that class that models Funcionario, but it has inside it some data of something that would be Empresa and also has the methods to validate the CPF and the mobile number is also a violation of the principle of sole liability.

Classes that are modeled by failing to decompose properly also fail the principle of single responsibility and high cohesion. For example:

public class Funcionario {
    private String nome;
    private String cpf;
    private LocalDate nascimento;
    private String cidade;
    private String estado;
    private String pais;
    private String endereco;
    private String numeroEndereco;
    private String complemento;
    private int dddCelular;
    private int numeroCelular;
    private int dddTelefoneFixo;
    private int numeroTelefoneFixo;
    private int ramalTelefoneFixo;

    // Um construtor gigante e uma porrada de métodos aqui.
}

Note that it is possible to decompose this class into smaller classes: Telefone, Endereco, Cidade, Estado, Pais, something similar can be achieved:

public class Funcionario {
    private String nome;
    private String cpf;
    private LocalDate nascimento;
    private Endereco endereco;
    private List<Telefone> telefones;

    // Um construtor razoável com alguns métodos aqui.
}

This process has a certain similarity with the database normalization process, although it is something quite different. The idea is that when using concepts of composition, aggregation and association, it is possible to extract components related to each other in concepts, increasing cohesion and moving towards a single responsibility. Obviously, this process changes the way the class is instantiated.

However, although having a single responsibility is necessary to achieve high cohesion, it is still not enough. The class must also assume its full responsibility, to avoid these falling into other classes of others.

An example where there is a gap in cohesion is when things like this start to appear:

public class FuncionarioHelper {
    public void marcarFerias(Funcionario f) {
        // ...
    }

    public void obterSalario(Funcionario f) {
        // ...
    }

    public void cadastrarDependente(Funcionario f, Dependente d) {
        // ...
    }
}

These methods there represent business class rules Funcionario, and therefore should be there. The fact that they are not there means that the class Funcionario probably has a low cohesion. The ideal would be to do this:

public class Funcionario {

    // Tudo que já estava antes.

    public void marcarFerias() {
        // ...
    }

    public void obterSalario() {
        // ...
    }

    public void cadastrarDependente(Dependente d) {
        // ...
    }
}

This also tends to improve the encapsulation, since the class (in this case Funcionario) will have much less need to share and expose your internal data. This also has direct effect on reducing the coupling, which is a way of assessing how much one class depends on others. The smaller the coupling, the better.

In general, classes that have names containing words such as Utils, Helper, Manager, among others, there are indications that there are problems in class cohesion. In some cases, this is inevitable because it is not possible to add new methods to the desired class (for example, a class StringUtils often have a lot of methods of things that we would like you to be in class String, but we can’t put it there because we can’t change it).

In languages such as Ruby and Javascript, where it is possible to add methods to existing classes without modifying them directly (a concept called mix-in), this problem is solved. For example:

String.prototype.reverse = function() {
    let r = "";
    for (let x = 0; x < this.length; x++) {
        r = this.charAt(x) + r;
    }
    return r;
}

Therefore, by refactoring the class, dividing related logical attributes into classes separately from those where there is a lower relation, we tend to obtain classes that are much simpler to be instantiated and to be reused and have a better encapsulation, requiring fewer parameters in the constructors. How to refactor this sometimes leads to the design pattern Façade.

The design pattern Façade

Sometimes a class represents a very complex set of subsystems. In this case it is appropriate to divide it into these subsystems (or more often, to integrate several complex subsystems into a simpler interface). A class can be used to aggregate all these subcomponents into a larger component so that the resulting class is a facade for all these systems (so the name of this project pattern is Façade). For instantiation, this means that:

  • Each of these subsystems could be instantiated independently, but by instantiating them (or otherwise obtaining instances) in the scope of Façade, the complexity of their creation is encapsulated.

  • Each of our subcomponents has its own rules and represents a smaller responsibility within a larger system, and so by separating them, we move towards the principle of single responsibility, high cohesion and also good encapsulation.

An example of a Façade that would be it:

public class Carro {
    private Roda frontalEsquerda;
    private Roda frontalDireita;
    private Roda traseiraEsquerda;
    private Roda traseiraDireita;
    private Cambio cambio;
    private Motor motor;
    private Volante volante;
    private Porta portaEsquerda;
    private Porta portaDireita;
    private Tanque tanqueCombustivel;
    private Radiador radiador;

    // ...
}

Note that in the above case, although the Carro has several subcomponents, when it is instantiated, it will be the responsibility of the constructor (or some other manufacturing method, as described below) to provide concrete implementations of internal details such as the tanqueCombustivel or the cambio.

Also, in the standard Façade, encapsulation is improved as it is expected to have methods like this:

    public double getCapacidadeCombustivel() {
        return tanque.getCapacidadeCombustivel();
    }

    public double getNivelCombustivel() {
        return tanque.getNivelCombustivel();
    }

    public void abastecer(TipoCombustivel tipo, double litros) {
        if (!motor.isCombustivelAceito(tipo)) {
            throw new IllegalArgumentException("Este carro não deve ser abastecido com esse tipo de combustível.");
        }
        tanque.abastecer(tipo, litros);
    }

    public List<TipoCombustivel> getTiposCombustivelAceitos() {
        return motor.getTiposCombustivelAceitos();
    }

And it’s not supposed to have methods like this:

    public void setPortaDireita(Porta portaDireita) {
        this.portaDireita = portaDireita;
    }

    public void setNivelCombustivel(double nivel) {
        tanque.setNivelCombustivel(nivel);
    }

    public Motor getMotor() {
        return motor;
    }

A case of this closer to reality would be that of a class to make an HTTP request to download and/or upload something. Instead of putting it all in one gigantic class RequisicaoHttp, you would have a class to represent a header, one to represent the HTTP method, one to represent the body of the request, one to represent the body of the response, the status code, the invoked URL, etc. If you want something that already deals with serialization/marshalling and deserialization/unmarshalling of the request and response in objects (rather than strings or strings of raw bytes), will model these behaviors in classes separately as well. This is exactly the case with namespace System.Net.Http of the C#.

However, it may be that this type of refactoring is not sufficient or not possible. Still, there are alternatives that can be used as listed below.

Class specialization

Let’s assume you have a class Funcionario which is used to model doctors, teachers, lawyers, accountants, etc., and there are attributes that only make sense in certain cases. In this situation, it is appropriate to create specialized classes for each of these specific cases each with its specific attributes, so that no class will have attributes that are used only in certain cases. That would then make the class Funcionario in a superclass or an interface.

The design patterns Strategy and State

Sometimes it occurs that a class has a lot of attributes because it models a complex object that can have a number of different behaviors, which in itself is already a violation of the principle of sole responsibility.

The solution in these cases is to move the behaviors to separate classes. For example, instead:

function Peca(tabuleiro, cor, x, y, tipo) {

    function verificarMovimentoRei() {
        // ...
    }

    function verificarMovimentoDama() {
        // ...
    }

    function verificarMovimentoBispo() {
        // ...
    }

    this.mover = function() {
        if (tipo === "Rei") {
            if (verificarMovimentoRei()) // ...
            // ...
        } else if (tipo === "Dama") {
            if (verificarMovimentoDama()) // ...
            // ...
        } else if (tipo === "Bispo") {
            if (verificarMovimentoBispo()) // ...
            // ...
        } else //...
        // ...
    }
}

You better do it:

function Rei() {
    function verificarMovimento(tabuleiro, cor, x, y) {
        // ...
    }
}

function Dama() {
    function verificarMovimento(tabuleiro, cor, x, y) {
        // ...
    }
}

function Bispo() {
    function verificarMovimento(tabuleiro, cor, x, y) {
        // ...
    }
}

function Peca(tabuleiro, cor, x, y, tipo) {

    this.mover = function() {
        if (tipo.verificarMovimento(tabuleiro, cor, x, y) // ...
        // ...
    }
}

This tends to facilitate the creation of objects because first it improves cohesion and the issue of single responsibility, but also because there are often attributes that only have sense of being used in specific behaviors.

Sometimes an object can change behavior (imagine the pawn that is promoted and turns into another piece). In this case the default is the State, who is the twin brother of Strategy, but when the behavior is changeable.

Note that a class can implement several distinct behaviors, each in its own Strategy or State.

Overload of builders

Remember that in many programming languages (not all), constructors can be overloaded. Sometimes, although the object can even have a large number of attributes and model complex rules, there are only a small number of situations where it is valid to create one of them from scratch and each of these depend on reasonably simple parameter sets, maybe independent of each other. In this case, a possible output would be to have multiple constructors, each working with a different set of parameters. Also remember that one constructor can call another.

The design pattern Factory Method

However, it is not always possible to model all cases where the object is manufactured by means of multiple constructors, especially considering that, due to the fact that all constructors of a class have the same name in many programming languages, It can happen that there are completely different cases that work with parameters of the same type. Thus, working with multiple manufacturing methods instead of multiple builders can be the output.

  • It is possible to place the constructor as private or internal and then add static methods to manufacture the instances, each of these covering a specific case. That’s what happens to class java.util.regex.Pattern, for example.

  • It is possible that the class has a behavior modeled by an interface (or that you refactor it to achieve this). Then, you can define static factory methods that produce instances in various ways can be made available. Real examples are the class javax.swing.BorderFactory and the methods of(...) of the interface java.util.List (added to Java 9).

The design pattern Prototype

Sometimes the complexity of creating an object is in creating copies of an existing object with slightly different properties. The base object is something simple, but we need several derived objects. A direct implementation by passing a truck parameters on the builder would lead us to something like this:

Personagem modelo = ...;
Personagem novo = new Personagem(
        modelo.getClasse(),
        modelo.getForca(),
        modelo.getInteligencia(),
        modelo.getPoder(),
        novaVelocidade, // Esse daqui não é copiado do modelo.
        modelo.getHP(),
        modelo.getMP());

Note that copying the attributes of an object to another being the two of the same class and being this code in a different class is a thing that causes a high coupling and a low cohesion. Thus, it is better for the object to provide a method that returns another object similar to being modified (or even already modified), so that the complexity of creating derived objects is reduced. For example, let’s assume that the class Personagem have methods like this:

public Personagem comForca(int novaForca) {
    return new Personagem(
            this.classe, novaForca, this.inteligencia, this.velocidade, this.hp, this.mp);
}

public Personagem comVelocidade(int novaVelocidade) {
    return new Personagem(
            this.classe, this.forca, this.inteligencia, novaVelocidade, this.hp, this.mp);
}

We can use it like this:

Personagem modelo = ...;
Personagem novo = modelo.comVelocidade(novaVelocidade);

Another possibility would be to do just that:

// Na classe Personagem:

public Personagem clone() {
    return new Personagem(/* Aquele montão de parâmetros... */);
}
// No código que usa a classe:

Personagem modelo = ...;
Personagem novo = modelo.clone();
novo.setVelocidade(novaVelocidade);

The second approach is simpler and more flexible, but the first is more robust.

The design pattern Factory

The Factory is an object whose purpose is to create a certain other object. It should be easy to obtain an instance of these objects (via constructor, Factory Method, Singleton or similar thing).

A real example is the class javax.swing.PopupFactory which contains two different methods to create popups.

The advantage of this approach is that it is possible to Factory before calling the methods of creating instances, which can even be called multiple times with the same instance of Factory.

The design pattern Abstract Factory

A special kind of Factory is the one that allows multiple distinct implementations. This is the design standard Abstract Factory, where the Factory is defined by an abstract class or interface and it is possible to create several specialized instances, each building the object in question in its own way. Often, in such cases, the object in question to be produced is also specified by an interface or abstract class.

The design pattern Builder

Already the Builder is to use in circumstances where creation is more complicated, where each method configures an aspect of the object being produced. For example:

 ServidorHttp s = new ServidorHttpBuilder()
         .porta(1234)
         .baseUrl("http://www.example.com")
         .staticFileLocation("/public")
         .addFilter(new AccessControlFilter())
         .addFilter(new LoginFilter())
         .addServices(services)
         .build();

In this case, each method of the Builder with the exception of the latter (the build()) can both return own Builder (ie, returns this, self, Me or the equivalent according to the programming language), or else returns a new instance of Builder.

This still has the disadvantage of not ensuring that all methods of Builder that they should be called are indeed called, nor make sure that none of them is called twice, nor ensure that they are called in the right order (there may be cases where this is important). The solution in that case would be to do the ServidorHttpBuilder have only the method porta that returns a ServidorHttpBuilder2 which has only the method baseUrl that returns a ServidorHttpBuilder3 which has only the method staticFileLocation, etc. This approach ensures that the final method build() can only be called if all the methods that have to be called have been, that none has been called twice and that they are called in the correct order, otherwise a compilation error occurs. However, usually this approach adds quite complexity and an excessive number of new classes, being feasible in a few cases.

Inversion of control

Often the difficulty of instantiating a class, is in providing it with other objects that it needs to work, namely its dependencies.

The idea is to free the class that wants to use the class to be instantiated (client class) from locating all dependencies and placing them in the class to be instantiated. Note that in this case, the previous standards help little, as none of them will free the client class from this job, only make it easier.

Therefore, an approach to be used to provide the appropriate dependencies to an object, freeing the classes that wish to use it from having to know how to find them is necessary. The name of it is Inversion of control.

Dependency injection

One way to have inversion of control is to delegate this complexity to a framework. The framework is configured through annotations, XML, JSON, code conventions or anything else in order to know what the dependency injection points of the classes are. These points can be parameters in the constructor, setters or loose attributes that will be populated via Reflection. Thus, the code that wants to get an instance of the class, asks one from the framework and the framework is responsible for locating all the dependencies and injecting them, freeing the code that you want to just use the object from having to worry about it. EJB, CDI and Spring are well-known examples of dependency injection frameworks. For example, in the class to be instantiated, this is:

public class Refeicao {
    private Fruta frutaSaborosa;
    private Fruta frutaDoce;

    public Refeicao (
            @Inject @Qualifier("saborosa") Fruta frutaSaborosa,
            @Inject @Qualifier("doce") Fruta frutaDoce)
    {
        this.frutaSaborosa = frutaSaborosa;
        this.frutaDoce = frutaDoce;
    }

    // ...
}

In the framework configuration, this is put:

<bean id="saborosa" class="com.example.frutas.Morango"/>
<bean id="doce" class="com.example.frutas.Abacaxi"/>

So, when the framework is instantiating the class Refeicao, it will already automatically find that Morango is the tasty fruit and that Abacaxi is the sweet fruit.

The design pattern Service Locator

Dependency injection is the most common form of control inversion, but it is not the only one. Another popular form is the design pattern Service Locator. In this pattern, there is an object (the Service Locator) that is the central responsible for providing implementations of several objects. Thus, the object to be instantiated asks the Service Locator the implementation of each of its dependencies. The Service Locator can be done by name, by interface of which some implementation is desired or by some other criteria.

For example:

public class Refeicao {
    private Fruta frutaSaborosa;
    private Fruta frutaDoce;

    public Refeicao() {
        ServiceLocator locator = ServiceLocator.getInstance();
        this.frutaSaborosa = (Fruta) locator.find("saborosa");
        this.frutaDoce = (Fruta) locator.find("doce");
    }

    // ...
}

Completion

There are several possible alternatives to reducing the complexity of creating an object. First of all, it is necessary to verify that the class is well designed, respecting the principle of sole responsibility and that of high cohesion. The design patterns Façade, Strategy and State can be very useful in this task, in addition to using class specialization.

Then one can consider the use of dependency injection, Service Locator or another form of inversion of control can be an output. If this is not enough, it is convenient to adopt overloads of builders, Factory Method, Factory or Prototype. If this is not enough, you can go to the most complex design patterns Abstract Factory or Builder.

7

Victor’s answer already speaks in all content I would like to give a much simpler answer.

Normally you have one and only one way to build an object. This form is as follows::

Object objecto = new Object();

And that’s it. All objects are built directly or indirectly in this way and have no other option. Even if you choose to build an object through reflection, you will always have to call the builder with reflection.

Manufacturers shall ensure, to the maximum extent possible, that an object is validly created and ready to use.1

Put another way, it should be possible to call any instance method without it having problems due to the object not being initialized correctly.


Going through the various solutions you propose:

A constructor with all the necessary parameters and it validates everything and decide whether to complete the construction or not.

This is exactly the technique that should be followed in most cases, as I have already mentioned.

A constructor with only the basics to create the object and then leave put value into other attributes by individually validating.

How you should proceed with temporary invalidity?

I would say that this depends very much on the usefulness of the object and even the conventions you are following with your colleagues for your current project! Often I do a DTO in hand without parameters in the constructor and use the initializer.

Whether this is correct or not I would say it is a philosophical question. Personally I would say that if the code works as expected and that it is all right. Eventually you may have more problems with inconsistent data, but you can always resolve by checking in place...

Have a static method that validates everything before calling the constructor to know that you will build correctly.

This also happens, perhaps a little less often. There are some very classic examples in C#, now look.

Tuple.Create(1, "a", 1.0);
File.OpenText("abc.txt"); //retorna um StreamReader 

None of these methods is strictly necessary since you can still build these objects with the new. But they are useful methods.

Tuple.Create(1, "a", 1.0); // = new Tuple<int, string, double>(1, "a", 1.0);

Other times, you don’t even have other options and can only build objects using static methods. This happens especially if you want to hide implementation details in your Apis, keeping classes that implement functionality closed.

Create a temporary object of a type defined for this purpose and use it as the basis to create the ultimate object, probably with a constructor that accepts this object and knows what to do.

This object would accept attributes incrementally and could validate at a given time to be used on the actual object.

This is very classic in Javascript and is known by the name Prototype. See Victor’s response since I don’t think I need to add anything else on the subject.

Some other way.

Well, as I said earlier you have only one way to build an object. Using the new and the builder. But that doesn’t mean that it solves all your problems.

There will be situations where you have to work on a logical drive. And you will eventually have various types of objects each working on a part of that logical drive.

To better understand, imagine the following scenario. You have a program that works with cars and motorcycles. And it has the following classes:

RodaMoto, MotorMoto, GuiadorMoto,
RodaCarro, MotorCarro, GuiadorCarro

There are drawing patterns, which help to group objects by logical unit. That is, they know how to decide which object to use in a given situation. In other words, these patterns can help you group all the respective components to the bike and all the respective components to the car.

And they can also help you decide which component to use. If you provide a bike, it will decide to use the components of the bike.

Again, for more details see Victor’s reply. (Abstract Factory, Factory, Factory method...)

3

From time to time I come to this question for a new attempt to build an answer.
In all of them I have always encountered the problem of finding a way to cover all the aspects that the question determines.

One of the attempts was like this:

In the scenario it presents, approaches 1 and 3 do not seem applicable to me and 2 and 4, although they may be applicable, transfer the responsibility for validation of the object to the client code.

So I’ll just state the obvious for each of them.

1 - A constructor with all the necessary parameters and it validates everything and decides whether to complete the construction or not.

  • Make sure you get a valid object. It is necessary to have all the necessary parameters before being able to build it.

2 - A constructor with only the basics to create the object and then let put value on other attributes validating individually.

  • It makes it possible to dispose of the object immediately. The client code will need to verify, at each time of using it, whether the object is in a valid state for the use in question.

3 - Have a static method that validates everything before calling the constructor to know that it will build correctly.

  • Make sure you get a valid object. All necessary information must be available. Allows adapting the form/type of information available to the form/type required by the manufacturer.

4 - Create a temporary object of a type defined for this purpose and use it as the basis to create the definitive object, probably with a constructor that accepts this object and knows what to do.

  • I don’t know if you’re referring to a Factory that allows building the object incrementally. Each of the methods Factory would return an interface whose contract allows using the object in a valid state for a given context. Each method would receive an object(interface) corresponding to the state immediately prior to what it will build.

  • Allows to obtain a valid object for a given context. Does not guarantee that the object is used in an invalid state.

The answer describes advantages and disadvantages, but perhaps there will be another(o)s. It does not illustrate possible use scenarios, but some can be inferred.

The question clearly defines its context/domain and its limits. However I cannot build a response that unambiguously meets all the required requirements.
Not being able to use the method described in point 1 to construct a response, since I do not have all the necessary knowledge, I chose to use the method described in point 2 and thus at least be able to construct a.

Would the answer be valid? I don’t know, maybe.
The information in it may be sufficient for a particular reader to consider it valid in its context. Another reader can, due to other advantages/disadvantages you know, come here to edit it, in order to make it valid in your scenario.
Others must take the answer as completely valid and will have troubles in the future.

One way that I could have given a complete answer would be to have narrower limits.
A solution would be to divide the question into several, in which case less knowledge is needed to answer each one.

The scenario (the use of the Client object) described in the question suffers from the same problem: its context/domain has too wide limits so that the answer (the Client object) can unequivocally satisfy all the requirements.

The solution will be, as for the question, to divide the scenario into several more limited contexts.
In each of them a different Customer Type will be used with only the necessary members in this context. The characteristics and invariances of this type will be those that are known in this context.

Method 1 can thus be used, which is the only method that guarantees obtaining a valid object.

  • To clarify, items 2 and 4 does not transfer the validation to the client, the class of the object would still be responsible for the validation, only that not everything would occur at the beginning, no 2 would not accept invalid data, and this has a problem, but I will not speak here, and in 4 could occur in the same way, or at least maintain a status in every change of ownership, but probably the ideal would be a unique validation at the end, and only with the status of validated is that could transfer to the main object, in this case could require the client to call the validator method, ...

  • ...but perhaps the passage from the temporary object to the main one would take care of the validation without requiring anything from the client, which may not solve the problem at all, but this is another aspect. And I believe Four is talking about Builder and not Factory, the Factory Method would be the 3. Or am I mistaken? I think an incomplete answer is better than no or wrong answer. The complete is better, but the incomplete is valid. Of course a good part of it is almost a goal :)

  • And the main one didn’t add much, but it helps to see the problem better and shows that what I posted didn’t give to understand well how I would implement, at least in items 2 and 4. I will see what I can do to improve the ability to be better answered without creating new problems.

  • It seems that I misunderstood the scenario you described, I understood as being an application with several modules (e.g. Budgeting, Orders, ...) and each module would use the same Client class. In some of them the Client would never be used "complete". In fact what I refer to in 4 is a Builder.

  • "Of course much of it is almost a goal" - I found it interesting to make the analogy between the difficulty of answering the question and the difficulty of using the Client object, at least as I understood the scenario.

  • She understood the scenario correctly, I think only the question that the client class or her temporary would be responsible for all validation, as every object should be.

  • "(...)that the client class or her temporary would be responsible for all validation(.. )" - I have no doubt about that. Do not see the text of "attempt" as an answer. It adds nothing because it was not the intention, It was included only to help build the analogy and justify the choice for separation/division in several smaller contexts. Of course, this only answers one part of the question (point 5).

  • The excerpt O código cliente terá de verificar, em cada momento de o usar made me think that understood that the validation would have to be done by the consumer code, clarified then.

  • 1

    Don’t take that as an answer. I know that the intention was for the AR to pick up each of the points and explain how it would implement, what the difficulties and possible problems would be. As I totally agree when you say: "I imagine that all can be useful in some specific situation, although some very rare and practically to circumvent some other deficiency.", I preferred to focus on point 5. Perhaps I was not happy with the analogy.

Show 4 more comments

Browser other questions tagged

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