B
TL;DRThere are several possible alternatives to reduce the complexity of creating an object. First of all, it should be verified whether the class is well archived, respecting the principle of single responsibility and high cohesion. Design patterns Do it, Strategy and State can be very useful in this task, besides using specialization of classes.Then one can cogitate the use of injection of dependencies, Service Locator or other form of control reversal can be an output. If this is not enough, it is appropriate to adopt overloads of builders, Factory Method, Factory or Prototype. If this is not enough, you can leave for more complex design patterns Abstract Factory or Builder.There is no cake recipe for this process. Every case is a case. In some cases, some of these processes will be right, others will not. In other cases, it may be that the one who worked on the first, does not work, but the one who had been discarded, this time is the best. There are cases where you will have to combine diverse approaches to have a good result and there are cases where there are several possible approaches equally satisfactory. 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, in addition to applying refactors and changes if the project evolves in a way in which what was good before it ceases to be now.O https://en.wikipedia.org/wiki/Single_responsibility_principle and https://en.wikipedia.org/wiki/Cohesion_(computer_science) First, if you have a class with a huge amount of attributes, it's probably violating the sole responsibility principle and has low cohesion.The principle of single responsibility says that the class must have, one, only one and no more than one responsibility. It should serve to model exactly a concept in the project.Cohesion is a way of evaluating how much a class shapes 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 single responsibility is violated is in that handy class with a lot of methods for a thousand different purposes. You don't even have to be such a blatant thing, because that class you model Funcionario, but that has inside it some data of something that would be of Empresa and also has the methods to validate the CPF and the mobile number is also a violation of the principle of unique responsibility.Classes that are modeled failing to decompose their concepts properly also fail with 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, being able to come to something similar to this: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 resemblance to the database normalization process, although it is still quite different. The idea is that when using concepts of composition, aggregation and association, it is possible to extract related components in concepts the part, increasing cohesion and moving towards single responsibility. Obviously, this process changes the way the class is instantiated.However, although having a unique responsibility is necessary to achieve high cohesion, it is still not enough. The class must also assume its full responsibility, to prevent these from falling into other foreign classes.An example where cohesion fails is when things start to appear like this:public class FuncionarioHelper {
public void marcarFerias(Funcionario f) {
// ...
}
public void obterSalario(Funcionario f) {
// ...
}
public void cadastrarDependente(Funcionario f, Dependente d) {
// ...
}
}
These methods here represent class business rules Funcionario, and therefore they should be in it. The fact that they are not there means 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 https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) , since the class (in the case Funcionario) will have much less need to share and expose your internal data. This also has a direct effect on reducing https://en.wikipedia.org/wiki/Coupling_(computer_programming) , which is a way to evaluate how much a 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, are evidence that there are problems in class cohesion. In some cases this is inevitable because it is not possible to add new methods in the desired class (e.g. a class StringUtils often have a lot of methods of things that we would like to be in class String, but we have no way to put there because we cannot change it). In languages such as Ruby and JavaScript, in which it is possible to add methods to existing classes without directly modifying them (named by this mix-in), this problem is sanctioned. For example:String.prototype.reverse = function() {
let r = "";
for (let x = 0; x < this.length; x++) {
r = this.charAt(x) + r;
}
return r;
}
Thus, by refuting the class, by dividing logical attributes related in classes the part, separated from those where there is a lower relation, tends to obtain classes that are much simpler to be instantiated and to be reused and that have a better encapsulation, requiring less parameters in the constructors. The way to refactor this, sometimes leads us to the design pattern do.The design pattern https://pt.wikipedia.org/wiki/Fa%C3%A7ade 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 (i.e. the name of this design pattern is do). For the instanciation, this means:Each of these subsystems could be independently instantiated, but by instancing them (or otherwise obtaining instances) in the scope of the do, the complexity of their creation is encapsulated.Each subcomponent has its own rules and represents a smaller responsibility within a larger system, and therefore, by separating them, we walk towards the principle of single responsibility, high cohesion and also a good encapsulation.An example of a do that would be: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 have several subcomponents, when it is instantiated, will be the responsibility of the constructor (or some other method of manufacturing, as described below) to provide concrete implementations of internal details such as tanqueCombustivel or cambio.In addition, in the standard do, the encapsulation is improved, since it is expected that there are 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 is not expected to have such methods: public void setPortaDireita(Porta portaDireita) {
this.portaDireita = portaDireita;
}
public void setNivelCombustivel(double nivel) {
tanque.setNivelCombustivel(nivel);
}
public Motor getMotor() {
return motor;
}
A case closer to reality would be that of a class to make an HTTP request to download and/or upload something. Instead of putting everything in one giant 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 URL invoked, etc. If you want something that already handles serialization/marshalling and deserialization/unmarshalling of request and response in objects (rather than strings or strings of brute bytes), will model these behaviors in classes the part too. That's exactly the case. https://msdn.microsoft.com/en-us/library/system.net.http(v=vs.110).aspx .However, it may be that this type of refactoring is not enough or not possible. Still, there are alternatives that can be used as listed below.Specialization of classesSupposing you have a class Funcionario that serves to model doctors, teachers, lawyers, accountants, etc. and there are attributes that have only meaning in certain cases. In this situation, it is appropriate to create specialized classes for each of these specific cases each with their 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.Design patterns https://pt.wikipedia.org/wiki/Strategy and https://pt.wikipedia.org/wiki/State Sometimes it occurs from a class to have a lot of attributes because it models a complex object that can have a number of different behaviors, which by itself is already a violation of the principle of single responsibility.The solution in these cases is to move the behaviors to classes the part. 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 this: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 it first improves cohesion and the question of unique responsibility, but also because often there are attributes that have only 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 pattern is the State, who is the twin brother of Strategy, but when behavior is changeable.Note that a class can implement various distinct behaviors, each in its Strategy or State.Overload of buildersRemember that in many programming languages (not all), builders can be overloaded. Sometimes, even though the object can 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 nothing and each of these depend on fairly simple set of parameters, perhaps 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 a builder can call another.The design pattern https://pt.wikipedia.org/wiki/Factory_Method However, it does not always give to model all cases where the object is manufactured through multiple constructors, even more considering that, due to the fact that all constructors of a class have the same name in many programming languages, there can be completely different cases that work with parameters of the same type. Thus, working with multiple manufacturing methods rather than multiple builders can be output.It is possible to place the constructor as private or internal and then to add static methods to manufacture the instances, each of these covering a specific case. That's what happens with https://docs.oracle.com/javase/9/docs/api/java/util/regex/Pattern.html For example.It is possible that the class has a behavior modeled by an interface (or that you refresh it to achieve this). Then, you can define factory static methods that produce instances in various ways can be made available. Real examples are https://docs.oracle.com/javase/9/docs/api/javax/swing/BorderFactory.html and https://docs.oracle.com/javase/9/docs/api/java/util/List.html (added in Java 9).The design pattern https://pt.wikipedia.org/wiki/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 passing a truck of parameters in 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 be copying the attributes of one object to another being the two of the same class and being that code in a diverse class is a thing that causes a high coupling and a low cohesion. Therefore, it is better that the object makes available a method that returns another object similar to being modified (or even already modified), so that the complexity of creating derivative 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 then use 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 FactoryO Factory is an object whose purpose is to create a certain other object. It should be easy to get an instance of these objects (via builder, Factory Method, https://pt.wikipedia.org/wiki/Singleton or similar thing).A real example is the class https://docs.oracle.com/javase/9/docs/api/javax/swing/PopupFactory.html which contains two different methods to create popups.The advantage of this approach is that it is possible to set up Factory before calling the methods of creating instances, which can even be called multiple times with the same instance of Factory.The design pattern https://pt.wikipedia.org/wiki/Abstract_Factory A special kind of factory is one that allows multiple distinct implementations. This is the design pattern Abstract Factory, where to factory is defined by an abstract class or interface and it is possible to create several specialized instances, each by 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 abstract interface or class.The design pattern https://pt.wikipedia.org/wiki/Builder Already Builder is to use in circumstances where creation is more complicated, where each method configures an aspect of the object to be 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 the case, each method of Builder with the exception of the last (o build()) can return both the own Builder (i.e., 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 should be called are indeed called, nor to make sure that none of them is called twice, nor to ensure that they are called in the correct order (there may be cases where this is important). The solution in that case would be to make ServidorHttpBuilder have only method porta that returns one ServidorHttpBuilder2 that has only the method baseUrl that returns one ServidorHttpBuilder3 that 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 have been called twice and that they are called in the correct order, otherwise a compilation error occurs. However, this approach usually adds a lot of complexity and an excessive number of new classes, being feasible in few cases. https://en.wikipedia.org/wiki/Inversion_of_control Often the difficulty of installing a class, is in providing you with other objects that it needs to work, i.e. your dependence.The idea is to rid the class that wants to use the class to be instantiated (client class) of locating all dependencies and placing them in the class to be instantiated. Note that in this case, the previous patterns help little, because none of them will rid the customer class of this work, just 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 that is Control reversal. https://en.wikipedia.org/wiki/Dependency_injection One way to have control reversal is to delegate this complexity to a framework. The framework is configured by means of annotations, XML, JSON, code conventions or anything else to know what class dependency injection points are. These points can be parameters in the constructor, setters or loose attributes that will be filled via reflection. Thus, the code you want to get an instance of the class, asks for a framework and the framework is responsible for locating all dependencies and injecting them, freeing the code you want to just use the object of 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 put: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 to instand the class Refeicao, it will already automatically find that Morango is the tasty fruit and that Abacaxi is the sweet fruit.The design pattern https://en.wikipedia.org/wiki/Service_locator_pattern The injection of dependencies is the most common form of control reversal, but it is not the only one. Another popular way is the design pattern Service Locator. In this pattern, there is an object (the Service Locator) which is central to providing implementations of various objects. Thus, the object to be instantiated asks the Service Locator by the implementation of each of its dependencies. Research in Service Locator can be made by name, by interface of which you want some implementation or by some other criterion.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");
}
// ...
}
ConclusionThere are several possible alternatives to reduce the complexity of creating an object. First of all, it should be verified whether the class is well archived, respecting the principle of single responsibility and high cohesion. Design patterns Do it, Strategy and State can be very useful in this task, besides using specialization of classes.Then one can cogitate the use of injection of dependencies, Service Locator or other form of control reversal can be an output. If this is not enough, it is appropriate to adopt overloads of builders, Factory Method, Factory or Prototype. If this is not enough, you can leave for more complex design patterns Abstract Factory or Builder.