Is there any alternate way to replace complex enum? - java

I have a complex enum class in my spring boot application which holds different status values for different systems.
package com.foo;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public enum Status {
FOO_STATUS("Status1" ,"status_1", "STATUS_1", "stat1"),
BAR_STATUS("Status2" ,"status_2", "STATUS_2", "stat2" ),
FOO1_STATUS("Status3" ,"status_3", "STATUS_3", "stat3" ),
BAR1_STATUS("Status4" ,"status_4", "STATUS_4", "stat4" ),
....
....
....
private final String system1Status;
private final String system2Status;
private final String system3Status;
private final String system4Status;
private static Map<String, String> statusMap;
Status(String system1Status, String system2Status, String system3Status, String system4Status) {
this.system1Status = system1Status;
this.system2Status = system2Status;
this.system3Status = system3Status;
this.system4Status = system4Status;
}
public String getSystem1Status() {
return system1Status;
}
public String getSystem2Status() {
return system2Status;
}
public String getSystem3Status() {
return system3Status;
}
public String getSystem4Status() {
return system4Status;
}
private static void initializeMapping() {
statusMap = new HashMap<>();
for (Status map : Status.values()) {
statusMap.put(map.getSystem1Status(), map.getSystem2Status());
}
}
public static String getSystem2StatusForSytem1Status(String status) {
if (statusMap == null) {
initializeMapping();
}
if (statusMap.containsKey(status)) {
return statusMap.get(status);
}
return null;
}
public static String getSystem3StatusForSytem1Status(String status) {
....
}
public static String getSystem4StatusForSytem2Status(String status) {
....
}
public static String getSystem3StatusForSytem2Status(String status) {
....
}
....
....
}
The enum holds status string mapping for various systems. It also has methods to get different system status by supplying the current system status.
Ex: We can get System1 status by sending the System 2 status value.
As the enum is getting more complex , is there any alternate way to hold this static data?
PS: I know this can be moved to a reference table in DB, But I am looking for any alternate within the code (like loading from yaml file).

The concern about the enum getting more and more complex is only valid if that complexity is accidental, not inherent. Otherwise, switching to a different approach would just move that complexity elsewhere (which kind of seems to be the case in your example). I think it makes sense to keep the enum (even if it grows complex) iif the following conditions are met:
There is no reasonable scenario in which you would want/need to account for new statuses or new mappings (or drop existing ones) without changing the code.
You rely on at least some enum features available out of the box, so you would have to reimplement those by hand. E.g. values() listed in a determinate order, valueOf() used with canonical String labels, ordinal() to infer position, compareTo(), name(), serialization, etc.
You use the enum constants polymorphically (and maybe you need to alter the behavior for some of them without a full-fledged class hierarchy) or you want to leverage the compiler check for exhaustive case branches in switch expressions (with newer java versions).

Related

What is the name of the design pattern where you have a single-value, strongly typed property container?

Looking for the name of the design pattern by which you have a POJO with public final "properties" where the property acts as a holder/wrapper for a certain type of value and contains the getter/setter for that value as well as potentially some additional logic.
This differs from the "Property Container" design pattern where you have a single properties container which contains many types, as this only holds a single value and can thus enjoy the benefits of remaining strongly typed.
An example use:
public class User extends Entity<User> {
private static final Structure<User> STRUCTURE = Structure.of(User.class, User::new)
.addPrimaryKey("user_id", UUID).property((e) -> e.userID).setDefault(() -> UUID()).build()
.addIndex("username", VCHARS_50).property((e) -> e.username).build()
.addIndex("email", VCHARS_255).property((e) -> e.email).build()
.add("password", VCHARS_255).property((e) -> e.passwordHash).build()
.add("privacy_policy_accepted", EPOCH).property((e) -> e.ppAccepted).setDefault(() -> now()).build()
.add("tos_accepted", EPOCH).property((e) -> e.tosAccepted).setDefault(() -> now()).build()
.add("registration_date", EPOCH).property((e) -> e.registrationDate).setDefault(() -> now()).build()
.buildFor(Schema.MASTER);
public final Property<UUID> userID = new Property<>();
public final Property<String> username = new Property<>();
public final Property<String> email = new Property<>();
public final Property<String> passwordHash = new Property<>();
public final Property<Long> ppAccepted = new Property<>();
public final Property<Long> tosAccepted = new Property<>();
public final Property<Long> registrationDate = new Property<>();
public User() {
super(STRUCTURE);
}
public void hashAndSetPassword(String password) {
this.passwordHash.set(Argon2Factory.create(Argon2Types.ARGON2id).hash(3, 102800, 1, password.toCharArray()));
}
public boolean verifyPassword(String attempt) {
return Argon2Factory.create(Argon2Types.ARGON2id).verify(passwordHash.get(), attempt.toCharArray());
}
}
With each entity property using the following:
public class Property<T> {
private T currentValue;
public void set(T newValue) {
this.currentValue = newValue;
}
public T get() {
return this.currentValue;
}
#Override
public boolean equals(Object o) {
return Objects.equals(currentValue, o);
}
#Override
public int hashCode() {
return Objects.hashCode(currentValue);
}
#Override
public String toString() {
return String.valueOf(currentValue);
}
}
We can extend or modify this Properties class and make it do more useful stuff for us, like have it record an original value, provided on creation (pulled from a database) and allow it to self-report on whether the current value of the property differs from what it was originally. Useful for determining which columns need to be updated in a database.
Most notably, this eliminates the need to create getters and setters for every new property because the Property has that functionality already. Moreover, the getters/setters are able to be overridden per-property if additional logic is needed.
I naturally ended up using this design while aiming for a more broad goal of eliminating the use of reflection/annotation processors and other black magic from my web framework. However, I’m having difficulty finding it on the internet so that I might be able to research its potential deficiencies.
This kind of wrapper "variable" is used for Observable properties like StringProperty and such. Its primary use is to hold state and have change listeners, binding in general.
It is fruitfully used, like in JavaFX. And as you mentioned, in entity frameworks. But it definitely is stateful, non-functional, mutable.
A pattern name I cannot find, and I think the gang of 4 would haunt one, if calling this a pattern, other than State.
Credit to #Michael and #Kayaman for answering in the comments: This is not a known design pattern, contrary to my expectations.
In other words, there is not a name by which people generally know to refer to what I’m calling a "Property" nor the design I’m suggesting which assumes public getters and setters are desired and uses public final fields to expose a wrapper which provides them.
This is likely because, as Kayaman pointed out, it’s pretty heavy while being not terribly useful.

Enum Localization generic approach

Enum
public enum EmployeeStatus {
ACTIVE, IN_ACTIVE
}
In callers scattered all over the application whicg get and set the enum like below. Here are examples
Caller_1
if(employee.getStatus() == EmployeeStatus.STATUS.SUBMITTED) {
}
Caller_2
employee.setStatus(EmployeeStatus.STATUS.SUBMITTED)
Problem
I need to implement the internationalization so that end user sees the employee status as per locale. So when i set the status for french locale
it should set the value from right resource bundel. Is there a way i can achieve this without changing the caller code. Here is the solution
I can think of
My proposed solution :-
public enum EmployeeStatus {
ACTIVE, IN_ACTIVE
public static String toString() {
return I18n.getMessage("label." + this);
}
}
public final class I18n {
private I18n() {
}
private static ResourceBundle bundle;
public static String getMessage(String key) {
if(bundle == null) {
bundle = ResourceBundle.getBundle("path.to.i18n.messages");
}
return bundle.getString(LocaleContextHolder.getLocale());
}
}
With this approach I need to add toString method in every Enum without change in caller ? Is there a better generic approach spring provides ?
I am using spring 4. See if spring can help here .

Strategy Pattern too many if statements

A user enters a code and the type of that code is determined by regular expressions. There are many different type of codes, such as EAN, ISBN, ISSN and so on. After the type is detected, a custom query has to be created for the code. I thought it might be a good idea to create a strategy for type, but with time it feels wrong.
public interface SearchQueryStrategie {
SearchQuery createSearchQuery(String code);
}
-
public class IssnSearchQueryStrategie implements SearchQueryStrategie {
#Override
public SearchQuery createSearchQuery(final String code) {
// Create search query for issn number
}
}
-
public class IsbnSearchQueryStrategie implements SearchQueryStrategie {
#Override
public SearchQuery createSearchQuery(final String code) {
// Create search query for ISBN number
}
}
-
public class EanShortNumberSearchQueryStrategie implements SearchQueryStrategie {
#Override
public SearchQuery createSearchQuery(final String code) {
// Create search query for ean short number
}
}
-
public class TestApplication {
public static void main(final String... args) {
final String code = "1144875X";
SearchQueryStrategie searchQueryStrategie = null;
if (isIssn(code)) {
searchQueryStrategie = new IssnSearchQueryStrategie();
} else if (isIsbn(code)) {
searchQueryStrategie = new IsbnSearchQueryStrategie();
} else if (isEan(code)) {
searchQueryStrategie = new EanShortNumberSearchQueryStrategie();
}
if (searchQueryStrategie != null) {
performSearch(searchQueryStrategie.createSearchQuery(code));
}
}
private SearchResult performSearch(final SearchQuery searchQuery) {
// perform search
}
// ...
}
I have to say that there are many more strategies. How should I dispatch the code to the right strategy?
My second approach was to put a boolean method into every strategy to decide if the code is correct for that strategy.
public class TestApplication {
final SearchQueryStrategie[] searchQueryStrategies = {new IssnSearchQueryStrategie(), new IsbnSearchQueryStrategie(),
new EanShortNumberSearchQueryStrategie()};
public static void main(final String... args) {
final String code = "1144875X";
for (final SearchQueryStrategie searchQueryStrategie : searchQueryStrategie) {
if (searchQueryStrategie.isRightCode(code)) {
searchQueryStrategie.createSearchQuery(code);
break;
}
}
}
private SearchResult performSearch(final SearchQuery searchQuery) {
// perform search
}
// ...
}
How would you solve this problem? Is the strategy pattern the right one for my purposes?
If you are using Java 8 and you can profit from the functional features I think one Enum will be sufficient.
You can avoid using if/else statements by mapping each type of code with a Function that will return the query that needs to be executed:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
public enum CodeType
{
EAN("1|2|3"),
ISBN("4|5|6"),
ISSN("7|8|9");
String regex;
Pattern pattern;
CodeType(String regex)
{
this.regex = regex;
this.pattern = Pattern.compile(regex);
}
private static Map<CodeType, Function<String, String>> QUERIES =
new HashMap<>();
static
{
QUERIES.put(EAN, (String code) -> String.format("Select %s from EAN", code));
QUERIES.put(ISBN, (String code) -> String.format("Select %s from ISBB", code));
QUERIES.put(ISSN, (String code) -> String.format("Select %s from ISSN", code));
}
private static CodeType evalType(String code)
{
for(CodeType codeType : CodeType.values())
{
if (codeType.pattern.matcher(code).matches())
return codeType;
}
// TODO DON'T FORGET ABOUT THIS NULL HERE
return null;
}
public static String getSelect(String code)
{
Function<String, String> function = QUERIES.get(evalType(code));
return function.apply(code);
}
}
And in the main you can test your query:
public class Main
{
public static void main(String... args)
{
System.out.println(CodeType.getSelect("1"));
// System.out: Select 1 from EAN
System.out.println(CodeType.getSelect("4"));
// System.out: Select 4 from ISBB
System.out.println(CodeType.getSelect("9"));
// System.out: Select 9 from ISSN
}
}
I usually tend to keep the code as compact as possible.
Some people dislike enums, so I believe you can use a normal class instead.
You can engineer further the way you obtain the QUERIES (selects), so instead of having String templates you can have a Runnable there.
If you don't want to use the the functional aspects of Java 8 you can use Strategy objects that are associated with each type of code:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
public enum CodeType2
{
EAN("1|2|3", new StrategyEAN()),
ISBN("4|5|6", new StrategyISBN()),
ISSN("7|8|9", new StrategyISSN());
String regex;
Pattern pattern;
Strategy strategy;
CodeType2(String regex, Strategy strategy)
{
this.regex = regex;
this.pattern = Pattern.compile(regex);
this.strategy = strategy;
}
private static CodeType2 evalType(String code)
{
for(CodeType2 codeType2 : CodeType2.values())
{
if (codeType2.pattern.matcher(code).matches())
return codeType2;
}
// TODO DON'T FORGET ABOUT THIS NULL HERE
return null;
}
public static void doQuery(String code)
{
evalType(code).strategy.doQuery(code);
}
}
interface Strategy { void doQuery(String code); }
class StrategyEAN implements Strategy {
#Override
public void doQuery(String code)
{
System.out.println("EAN-" + code);
}
}
class StrategyISBN implements Strategy
{
#Override
public void doQuery(String code)
{
System.out.println("ISBN-" + code);
}
}
class StrategyISSN implements Strategy
{
#Override
public void doQuery(String code)
{
System.out.println("ISSN-" + code);
}
}
And the main method will look like this:
public class Main
{
public static void main(String... args)
{
CodeType2.doQuery("1");
CodeType2.doQuery("4");
CodeType2.doQuery("9");
}
}
So, The strategy pattern is indeed the right choice here, but strategy by itself is not enough. You have several options:
Use a Factory with simple if/else or switch. It's ugly, error prone to extend with new strategies, but is simple and quick to implement.
Use a registry. During the application initialization phase you can register in a registry each SearchQueryStratgeyFactory with the right code. For instance if you use a simple Map you can just do :
strategyRegistry.put("isbn", new IsbnSearchStrategyFactory());
strategyRegistry.put("ean", new EanSearchStrategyFactory());
.... and so on
Then when you need to get the right strategy you just get() the strategy factory from the map using the code id. This approach is better if you have a lot of strategies, but it requires an aditional iitialization step during the application startup.
Use a service locator. ServiceLocator is a pattern that enables the dynamic lookup of implementations. Java comes with an implementation of the ServiceLocator pattern -> the infamous ServiceLoader class. This is my favourite approach because it allows for complete decoupling of the consumer and implementation. Also using the service locator you can easily add new strategies without having to modify the existing code. I won't explain how to use the ServiceLoader - there is plenty of information online. I'll just mention that using the service locator you'll need to implement a "can process such codes ?" logic in each strategy factory. For instance if the factory cannot create a strategy for "isbn" then return null and try with the next factory.
Also note that in all cases you work with factories that produce the strategy implementations.
PS: It's strategy not strategie :)
Your approach is not the Strategy Pattern. Strategy Pattern is all about customizing behavior of an object (Context in terms of this pattern) by passing alternative Strategy object to it. By this way, we don't need to modify the source code of the Context class but still can customize the behavior of objects instanced from it.
Your problem is somewhat related to the Chain of Responsibility (CoR) Pattern where you have a request (your code) and need to figure out which SearchQueryStrategie in a predefined list should handle the request.
The second approach -- using array -- that you mentioned is fine. However, to make it usable in production code, you must have another object -- let's say Manager -- that manages the array and is responsible to find the relevant element for each request. So your client code have to depend on two objects: the Manager and the result SearchQueryStrategie. As you can see, the source code of Manager class tend to be changed frequently because new implementations of SearchQueryStrategie may come. This might make your client annoyed.
That's why the CoR Pattern uses the linked list mechanism instead of array. Each SearchQueryStrategie object A would hold a reference to a next SearchQueryStrategie B. If A cannot handle the request, it will delegate to B (it can even decorate the request before delegating). Of course, somewhere still must know all kinds of strategies and create a linked list of SearchQueryStrategie, but your client will then depend only on a SearchQueryStrategie object (the head one of the list).
Here is the code example:
class SearchQueryConsumer {
public void consume(SearchQuery sq) {
// ...
}
}
abstract class SearchQueryHandler {
protected SearchQueryHandler next = null;
public void setNext(SearchQueryHandler next) { this.next = next; }
public abstract void handle(String code, SearchQueryConsumer consumer);
}
class IssnSearchQueryHandler extends SearchQueryHandler {
#Override
public void handle(String code, SearchQueryConsumer consumer) {
if (issn(code)) {
consumer.consume(/* create a SearchQuery */);
} else if (next != null) {
next.handle(code, consumer);
}
}
private boolean issn(String code) { ... }
}
What i recommend is using the Factory pattern. It describes and handles your scenario better.
Factory Pattern
You can design in the following way (using concepts of factory DP and polymorphism):
Code as interface.
ISSNCode, ISBNCode and EANCode as concrete classes
implementing Code interface, having single-arg constructor taking text as String.
Code has method getInstanceOfCodeType(String text) which returns an instance of a sub-class of Code (decided by checking the type of text passed to it). Let's say the returned value be code
Class SearchQueryStrategieFactory with
getSearchQueryStrategie(code) method. It consumes the returned value from step 3, and generates different
instances of SearchQueryStrategie subclasses based on code type using new operator and, then returns the same.
So, you need to call two methods getInstanceOfCodeType(text) and getSearchQueryStrategie(code) from anywhere.
Instead of implicitly implementing the factory inside main, keep the whole factory code separate, to make it easily maintainable and extensible .

How to improve the code quality to see if a string matches either one of the regex's Java

In one of my projects I need to compare the URI with several regex patterns(15+ regex patterns). Currently I have used a if ladder to see if either one of them gets matched and there onward the logical part of the code is executed.
Glimpse of the code now:
if (uri.matches(Constants.GET_ALL_APIS_STORE_REGEX)) {
long lastUpdatedTime = InBoundETagManager.apisGet(null, null, tenantDomain, null);
String eTag = ETagGenerator.getETag(lastUpdatedTime);
if (eTag.equals(ifNoneMatch)) {
message.getExchange().put("ETag", eTag);
generate304NotModifiedResponse(message);
}
message.getExchange().put("ETag", eTag);
}
else if (uri.matches(Constants.GET_API_FOR_ID_REGEX)) { // /apis/{apiId}
apiId = UUIDList.get(0);
String requestedTenantDomain = RestApiUtil.getRequestedTenantDomain(tenantDomain);
long lastUpdatedTime = InBoundETagManager.apisApiIdGet(apiId, requestedTenantDomain, uri);
String eTag = ETagGenerator.getETag(lastUpdatedTime);
handleInterceptorResponse(message, ifNoneMatch, eTag);
}
else if (uri.matches(Constants.GET_SWAGGER_FOR_API_ID_REGEX)) { // /apis/{apiId}/swagger
apiId = UUIDList.get(0);
long lastUpdatedTime = InBoundETagManager.apisApiIdSwaggerGet(apiId, tenantDomain);
String eTag = ETagGenerator.getETag(lastUpdatedTime);
if (lastUpdatedTime == 0L) {
log.info("No last updated time available for the desired API swagger json file");
}
handleInterceptorResponse(message, ifNoneMatch, eTag);
}
Can someone please introduce me with a more neat and clever way of doing this regex matching thing?
One url-type(regex) = one handler = one class. This way would be much easier to read and support especially if you have 15 regex checks.
interface URLHandler {
void handle();
boolean isSupported(String url);
}
class GetAllApisStoreHandler implements URLHandler{
private static final Pattern GET_ALL_API_STORE_PATTERN = Pattern.compile(GET_ALL_APIS_STORE_REGEX);
public boolean isSupported(String url) {
return GET_ALL_API_STORE_PATTERN.matcher(url).matches();
}
public void handle(...) {
...
}
}
class GetApiIdHandler implements URLHandler{
private static final Pattern GET_API_ID_REGEX = Pattern.compile(GET_API_ID_REGEX);
public boolean isSupported(String url) {
return GET_API_ID_PATTERN.matcher(url).matches();
}
public void handle(...) {
...
}
}
class GetApiIdHandler implements URLHandler{
private static final Pattern GET_SWAGGER_FORAPI_ID_PATTERN = Pattern.compile(GET_SWAGGER_FOR_API_ID_REGEX);
public boolean isSupported(String url) {
return GET_SWAGGER_FORAPI_ID_PATTERN.matcher(url).matches();
}
public void handle(...) {
...
}
}
class Main {
private List<URLHandler> urlHandlers;
public void method(){
...
for (URLHandler handler : urlHandlers) {
if(handler.isSupported(url)) {
handler.handle(arg1, arg2, arg3, ...);
}
}
...
}
}
Using multiple classes as #KonstantinLabun proposed is probably the way to go(*), but it shouldn't lead to much code duplication. So use an abstract class instead of (or in addition to an interface). Or (mis)use default methods.
abstract class URLHandler {
abstract void handle();
abstract Pattern urlPattern():
final boolean isSupported(String url) {
return urlPattern().matcher(url).matches();
}
}
class GetAllApisStoreHandler extends URLHandler{
private static final Pattern URL_PATTERN =
Pattern.compile(Constants.GET_ALL_APIS_STORE_REGEX);
Pattern urlPattern() {
return URL_PATTERN;
}
public void handle(...) {
...
}
}
There's no need to invent names for the PATTERN as its scope identified it already. The static field exists only as an optimization, so that the Pattern don't get compiled for each match.
(*) There's nothing wrong with a single class, as long as it's concise (I like spaghetti except in code) and doesn't leak implementation details. There's nothing wrong with multiple classes (except maybe on Android as 50 kB per class might matter) as long as they don't lead to code bloat. An enum is sometimes a good solution, too.
Explanation of abstract class vs. interface
An interface forces you to implement its methods(**), which may quickly lead to duplication. It's advantage is multiple inheritance and some conceptual purity.
An abstract class allows you to gather the common parts. But there's no dilemma, you can do both, see e.g., interface List and abstract class AbstractList.
(**) Since Java 8, an interface can have default methods, so this is no more true. Assuming you want to use them for this purpose. It can't declare any state, but it can access the state of the object. For example, my above URLHandler could be such an interface. There are still disadvantages, e.g., methods must be public and mustn't be final.

fuzzy implementation for capturing specific strings

I am going to develop a web crawler using java to capture hotel room prices from hotel websites.
In this case I want to capture room price with the room type and the meal type, so my algorithm should be intelligent to handle that.
For example:
Room type: Deluxe
Meal type: HalfBoad
price : $20.00
The main problem is room prices can be in different ways in different hotel sites. So my algorithm should be independent from hotel sites.
I am plan to use above room types and meal types as a fuzzy sets and compare the words in webpage with above fuzzy sets using a suitable membership function.
Anyone experienced with this? or have an idea for my problem?
There are two ways to approach this problem:
You can customize your crawler to understand the formats used by different Websites; or
You can come up with a general ("fuzzy") solution.
(1) will, by far, be the easiest. Ideally you want to create some tools that make this easier so you can create a filter for any new site in minimal time. IMHO your time will be best spent with this approach.
(2) has lots of problems. Firstly it will be unreliable. You will come across formats you don't understand or (worse) get wrong. Second, it will require a substantial amount of development to get something working. This is the sort of thing you use when you're dealing with thousands or millions of sites.
With hundreds of sites you will get better and more predictable results with (1).
As with all problems, design can let you deliver value adapt to situations you haven't considered much more quickly than the general solution.
Start by writing something that parses the data from one provider - the one with the simplest format to handle. Find a way to adapt that handler into your crawler. Be sure to encapsulate construction - you should always do this anyway...
public class RoomTypeExtractor
{
private RoomTypeExtractor() { }
public static RoomTypeExtractor GetInstance()
{
return new RoomTypeExtractor();
}
public string GetRoomType(string content)
{
// BEHAVIOR #1
}
}
The GetInstance() ,ethod lets you promote to a Strategy pattern for practically free.
Then add your second provider type. Say, for instance, that you have a slightly more complex data format which is a little more prevalent than the first format. Start by refactoring what was your concrete room type extractor class into an abstraction with a single variation behind it and have the GetInstance() method return an instance of the concrete type:
public abstract class RoomTypeExtractor
{
public static RoomTypeExtractor GetInstance()
{
return SimpleRoomTypeExtractor.GetInstance();
}
public abstract string GetRoomType(string content);
}
public final class SimpleRoomTypeExtractor extends RoomTypeExtractor
{
private SimpleRoomTypeExtractor() { }
public static SimpleRoomTypeExtractor GetInstance()
{
return new SimpleRoomTypeExtractor();
}
public string GetRoomType(string content)
{
// BEHAVIOR #1
}
}
Create another variation that implements the Null Object pattern...
public class NullRoomTypeExtractor extends RoomTypeExtractor
{
private NullRoomTypeExtractor() { }
public static NullRoomTypeExtractor GetInstance()
{
return new NullRoomTypeExtractor();
}
public string GetRoomType(string content)
{
// whatever "no content" behavior you want... I chose returning null
return null;
}
}
Add a base class that will make it easier to work with the Chain of Responsibility pattern that is in this problem:
public abstract class ChainLinkRoomTypeExtractor extends RoomTypeExtractor
{
private final RoomTypeExtractor next_;
protected ChainLinkRoomTypeExtractor(RoomTypeExtractor next)
{
next_ = next;
}
public final string GetRoomType(string content)
{
if (CanHandleContent(content))
{
return GetRoomTypeFromUnderstoodFormat(content);
}
else
{
return next_.GetRoomType(content);
}
}
protected abstract bool CanHandleContent(string content);
protected abstract string GetRoomTypeFromUnderstoodFormat(string content);
}
Now, refactor the original implementation to have a base class that joins it into a Chain of Responsibility...
public final class SimpleRoomTypeExtractor extends ChainLinkRoomTypeExtractor
{
private SimpleRoomTypeExtractor(RoomTypeExtractor next)
{
super(next);
}
public static SimpleRoomTypeExtractor GetInstance(RoomTypeExtractor next)
{
return new SimpleRoomTypeExtractor(next);
}
protected string CanHandleContent(string content)
{
// return whether or not content contains the right format
}
protected string GetRoomTypeFromUnderstoodFormat(string content)
{
// BEHAVIOR #1
}
}
Be sure to update RoomTypeExtractor.GetInstance():
public static RoomTypeExtractor GetInstance()
{
RoomTypeExtractor extractor = NullRoomTypeExtractor.GetInstance();
extractor = SimpleRoomTypeExtractor.GetInstance(extractor);
return extractor;
}
Once that's done, create a new link for the Chain of Responsibility...
public final class MoreComplexRoomTypeExtractor extends ChainLinkRoomTypeExtractor
{
private MoreComplexRoomTypeExtractor(RoomTypeExtractor next)
{
super(next);
}
public static MoreComplexRoomTypeExtractor GetInstance(RoomTypeExtractor next)
{
return new MoreComplexRoomTypeExtractor(next);
}
protected string CanHandleContent(string content)
{
// Check for presence of format #2
}
protected string GetRoomTypeFromUnderstoodFormat(string content)
{
// BEHAVIOR #2
}
}
Finally, add the new link to the chain, if this is a more common format, you might want to give it higher priority by putting it higher in the chain (the real forces that govern the order of the chain will become apparent when you do this):
public static RoomTypeExtractor GetInstance()
{
RoomTypeExtractor extractor = NullRoomTypeExtractor.GetInstance();
extractor = SimpleRoomTypeExtractor.GetInstance(extractor);
extractor = MoreComplexRoomTypeExtractor.GetInstance(extractor);
return extractor;
}
As time passes, you may want to add ways to dynamically add new links to the Chain of Responsibility, as pointed out by Cletus, but the fundamental principle here is Emergent Design. Start with high quality. Keep quality high. Drive with tests. Do those three things and you will be able to use the fuzzy logic engine between your ears to overcome almost any problem...
EDIT
Translated to Java. Hope I did that right; I'm a little rusty.

Categories