⬆️ ⬇️

A few words about using enums in a changing environment.

This post offers a solution to problems with the use of enumerations when changing the composition of constants or the presence of duplication of code when using them. In other cases, the use of the approach sounded below, as a rule, is impractical.







Description of enums in java



In Java, starting with version 1.5, among other things, the so-called enumerations (enum) have appeared. There are a number of advantages from using enumerations against named constants:



However, compared to, say, C ++, enumerations in Java are full-fledged objects, which gives the developer much more flexibility.



Considering all of the above, we can say that enumerations in Java are no longer just constants (ie, data), but full-fledged objects, from which certain conclusions can be drawn about their use.

')

Simplicity is power!



When using the approach proposed below, it is necessary to take into account that it is rather complicated. In accordance with the following post , this construction should not be applied in simple cases.



Its purpose is to simplify life in difficult expansion-oriented situations, such as:





In the absence of these symptoms, the use of the approach proposed below is doubtful and even dangerous, since The increased complexity of the code requires more careful analysis if any errors occur.



Using enums in a changing environment



Now, in fact, let us go to the essence of the question that I wanted to consider.



Enumerations should be used in those cases for which switch'i were used in pluses. Since enumerations can contain inherited methods, as well as implement interfaces, all the logic of the switch handlers can be included in the methods of the enumeration class. Remember this as a fact, and consider the following problem.



Due to the seeming simplicity of enumerations, as well as the fact that, from the experience of languages ​​like C ++, we are accustomed to considering them as data, enumerations control the behavior of the system in various parts of the system, as a rule, weakly connected with each other. Moreover, if we carry out all the conditional logic in enumeration methods (to get rid of branching), saturation and overflow of logic contained in the enumeration class inevitably occurs. It also leads to increased module connectivity.



In this case, you can do the following:

  1. We divide the enumeration class into handlers, each of which will correspond to one of the modules of the system. This is how we solve the interface congestion problem of the enumeration class itself.
  2. It remains to solve the problem of connectivity. To do this, we assign an interface to each handler, and will receive instances through the factory. The factory itself can be created using a declarative approach, i.e. communication of interfaces with implementations will be carried out at the configuration level (for example, via xml).


Thus, we get an approach that differs by the following pros and cons:



All the listed disadvantages can be resolved by writing the corresponding support for this functionality as a plug-in for the IDE used. This, of course, requires some costs, but can be decided centrally and once, after which it will be used always and everywhere.



Usage example





For example, we have the following set of classes (in different modules):



 public enum DocumentStatus {
	 NEW (0),
	 DRAFT (1),
	 PUBLISHED (2),
	 ARCHIVED (3);

	 private DocumentStatus (int statusCode) {
		 this.statusCode = statusCode;
	 }

	 public int getStatusCode () {
		 return statusCode;
	 }

	 private int statusCode;
 }

 // Web
 public class DocumentWorklfowProcessor {
	 ...

	 public List <Operation> getAvailableOperations (DocumentStatus status) {
		 List <Operation> operations;

		 switch (status) {
		 case NEW:
			 operations = ...;
			 break;
		 case DRAFT:
			 operations = ...;
			 break;
		 case PUBLISHED:
			 operations = ...;
			 break;
		 case ARCHIVED:
			 operations = ...;
			 break;
		 }

		 return operations;
	 }

	 public void doOperation (DocumentStatus status, Operation op) throws APIException {
		 switch (status) {
		 case NEW:
			 // one set of operation parameters
			 break;
		 case DRAFT:
			 // another set of operation parameters
			 break;
		 case PUBLISHED:
			 // third set of operation parameters
			 break;
		 case ARCHIVED:
			 // etc.
			 break;
		 }
	 }
 }


 // Scheduled task
 public class ReportGenerationProcessor {
	 ...

	 public void generateReport (Report report) {
		 DocumentStatus status = report.getDocument (). GetStatus ();
		 ReportParams params;

		 switch (status) {
		 case NEW:
		 case DRAFT:
			 // params = one selection criteria for the elements displayed in the report
			 break;
		 case PUBLISHED:
			 // params = other selection criteria for the elements displayed in the report
			 break;
		 case ARCHIVED:
			 // etc.
			 break;
		 }

		 // Generate report
	 }
 }




It can be seen that the modules are rather weakly interconnected.

Suppose we are at the request of the customer there is a new document status - sent for confirmation (VERIFY).

When adding a new status, you will have to add a new case in all the proposed locations. It is very easy to forget to add it somewhere. Of course, you can provide default-blocks, throwing exceptions, but for a large system it does not guarantee that all places will be noticed.

It is proposed to convert this code to the following form:



 public interface IReportGeneratorProcessor {
	 public ReportParams getReportParams ();
 }

 public interface IDocumentWorklfowProcessor {
	 public List <Operation> getAvailableOperations ();

	 public void doOperation (Operation op) throws APIException;
 }

 public enum DocumentStatus {
	 // Here, instead of new, you can use a factory or even lazy-initialization in get-methods
	 NEW (0, new NewDocReportGeneratorProcessor (), new NewDocWorklfowProcessor ()),
	 DRAFT (1, new DraftDocReportGeneratorProcessor (), new DraftDocWorklfowProcessor ()),
	 PUBLISHED (2, ...),
	 ARCHIVED (3, ...);

	 private DocumentStatus (int statusCode, IReportGeneratorProcessor reportProc,
			 IDocumentWorklfowProcessor docProc) {
		 this.statusCode = statusCode;
		 this.reportProc = reportProc;
		 this.docProc = docProc;
	 }

	 public int getStatusCode () {
		 return statusCode;
	 }

	 public IReportGeneratorProcessor getReportGeneratorProcessor () {
		 return reportProc;
	 }

	 public IDocumentWorklfowProcessor getDocumentWorklfowProcessor () {
		 return docProc;
	 }

	 private int statusCode;
	 private IReportGeneratorProcessor reportProc;
	 private IDocumentWorklfowProcessor docProc;
 }

 // Web
 public class DocumentWorklfowProcessor {
	 ...

	 public List <Operation> getAvailableOperations (DocumentStatus status) {
		 return status.getDocumentWorklfowProcessor (). getAvailableOperations ();
	 }

	 public void doOperation (DocumentStatus status, Operation op) throws APIException {
		 status.getDocumentWorklfowProcessor (). doOperation (op);
	 }
 }


 // Scheduled task
 public class ReportGenerationProcessor {
	 ...

	 public void generateReport (Report report) {
		 DocumentStatus status = report.getDocument (). GetStatus ();
		 ReportParams params = status.getReportGeneratorProcessor (). GetReportParams ();

		 // Generate report
	 }
 }




It can be seen that the client code has become much shorter and clearer.

Of course, the handlers themselves still need to be written, but now when adding a new enumeration item, its handlers will have to be written. Already there is no danger that they will forget about it (deliberate sabotage and leaving “for later” are not considered), at least at least they will think. And, as already mentioned, you can always start a default handler:



	 public IDocumentWorklfowProcessor getDocumentWorklfowProcessor () {
		 return (docProc! = null)?  docProc: DEFAULT_WORKFLOW_PROCESSOR;
	 }




PS I look forward to commenting on the validity of this approach. If you get enough votes, you are ready to implement this plugin for IntelliJ IDEA, Eclipse and NetBeans.



UPD. The section “Simplicity is power!” Was added in response to the corresponding post to show its place within this approach.



UPD 2. By numerous requests, added an example. Also at the beginning of the post added a brief description of the applicability of this solution.

Source: https://habr.com/ru/post/101280/



All Articles