Writing custom predicate to handle case insensitive use cases for grouped queries in AEM - aem

so I have an AEM query that looks like this:
nodename=master
group.p.or=true
group.1_property=title
group.1_property.value=%article%
group.2_property=body
group.2_property.value=%article%
group.1_property.operation=like
Now this gives me all the results where the property contains article but not if it contains Article or aRticle etc.
I would want to know if there's already an inbuilt predicate like `[13:46] Sahil, Shivam
Oh okay actually I want to make a group query case insensetive:
nodename=mastergroup.p.or=truegroup.1_property=titlegroup.1_property.value=%article%
group.2_property=bodygroup.2_property.value=%article%group.1_property.operation=like
So this gives me result wherever the title contains article however not if it contains Article or aRticle etc etc
I want to know if we already have a predicate for which I can add something like:
group.1_property.caseSenstivity=false
otherwise I would be interested in extending property predicate one like:
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to
*/
package com.perficient.adobe.predicates;
import java.util.Locale;
import java.util.Optional;
import com.day.cq.search.Predicate;
import com.day.cq.search.eval.AbstractPredicateEvaluator;
import com.day.cq.search.eval.EvaluationContext;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Component(factory = "com.day.cq.search.eval.PredicateEvaluator/equalsIgnoreCase")
public class CaseInsensitiveEquals extends AbstractPredicateEvaluator {
private static final Logger log = LoggerFactory.getLogger(CaseInsensitiveEquals.class);
static final String PREDICATE_PROPERTY = "property";
static final String PREDICATE_VALUE = "value";
static final String PREDICATE_LOCALE = "locale";
#Override
public String getXPathExpression(Predicate predicate, EvaluationContext context) {
log.debug("Evaluating predicate: {}", predicate);
String property = predicate.get(PREDICATE_PROPERTY);
Locale locale = Optional.ofNullable(predicate.get(PREDICATE_LOCALE)).map(lt -> Locale.forLanguageTag(lt))
.orElse(Locale.getDefault());
String value = predicate.get(PREDICATE_VALUE).toLowerCase(locale).replace("'", "''");
String query = String.format("fn:lower-case(#%s)='%s'", property, value);
log.debug("Generated query: {}", query);
return query;
}
}
Can someone help me extending property predicate to caseInsensetiveProperty predicate where I can also specify if the search should have case insenstivity or not?

Related

How to make Dart detects enum extensions from different file globally?

I wanted to create an extension for generated enum from amplify model generator, so I created the extension in different file (because the enum is autogenerated) but Dart is not detecting my extension when I try to using it.
Auto Generated Enum PetsType.dart
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
// NOTE: This file is generated and may not follow lint rules defined in your app
// Generated files can be excluded from analysis in analysis_options.yaml
// For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis
// ignore_for_file: public_member_api_docs, file_names, unnecessary_new, prefer_if_null_operators, prefer_const_constructors, slash_for_doc_comments, annotate_overrides, non_constant_identifier_names, unnecessary_string_interpolations, prefer_adjacent_string_concatenation, unnecessary_const, dead_code
enum PetsType { DOG, CAT, BIRD, HAMSTER, FISH, OTHER }
pets_type_extension.dart
extension EnumPetCategoryExtension on PetsType {
Color get color => _getColor(this);
IconData get icon => _getIcon(this);
String get valueString => _getUiValue(this);
Color _getColor(PetsType category) {
switch (category) {
case PetsType.DOG:
return const Color(0xffFFB56B);
case PetsType.CAT:
return const Color(0xffFFD977);
case PetsType.BIRD:
return const Color(0xff826491);
case PetsType.HAMSTER:
return const Color(0xffA66551);
case PetsType.FISH:
return const Color(0xff6DB5AD);
case PetsType.OTHER:
return const Color(0xffFFB56B);
}
}
IconData _getIcon(PetsType category) {
switch (category) {
case PetsType.DOG:
return Icons.ac_unit;
case PetsType.CAT:
return Icons.account_tree_outlined;
case PetsType.BIRD:
return Icons.airline_seat_legroom_reduced;
case PetsType.HAMSTER:
return Icons.bookmark_add_sharp;
case PetsType.FISH:
return Icons.bed;
case PetsType.OTHER:
return Icons.computer_sharp;
}
}
String toShortString() {
return toString().split('.').last;
}
String toUpperCase() {
return toShortString().toUpperCase();
}
String _getUiValue(PetsType category) {
switch (category) {
case PetsType.DOG:
return LocaleKeys.petTypes_dog.tr();
case PetsType.CAT:
return LocaleKeys.petTypes_cat.tr();
case PetsType.BIRD:
return LocaleKeys.petTypes_bird.tr();
case PetsType.HAMSTER:
return LocaleKeys.petTypes_hamster.tr();
case PetsType.FISH:
return LocaleKeys.petTypes_fish.tr();
case PetsType.OTHER:
return LocaleKeys.petTypes_others.tr();
}
}
}
ERROR
extension unrecognized
Note: Pet model is using PetsType enum as a property type.
Extension methods are syntactic sugar. They are not part of the class (or in this case, enum) interface and can't be automatically detected and used. (How would the Dart compiler even know where to look to detect available extensions?)
What you could do is:
Move the autogenerated enum definition to a private .dart file (the convention is to put private implementation files in a src/ subdirectory).
Put your extension in a public file that imports that private file and exports the enum.
Expect that consumers import the public file.
That should (mostly) ensure that consumers of the enum also consume the extension at the same time. (It wouldn't be guaranteed; pathological consumers could import the private file directly if they explicitly choose to, and they also could explicitly hide the extension when importing the public file. However, fighting people intentionally trying to be pathological is a waste of time anyway.)

AssertJ: How to build custom and nested assertions

I need to compare one PatientDTO dto object with other one PatientModel model object.
Both classes are quite similar:
class PatientDTO {
private String name;
private List<AddressDTO> address;
// Constructors, getters and setters
}
class PatientModel {
private String id;
private String nameElement;
private List<AddressModel> addressElement;
// Constructors, getters and setters
}
class AddressDTO {
private String city;
private String country;
private List<String> linesElement;
// Constructors, getters and setters
}
class AddressModel {
private String city;
private String countryElement;
private List<String> linesElement;
// Constructors, getters and setters
}
Main differences are:
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
Other issue I like to solve, is that:
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
I'd like to build an assertion like this:
PatientDTO patientDTO;
PatientModel patientModel;
assertThat(patientDTO).isEqual(patientModel);
Shortly:
Should I build a custom assertion?
Should I have an assertion for Address an other one for Patient containing previous Address assertion? How could I get this?
What aboud Address assertion for Patient, Organization
What I want to avoid is code like this:
assertThat(patientDTO).anySatisfy(p->{
assertThat(p.getName()).withFailMessage("expected name: "+ p.getAddress().getCity()).isEqualTo(patientModel.getNameElement());
assertThat(p.getAddress().getCity()).withFailMessage("expected city: "+ p.getAddress().getCity()).isEqualTo(patientModel.getCityElement());
assertThat(p.getAddress().getCountry()).withFailMessage("expected country: "+ p.getAddress().getCountry()).isEqualTo(patientModel.getCountryElement());
...
}
);
I want to avoid above code since Patient classes are really large. Here I've shorted them for clarity purpouses.
Any ideas?
The field-by-field recursive comparison could help for this purpose:
PatientDTO patientDTO = new PatientDTO(...);
PatientModel patientModel = new PatientModel(...);
assertThat(patientDTO).usingRecursiveComparison()
.isEqualTo(patientModel);
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
There are a few methods that can be used to tune the comparison and ignore fields:
Directly with ignoringFields(String…​ fieldsToIgnore)
By regexes with ignoringFieldsMatchingRegexes(String…​ regexes)
By types with ignoringFieldsOfTypes(Class…​ typesToIgnore)
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
This is currently not supported and was also asked in https://stackoverflow.com/a/70381488/9714611. We plan to raise a feature request about it and I will update the answer once the issue link is ready.
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
If the target is always isEqualTo, probably a custom assertion implementation is not needed as long as the limitation of the recursive comparison about not being able to compare fields with different names is not a show-stopper. These fields would require ad-hoc comparison until a better solution is available.
If the target is to provide assertions in a domain-specific language, like:
assertThat(patientDTO).hasAddress(addressDTO);
then a custom assertion implementation can be added.
Also, there is an assertions generator with plugins for Maven and Gradle that can be used to generate assertions based on the class attributes.

Class Design - Object Oriented Programming Question

This was asked during an interview.
There are different manufacturers of buses. Each bus has got different models and each model has only 2 variants. So different manufacturers have different models with only 2 variants. The interviewer asked me to design a standalone program with just classes. She mentioned that I should not think about databases and I didn't have to code them. For example, it could be a console based program with inputs and outputs.
The manufacturers, models and variants information should be held in memory (hard-coded values were fine for this standalone program). She wanted to observe the classes and my problem solving approach.
She told me to focus on implementing three APIs or methods for this system.
The first one was to get information about a particular bus. Input would be manufacturer name, model name and variant name. Given these three values, the information about a particular bus such as its price, model, year, etc should be shown to the client.
The second API would be to compare two buses and the output would be to list the features side by side, probably in a tabular format. Input would be the same as the one for the first API i.e. manufacturer name, model name and variant name for both the buses.
The third one would be to search the buses by price (>= price) and get the list of buses which satisfy the condition.
She also added that the APIs should be scalable and I should design the solution with this condition on my mind.
This is how I designed the classes:
class Manufacturer {
private String name;
private Set<Model> models;
// some more properties related to manufacturer
}
class Model {
private String name;
private Integer year;
private Set<Variant> variants;
// some more properties related to model
}
class Variant {
private String name;
private BigDecimal price;
// some more properties related to variant
}
class Bus {
private String manufacturerName;
private String modelName;
private String variantName;
private Integer year;
private BigDecimal price;
// some more additional properties as required by client
}
class BusService {
// The first method
public Bus getBusInformation(String manufacturerName, String modelName, String variantName) throws Exception {
Manufacturer manufacturer = findManufacturer(manufacturerName);
//if(manufacturer == null) throw a valid exception
Model model = findModel(manufacturer);
// if(model == null) throw a valid exception
Variant variant = findVariant(model);
// if(variant == null) throw a valid exception
return createBusInformation(manufacturer, model, variant);
}
}
She stressed that there were only 2 variants and there wouldn't be any more variants and it should be scalable. After going through the classes, she said she understood my approach and I didn't have to implement the other APIs/methods. I realized that she wasn't impressed with the way I designed them.
It would be helpful to understand the mistake I made so that I could learn from it.
I interpreted your 3 requirements a bit differently (and I may be wrong). But it sounds like the overall desire is to be able to perform different searches against all Models, correct?
Also, sounds to me that as all Variants are Models. I suspect different variants would have different options, but nothing to confirm that. If so, a variant is just a subclass of a particular model. However, if variants end up having the same set of properties, then variant isn't anything more than an additional descriptor to the model.
Anyway, going on my suspicions, I'd have made Model the center focus, and gone with:
(base class)
abstract class Model {
private Manufacturer manufacturer;
private String name;
private String variant;
private Integer year;
private BigDecimal price;
// some more properties related to model
}
(manufacturer variants)
abstract class AlphaModel {
AlphaModel() {
this.manufacturer = new Manufacturer() { name = "Alpha" }
}
// some more properties related to this manufacturer
}
abstract class BetaModel {
BetaModel() {
this.manufacturer = new Manufacturer() { name = "Beta" }
}
// some more properties related to this manufacturer
}
(model variants)
abstract class AlphaBus : AlphaModel {
AlphaBus() {
this.name = "Super Bus";
}
// some more properties related to this model
}
abstract class BetaTruck : BetaModel {
BetaTruck() {
this.name = "Big Truck";
}
// some more properties related to this model
}
(actual instances)
class AlphaBusX : AlphaBus {
AlphaBusX() {
this.variant = "X Edition";
}
// some more properties exclusive to this variant
}
class AlphaBusY : AlphaBus {
AlphaBusY() {
this.variant = "Y Edition";
}
// some more properties exclusive to this variant
}
class BetaTruckF1 : BetaTruck {
BetaTruckF1() {
this.variant = "Model F1";
}
// some more properties exclusive to this variant
}
class BetaTruckF2 : BetaTruck {
BetaTruckF2() {
this.variant = "Model F2";
}
// some more properties exclusive to this variant
}
Then finally:
var data = new Set<Model> {
new AlphaBusX(),
new AlphaBusY(),
new BetaTruckF1(),
new BetaTruckF2()
}
API #1:
var result = data.First(x => x.manufacturer.name = <manufactuer>
&& x.name = <model>
&& x.variant = <variant>);
API #2:
var result1 = API#1(<manufacturer1>, <model1>, <variant1>);
var result2 = API#1(<manufacturer2>, <model2>, <variant2>);
API #3:
var result = data.Where(x => x.price >= <price>);
I would say your representation of the Bus class is severely limited, Variant, Model, Manufacturer should be hard links to the classes and not strings. Then a get for the name of each.
E.G from the perspective of Bus bus1 this.variant.GetName() or from the outside world. bus1.GetVariant().name
By limiting your bus to strings of it's held pieces, you're forced to do a lookup even when inside the bus class, which performs much slower at scale than a simple memory reference.
In terms of your API (while I don't have an example), your one way to get bus info is limited. If the makeup of the bus changes (variant changes, new component classes are introduced), it requires a decent rewrite of that function, and if other functions are written similarly then all of those two.
It would require some thought but a generic approach to this that can dynamically grab the info based on the input makes it easier to add/remove component pieces later on. This will be the are your interviewer was focusing on most in terms of advanced technical&language skills. Implementing generics, delegates, etc. here in the right way can make future upkeep of your API a lot easier. (Sorry I don't have an example)
I wouldn't say your approach here is necessarily bad though, the string member variables are probably the only major issue.

How to avoid anemic domain model with business logic in the form of rules

I am designing a system that has a simple Entity Framework backed domain object that has fields I need to update based on a series of rules - I want to implement these rules progressively (in an agile style) and as I am using EF I am sceptical about putting each rule into the domain object. However, I want to avoid writing "procedural code" and using anemic domain models. This all needs to be testable as well.
As an example, the object is:
class Employee {
private string Name;
private float Salary;
private float PensionPot;
private bool _pension;
private bool _eligibleForPension;
}
I need to build rules such as "if Salary is higher than 100,000 and _eligibleForPension is false then set _eligibleForPension as true" and "if _pension is true then set _eligibleForPension as true".
There are approximately 20 such rules and I am looking for advice whether they should be implemented in the Employee class or in something like an EmployeeRules class? My first thought was to create a separate class for each rule inheriting from "Rule" and then apply each rule to the Employee class, maybe using the Visitor pattern but I'd have to expose all the fields to the rules to do this so it feels wrong. Having each rule on the Employee class though doesn't feel quite right either. How would this be implemented?
The second concern is that the actual Employees are Entity Framework entities backed to the DB so I don't feel happy adding logic to these "Entities" - especially when I need to mock the objects for unit testing each rule. How could I mock them if they have the rules I'm testing on the same object?
I have been thinking of using AutoMapper to convert to a simpler domain object before applying rules but then need to manage the updates to the fields myself. Any advice on this too?
One approach is to make the rules inner classes of Employee. The benefit of this approach is that the fields can remain private. Also, the invocation of the rules can be enforced by the Employee class itself, ensuring that they are always invoked when needed:
class Employee
{
string id;
string name;
float salary;
float pensionPot;
bool pension;
bool eligibleForPension;
public void ChangeSalary(float salary)
{
this.salary = salary;
ApplyRules();
}
public void MakeEligibleForPension()
{
this.eligibleForPension = true;
ApplyRules(); // may or may not be needed
}
void ApplyRules()
{
rules.ForEach(rule => rule.Apply(this));
}
readonly static List<IEmployeeRule> rules;
static Employee()
{
rules = new List<IEmployeeRule>
{
new SalaryBasedPensionEligibilityRule()
};
}
interface IEmployeeRule
{
void Apply(Employee employee);
}
class SalaryBasedPensionEligibilityRule : IEmployeeRule
{
public void Apply(Employee employee)
{
if (employee.salary > 100000 && !employee.eligibleForPension)
{
employee.MakeEligibleForPension();
}
}
}
}
One problem here is that the Employee class has to contain all rule implementations. This isn't a major problem since the rules embody business logic associated with employee pensions and so they do belong together.
Business rules are usually an interesting topic. There may certainly be a difference between an aggregate / entity invariant and a business rule. Business rules may need external data and I wouldn't agree with a rule changing an aggregate / entity.
You should think specification pattern for rules. The rule should basically just return whether it was broken or not with possibly a description of sorts.
In your example SalaryBasedPensionEligibilityRule, as used by eulerfx, may need some PensionThreshold. This rule really does look more like a task since the rule really isn't checking any validity of the entity.
So I would suggest that rules are a decision mechanism and tasks are for changing the state.
That being said you probably want to ask the entity for advice here since you may not want to expose the state:
public class Employee
{
float salary;
bool eligibleForPension;
public bool QualifiesForPension(float pensionThreshold)
{
return salary > pensionThreshold && !eligibleForPension;
}
public void MakeEligibleForPension()
{
eligibleForPension = true;
}
}
This sticks with the command/query separation idea.
If you are building directly from your ORM objects and do not want to, or cannot, include all the behaviour then that is OK --- but it certainly would help :)

How do you refactor a God class?

Does anyone know the best way to refactor a God-object?
Its not as simple as breaking it into a number of smaller classes, because there is a high method coupling. If I pull out one method, i usually end up pulling every other method out.
It's like Jenga. You will need patience and a steady hand, otherwise you have to recreate everything from scratch. Which is not bad, per se - sometimes one needs to throw away code.
Other advice:
Think before pulling out methods: on what data does this method operate? What responsibility does it have?
Try to maintain the interface of the god class at first and delegate calls to the new extracted classes. In the end the god class should be a pure facade without own logic. Then you can keep it for convenience or throw it away and start to use the new classes only
Unit Tests help: write tests for each method before extracting it to assure you don't break functionality
I assume "God Object" means a huge class (measured in lines of code).
The basic idea is to extract parts of its functions into other classes.
In order to find those you can look for
fields/parameters that often get used together. They might move together into a new class
methods (or parts of methods) that use only a small subset of the fields in the class, the might move into a class containing just those field.
primitive types (int, String, boolean). They often are really value objects before their coming out. Once they are value object, they often attract methods.
look at the usage of the god object. Are there different methods used by different clients? Those might go in separate interfaces. Those intefaces might in turn have separate implementations.
For actually doing these changes you should have some infrastructure and tools at your command:
Tests: Have a (possibly generated) exhaustive set of tests ready that you can run often. Be extremely careful with changes you do without tests. I do those, but limit them to things like extract method, which I can do completely with a single IDE action.
Version Control: You want to have a version control that allows you to commit every 2 minutes, without really slowing you down. SVN doesn't really work. Git does.
Mikado Method: The idea of the Mikado Method is to try a change. If it works great. If not take note what is breaking, add them as dependency to the change you started with. Rollback you changes. In the resulting graph, repeat the process with a node that has no dependencies yet. http://mikadomethod.wordpress.com/book/
According to the book "Object Oriented Metrics in Practice" by Lanza and Marinescu, The God Class design flaw refers to classes that tend to centralize the intelligence of the system. A God Class performs too much work on its own, delegating only minor details to a set of trivial classes and using the data from other classes.
The detection of a God Class is based on three main characteristics:
They heavily access data of other simpler classes, either directly or using accessor methods.
They are large and complex
They have a lot of non-communicative behavior i.e., there is a low
cohesion between the methods belonging to that class.
Refactoring a God Class is a complex task, as this disharmony is often a cumulative effect of other disharmonies that occur at the method level. Therefore, performing such a refactoring requires additional and more fine-grained information about the methods of the class, and sometimes even about its inheritance context. A first approach is to identify clusters of methods and attributes that are tied together and to extract these islands into separate classes.
Split Up God Class method from the book "Object-Oriented Reengineering Patterns" proposes to incrementally redistribute the responsibilities of the God Class either to its collaborating classes or to new classes that are pulled out of the God Class.
The book "Working Effectively with Legacy Code" presents some techniques such as Sprout Method, Sprout Class, Wrap Method to be able to test the legacy systems that can be used to support the refactoring of God Classes.
What I would do, is to sub-group methods in the God Class which utilize the same class properties as inputs or outputs. After that, I would split the class into sub-classes, where each sub-class will hold the methods in a sub-group, and the properties which these methods utilize.
That way, each new class will be smaller and more coherent (meaning that all their methods will work on similar class properties). Moreover, there will be less dependency for each new class we generated. After that, we can further reduce those dependencies since we can now understand the code better.
In general, I would say that there are a couple of different methods according to the situation at hand. As an example, let's say that you have a god class named "LoginManager" that validates user information, updates "OnlineUserService" so the user is added to the online user list, and returns login-specific data (such as Welcome screen and one time offers)to the client.
So your class will look something like this:
import java.util.ArrayList;
import java.util.List;
public class LoginManager {
public void handleLogin(String hashedUserId, String hashedUserPassword){
String userId = decryptHashedString(hashedUserId);
String userPassword = decryptHashedString(hashedUserPassword);
if(!validateUser(userId, userPassword)){ return; }
updateOnlineUserService(userId);
sendCustomizedLoginMessage(userId);
sendOneTimeOffer(userId);
}
public String decryptHashedString(String hashedString){
String userId = "";
//TODO Decrypt hashed string for 150 lines of code...
return userId;
}
public boolean validateUser(String userId, String userPassword){
//validate for 100 lines of code...
List<String> userIdList = getUserIdList();
if(!isUserIdValid(userId,userIdList)){return false;}
if(!isPasswordCorrect(userId,userPassword)){return false;}
return true;
}
private List<String> getUserIdList() {
List<String> userIdList = new ArrayList<>();
//TODO: Add implementation details
return userIdList;
}
private boolean isPasswordCorrect(String userId, String userPassword) {
boolean isValidated = false;
//TODO: Add implementation details
return isValidated;
}
private boolean isUserIdValid(String userId, List<String> userIdList) {
boolean isValidated = false;
//TODO: Add implementation details
return isValidated;
}
public void updateOnlineUserService(String userId){
//TODO updateOnlineUserService for 100 lines of code...
}
public void sendCustomizedLoginMessage(String userId){
//TODO sendCustomizedLoginMessage for 50 lines of code...
}
public void sendOneTimeOffer(String userId){
//TODO sendOneTimeOffer for 100 lines of code...
}}
Now we can see that this class will be huge and complex. It is not a God class by book definition yet, since class fields are commonly used among methods now. But for the sake of argument, we can treat it as a God class and start refactoring.
One of the solutions is to create separate small classes which are used as members in the main class. Another thing you could add, could be separating different behaviors in different interfaces and their respective classes. Hide implementation details in classes by making those methods "private". And use those interfaces in the main class to do its bidding.
So at the end, RefactoredLoginManager will look like this:
public class RefactoredLoginManager {
IDecryptHandler decryptHandler;
IValidateHandler validateHandler;
IOnlineUserServiceNotifier onlineUserServiceNotifier;
IClientDataSender clientDataSender;
public void handleLogin(String hashedUserId, String hashedUserPassword){
String userId = decryptHandler.decryptHashedString(hashedUserId);
String userPassword = decryptHandler.decryptHashedString(hashedUserPassword);
if(!validateHandler.validateUser(userId, userPassword)){ return; }
onlineUserServiceNotifier.updateOnlineUserService(userId);
clientDataSender.sendCustomizedLoginMessage(userId);
clientDataSender.sendOneTimeOffer(userId);
}
}
DecryptHandler:
public class DecryptHandler implements IDecryptHandler {
public String decryptHashedString(String hashedString){
String userId = "";
//TODO Decrypt hashed string for 150 lines of code...
return userId;
}
}
public interface IDecryptHandler {
String decryptHashedString(String hashedString);
}
ValidateHandler:
public class ValidateHandler implements IValidateHandler {
public boolean validateUser(String userId, String userPassword){
//validate for 100 lines of code...
List<String> userIdList = getUserIdList();
if(!isUserIdValid(userId,userIdList)){return false;}
if(!isPasswordCorrect(userId,userPassword)){return false;}
return true;
}
private List<String> getUserIdList() {
List<String> userIdList = new ArrayList<>();
//TODO: Add implementation details
return userIdList;
}
private boolean isPasswordCorrect(String userId, String userPassword)
{
boolean isValidated = false;
//TODO: Add implementation details
return isValidated;
}
private boolean isUserIdValid(String userId, List<String> userIdList)
{
boolean isValidated = false;
//TODO: Add implementation details
return isValidated;
}
}
Important thing to note here is that the interfaces () only has to include the methods used by other classes. So IValidateHandler looks as simple as this:
public interface IValidateHandler {
boolean validateUser(String userId, String userPassword);
}
OnlineUserServiceNotifier:
public class OnlineUserServiceNotifier implements
IOnlineUserServiceNotifier {
public void updateOnlineUserService(String userId){
//TODO updateOnlineUserService for 100 lines of code...
}
}
public interface IOnlineUserServiceNotifier {
void updateOnlineUserService(String userId);
}
ClientDataSender:
public class ClientDataSender implements IClientDataSender {
public void sendCustomizedLoginMessage(String userId){
//TODO sendCustomizedLoginMessage for 50 lines of code...
}
public void sendOneTimeOffer(String userId){
//TODO sendOneTimeOffer for 100 lines of code...
}
}
Since both methods are accessed in LoginHandler, interface has to include both methods:
public interface IClientDataSender {
void sendCustomizedLoginMessage(String userId);
void sendOneTimeOffer(String userId);
}
There are really two topics here:
Given a God class, how its members be rationally partitioned into subsets? The fundamental idea is to group elements by conceptual coherency (often indicated by frequent co-usage in client modules) and by forced dependencies. Obviously the details of this are specific to the system being refactored. The outcome is a desired partition (set of groups) of God class elements.
Given a desired partition, actually making the change. This is difficult if the code base has any scale. Doing this manually, you are almost forced to retain the God class while you modify its accessors to instead call new classes formed from the partitions. And of course you need to test, test, test because it is easy to make a mistake when manually making these changes. When all accesses to the God class are gone, you can finally remove it. This sounds great in theory but it takes a long time in practice if you are facing thousands of compilation units, and you have to get the team members to stop adding accesses to the God interface while you do this. One can, however, apply automated refactoring tools to implement this; with such a tool you specify the partition to the tool and it then modifies the code base in a reliable way. Our DMS can implement this Refactoring C++ God Classes and has been used to make such changes across systems with 3,000 compilation units.