docs: add editorconfig + formatting

This commit is contained in:
Ilkka Seppälä
2024-03-29 14:30:50 +02:00
parent f80cc468b2
commit 6322d538cd
23 changed files with 1823 additions and 1480 deletions
+340
View File
@@ -0,0 +1,340 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
ij_smart_tabs = false
ij_visual_guides =
ij_wrap_on_typing = false
[*.java]
indent_size = 2
max_line_length = 100
ij_continuation_indent_size = 4
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_deconstruction_list_components = true
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_align_types_in_multi_catch = true
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = normal
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = normal
ij_java_assignment_wrap = normal
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = normal
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 1
ij_java_block_brace_style = end_of_line
ij_java_block_comment_add_space = false
ij_java_block_comment_at_first_column = true
ij_java_builder_methods =
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = normal
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 999
ij_java_class_names_in_javadoc = 1
ij_java_deconstruction_list_wrap = normal
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_not_wrap_after_single_annotation_in_parameter = false
ij_java_do_while_brace_force = always
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_entity_dd_prefix =
ij_java_entity_dd_suffix = EJB
ij_java_entity_eb_prefix =
ij_java_entity_eb_suffix = Bean
ij_java_entity_hi_prefix =
ij_java_entity_hi_suffix = Home
ij_java_entity_lhi_prefix = Local
ij_java_entity_lhi_suffix = Home
ij_java_entity_li_prefix = Local
ij_java_entity_li_suffix =
ij_java_entity_pk_class = java.lang.String
ij_java_entity_ri_prefix =
ij_java_entity_ri_suffix =
ij_java_entity_vo_prefix =
ij_java_entity_vo_suffix = VO
ij_java_enum_constants_wrap = normal
ij_java_extends_keyword_wrap = normal
ij_java_extends_list_wrap = normal
ij_java_field_annotation_wrap = split_into_lines
ij_java_field_name_prefix =
ij_java_field_name_suffix =
ij_java_filter_class_prefix =
ij_java_filter_class_suffix =
ij_java_filter_dd_prefix =
ij_java_filter_dd_suffix =
ij_java_finally_on_new_line = false
ij_java_for_brace_force = always
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = normal
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = always
ij_java_imports_layout = $*,|,*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
ij_java_keep_blank_lines_in_code = 2
ij_java_keep_blank_lines_in_declarations = 2
ij_java_keep_builder_methods_indents = false
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_add_space_on_reformat = false
ij_java_line_comment_at_first_column = true
ij_java_listener_class_prefix =
ij_java_listener_class_suffix =
ij_java_local_variable_name_prefix =
ij_java_local_variable_name_suffix =
ij_java_message_dd_prefix =
ij_java_message_dd_suffix = EJB
ij_java_message_eb_prefix =
ij_java_message_eb_suffix = Bean
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = normal
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = normal
ij_java_modifier_list_wrap = false
ij_java_multi_catch_types_wrap = normal
ij_java_names_count_to_use_import_on_demand = 999
ij_java_new_line_after_lparen_in_annotation = false
ij_java_new_line_after_lparen_in_deconstruction_pattern = true
ij_java_new_line_after_lparen_in_record_header = false
ij_java_packages_to_use_import_on_demand =
ij_java_parameter_annotation_wrap = normal
ij_java_parameter_name_prefix =
ij_java_parameter_name_suffix =
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_record_components_wrap = normal
ij_java_repeat_annotations =
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = false
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = normal
ij_java_rparen_on_new_line_in_annotation = false
ij_java_rparen_on_new_line_in_deconstruction_pattern = true
ij_java_rparen_on_new_line_in_record_header = false
ij_java_servlet_class_prefix =
ij_java_servlet_class_suffix =
ij_java_servlet_dd_prefix =
ij_java_servlet_dd_suffix =
ij_java_session_dd_prefix =
ij_java_session_dd_suffix = EJB
ij_java_session_eb_prefix =
ij_java_session_eb_suffix = Bean
ij_java_session_hi_prefix =
ij_java_session_hi_suffix = Home
ij_java_session_lhi_prefix = Local
ij_java_session_lhi_suffix = Home
ij_java_session_li_prefix = Local
ij_java_session_li_suffix =
ij_java_session_ri_prefix =
ij_java_session_ri_suffix =
ij_java_session_si_prefix =
ij_java_session_si_suffix = Service
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = true
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = true
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_deconstruction_list = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = true
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = true
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = true
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = true
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = true
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = true
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_annotation_eq = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_deconstruction_list = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_record_header = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_static_field_name_prefix =
ij_java_static_field_name_suffix =
ij_java_subclass_name_prefix =
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = normal
ij_java_test_name_prefix =
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = normal
ij_java_throws_list_wrap = normal
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = normal
ij_java_visibility = public
ij_java_while_brace_force = always
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
[{*.markdown,*.md}]
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
ij_markdown_force_one_space_after_list_bullet = true
ij_markdown_force_one_space_between_words = true
ij_markdown_format_tables = true
ij_markdown_insert_quote_arrows_on_wrap = true
ij_markdown_keep_indents_on_empty_lines = false
ij_markdown_keep_line_breaks_inside_text_blocks = true
ij_markdown_max_lines_around_block_elements = 1
ij_markdown_max_lines_around_header = 1
ij_markdown_max_lines_between_paragraphs = 1
ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
ij_markdown_wrap_text_if_long = true
ij_markdown_wrap_text_inside_blockquotes = true
+68 -68
View File
@@ -3,9 +3,9 @@ title: Abstract Document
category: Structural
language: en
tag:
- Abstraction
- Extensibility
- Decoupling
- Abstraction
- Extensibility
- Decoupling
---
## Intent
@@ -43,43 +43,43 @@ map and any amount of child objects.
```java
public interface Document {
Void put(String key, Object value);
Void put(String key, Object value);
Object get(String key);
Object get(String key);
<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
}
public abstract class AbstractDocument implements Document {
private final Map<String, Object> properties;
private final Map<String, Object> properties;
protected AbstractDocument(Map<String, Object> properties) {
Objects.requireNonNull(properties, "properties map is required");
this.properties = properties;
}
protected AbstractDocument(Map<String, Object> properties) {
Objects.requireNonNull(properties, "properties map is required");
this.properties = properties;
}
@Override
public Void put(String key, Object value) {
properties.put(key, value);
return null;
}
@Override
public Void put(String key, Object value) {
properties.put(key, value);
return null;
}
@Override
public Object get(String key) {
return properties.get(key);
}
@Override
public Object get(String key) {
return properties.get(key);
}
@Override
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
return Stream.ofNullable(get(key))
.filter(Objects::nonNull)
.map(el -> (List<Map<String, Object>>) el)
.findAny()
.stream()
.flatMap(Collection::stream)
.map(constructor);
}
@Override
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
return Stream.ofNullable(get(key))
.filter(Objects::nonNull)
.map(el -> (List<Map<String, Object>>) el)
.findAny()
.stream()
.flatMap(Collection::stream)
.map(constructor);
}
...
}
```
@@ -90,35 +90,35 @@ static looking interface to our `Car` class.
```java
public enum Property {
PARTS, TYPE, PRICE, MODEL
PARTS, TYPE, PRICE, MODEL
}
public interface HasType extends Document {
default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString()));
}
default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString()));
}
}
public interface HasPrice extends Document {
default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
}
default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
}
}
public interface HasModel extends Document {
default Optional<String> getModel() {
return Optional.ofNullable((String) get(Property.MODEL.toString()));
}
default Optional<String> getModel() {
return Optional.ofNullable((String) get(Property.MODEL.toString()));
}
}
public interface HasParts extends Document {
default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new);
}
default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new);
}
}
```
@@ -127,9 +127,9 @@ Now we are ready to introduce the `Car`.
```java
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
public Car(Map<String, Object> properties) {
super(properties);
}
public Car(Map<String, Object> properties) {
super(properties);
}
}
```
@@ -138,32 +138,32 @@ And finally here's how we construct and use the `Car` in a full example.
```java
LOGGER.info("Constructing parts and car");
var wheelProperties=Map.of(
Property.TYPE.toString(),"wheel",
Property.MODEL.toString(),"15C",
Property.PRICE.toString(),100L);
var wheelProperties=Map.of(
Property.TYPE.toString(),"wheel",
Property.MODEL.toString(),"15C",
Property.PRICE.toString(),100L);
var doorProperties=Map.of(
Property.TYPE.toString(),"door",
Property.MODEL.toString(),"Lambo",
Property.PRICE.toString(),300L);
var doorProperties=Map.of(
Property.TYPE.toString(),"door",
Property.MODEL.toString(),"Lambo",
Property.PRICE.toString(),300L);
var carProperties=Map.of(
Property.MODEL.toString(),"300SL",
Property.PRICE.toString(),10000L,
Property.PARTS.toString(),List.of(wheelProperties,doorProperties));
var carProperties=Map.of(
Property.MODEL.toString(),"300SL",
Property.PRICE.toString(),10000L,
Property.PARTS.toString(),List.of(wheelProperties,doorProperties));
var car=new Car(carProperties);
var car=new Car(carProperties);
LOGGER.info("Here is our car:");
LOGGER.info("-> model: {}",car.getModel().orElseThrow());
LOGGER.info("-> price: {}",car.getPrice().orElseThrow());
LOGGER.info("-> parts: ");
car.getParts().forEach(p->LOGGER.info("\t{}/{}/{}",
p.getType().orElse(null),
p.getModel().orElse(null),
p.getPrice().orElse(null))
);
LOGGER.info("Here is our car:");
LOGGER.info("-> model: {}",car.getModel().orElseThrow());
LOGGER.info("-> price: {}",car.getPrice().orElseThrow());
LOGGER.info("-> parts: ");
car.getParts().forEach(p->LOGGER.info("\t{}/{}/{}",
p.getType().orElse(null),
p.getModel().orElse(null),
p.getPrice().orElse(null))
);
// Constructing parts and car
// Here is our car:
+76 -76
View File
@@ -3,9 +3,9 @@ title: Abstract Factory
category: Creational
language: en
tag:
- Abstraction
- Decoupling
- Gang of Four
- Abstraction
- Decoupling
- Gang of Four
---
## Also known as
@@ -43,43 +43,43 @@ kingdom.
```java
public interface Castle {
String getDescription();
String getDescription();
}
public interface King {
String getDescription();
String getDescription();
}
public interface Army {
String getDescription();
String getDescription();
}
// Elven implementations ->
public class ElfCastle implements Castle {
static final String DESCRIPTION = "This is the elven castle!";
static final String DESCRIPTION = "This is the elven castle!";
@Override
public String getDescription() {
return DESCRIPTION;
}
@Override
public String getDescription() {
return DESCRIPTION;
}
}
public class ElfKing implements King {
static final String DESCRIPTION = "This is the elven king!";
static final String DESCRIPTION = "This is the elven king!";
@Override
public String getDescription() {
return DESCRIPTION;
}
@Override
public String getDescription() {
return DESCRIPTION;
}
}
public class ElfArmy implements Army {
static final String DESCRIPTION = "This is the elven Army!";
static final String DESCRIPTION = "This is the elven Army!";
@Override
public String getDescription() {
return DESCRIPTION;
}
@Override
public String getDescription() {
return DESCRIPTION;
}
}
// Orcish implementations similarly -> ...
@@ -90,47 +90,47 @@ Then we have the abstraction and implementations for the kingdom factory.
```java
public interface KingdomFactory {
Castle createCastle();
Castle createCastle();
King createKing();
King createKing();
Army createArmy();
Army createArmy();
}
public class ElfKingdomFactory implements KingdomFactory {
@Override
public Castle createCastle() {
return new ElfCastle();
}
@Override
public Castle createCastle() {
return new ElfCastle();
}
@Override
public King createKing() {
return new ElfKing();
}
@Override
public King createKing() {
return new ElfKing();
}
@Override
public Army createArmy() {
return new ElfArmy();
}
@Override
public Army createArmy() {
return new ElfArmy();
}
}
public class OrcKingdomFactory implements KingdomFactory {
@Override
public Castle createCastle() {
return new OrcCastle();
}
@Override
public Castle createCastle() {
return new OrcCastle();
}
@Override
public King createKing() {
return new OrcKing();
}
@Override
public King createKing() {
return new OrcKing();
}
@Override
public Army createArmy() {
return new OrcArmy();
}
@Override
public Army createArmy() {
return new OrcArmy();
}
}
```
@@ -139,21 +139,21 @@ castle, king and army, etc.
```java
var factory=new ElfKingdomFactory();
var castle=factory.createCastle();
var king=factory.createKing();
var army=factory.createArmy();
var castle=factory.createCastle();
var king=factory.createKing();
var army=factory.createArmy();
castle.getDescription();
king.getDescription();
army.getDescription();
castle.getDescription();
king.getDescription();
army.getDescription();
```
Program output:
```java
This is the elven castle!
This is the elven king!
This is the elven Army!
This is the elven king!
This is the elven Army!
```
Now, we can design a factory for our different kingdom factories. In this example, we created `FactoryMaker`,
@@ -165,31 +165,31 @@ factory the client will ask for.
```java
public static class FactoryMaker {
public enum KingdomType {
ELF, ORC
}
public enum KingdomType {
ELF, ORC
}
public static KingdomFactory makeFactory(KingdomType type) {
return switch (type) {
case ELF -> new ElfKingdomFactory();
case ORC -> new OrcKingdomFactory();
};
}
public static KingdomFactory makeFactory(KingdomType type) {
return switch (type) {
case ELF -> new ElfKingdomFactory();
case ORC -> new OrcKingdomFactory();
};
}
}
public static void main(String[] args) {
var app = new App();
public static void main(String[] args) {
var app = new App();
LOGGER.info("Elf Kingdom");
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF));
LOGGER.info(app.getArmy().getDescription());
LOGGER.info(app.getCastle().getDescription());
LOGGER.info(app.getKing().getDescription());
LOGGER.info("Elf Kingdom");
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF));
LOGGER.info(app.getArmy().getDescription());
LOGGER.info(app.getCastle().getDescription());
LOGGER.info(app.getKing().getDescription());
LOGGER.info("Orc Kingdom");
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC));
--similar use of the orc factory
}
LOGGER.info("Orc Kingdom");
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC));
--similar use of the orc factory
}
```
## Class diagram
+62 -62
View File
@@ -3,7 +3,7 @@ title: Active Object
category: Concurrency
language: en
tag:
- Performance
- Performance
---
## Intent
@@ -29,57 +29,57 @@ itself, we can use the Active Object pattern.
```java
public abstract class ActiveCreature {
private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
private BlockingQueue<Runnable> requests;
private BlockingQueue<Runnable> requests;
private String name;
private String name;
private Thread thread;
private Thread thread;
public ActiveCreature(String name) {
this.name = name;
this.requests = new LinkedBlockingQueue<Runnable>();
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
requests.take().run();
} catch (InterruptedException e) {
logger.error(e.getMessage());
}
public ActiveCreature(String name) {
this.name = name;
this.requests = new LinkedBlockingQueue<Runnable>();
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
requests.take().run();
} catch (InterruptedException e) {
logger.error(e.getMessage());
}
}
}
}
}
);
thread.start();
}
);
thread.start();
}
public void eat() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} is eating!", name());
logger.info("{} has finished eating!", name());
}
}
);
}
public void eat() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} is eating!", name());
logger.info("{} has finished eating!", name());
}
}
);
}
public void roam() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} has started to roam the wastelands.", name());
}
}
);
}
public void roam() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} has started to roam the wastelands.", name());
}
}
);
}
public String name() {
return this.name;
}
public String name() {
return this.name;
}
}
```
@@ -91,9 +91,9 @@ For example, the Orc class:
```java
public class Orc extends ActiveCreature {
public Orc(String name) {
super(name);
}
public Orc(String name) {
super(name);
}
}
```
@@ -103,25 +103,25 @@ thread of control:
```java
public static void main(String[]args){
var app=new App();
app.run();
}
var app=new App();
app.run();
}
@Override
public void run(){
ActiveCreature creature;
try{
for(int i=0;i<creatures;i++){
creature=new Orc(Orc.class.getSimpleName().toString()+i);
creature.eat();
creature.roam();
}
Thread.sleep(1000);
}catch(InterruptedException e){
logger.error(e.getMessage());
}
Runtime.getRuntime().exit(1);
}
ActiveCreature creature;
try{
for(int i=0;i<creatures;i++){
creature=new Orc(Orc.class.getSimpleName().toString()+i);
creature.eat();
creature.roam();
}
Thread.sleep(1000);
}catch(InterruptedException e){
logger.error(e.getMessage());
}
Runtime.getRuntime().exit(1);
}
```
## Class diagram
+38 -38
View File
@@ -3,8 +3,8 @@ title: Acyclic Visitor
category: Behavioral
language: en
tag:
- Decoupling
- Extensibility
- Decoupling
- Extensibility
---
## Intent
@@ -34,33 +34,33 @@ Here's the `Modem` hierarchy.
```java
public abstract class Modem {
public abstract void accept(ModemVisitor modemVisitor);
public abstract void accept(ModemVisitor modemVisitor);
}
public class Zoom extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) {
((ZoomVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) {
((ZoomVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
}
}
}
}
public class Hayes extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) {
((HayesVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) {
((HayesVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
}
}
}
}
```
@@ -71,11 +71,11 @@ public interface ModemVisitor {
}
public interface HayesVisitor extends ModemVisitor {
void visit(Hayes hayes);
void visit(Hayes hayes);
}
public interface ZoomVisitor extends ModemVisitor {
void visit(Zoom zoom);
void visit(Zoom zoom);
}
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
@@ -84,24 +84,24 @@ public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
public class ConfigureForDosVisitor implements AllModemVisitor {
...
@Override
public void visit(Hayes hayes) {
LOGGER.info(hayes + " used with Dos configurator.");
}
@Override
public void visit(Hayes hayes) {
LOGGER.info(hayes + " used with Dos configurator.");
}
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Dos configurator.");
}
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Dos configurator.");
}
}
public class ConfigureForUnixVisitor implements ZoomVisitor {
...
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator.");
}
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator.");
}
}
```
@@ -109,13 +109,13 @@ Finally, here are the visitors in action.
```java
var conUnix=new ConfigureForUnixVisitor();
var conDos=new ConfigureForDosVisitor();
var zoom=new Zoom();
var hayes=new Hayes();
hayes.accept(conDos);
zoom.accept(conDos);
hayes.accept(conUnix);
zoom.accept(conUnix);
var conDos=new ConfigureForDosVisitor();
var zoom=new Zoom();
var hayes=new Hayes();
hayes.accept(conDos);
zoom.accept(conDos);
hayes.accept(conUnix);
zoom.accept(conUnix);
```
Program output:
+29 -29
View File
@@ -3,9 +3,9 @@ title: Adapter
category: Structural
language: en
tag:
- Compatibility
- Gang of Four
- Integration
- Compatibility
- Gang of Four
- Integration
---
## Also known as
@@ -22,11 +22,11 @@ compatibility.
Real-world example
> Consider that you have some pictures on your memory card and you need to transfer them to your computer. To transfer
> them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card to
> your computer. In this case card reader is an adapter.
> Another example would be the famous power adapter; a three-legged plug can't be connected to a two-pronged outlet, it
> needs to use a power adapter that makes it compatible with the two-pronged outlets.
> Yet another example would be a translator translating words spoken by one person to another
> them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card
> to your computer. In this case card reader is an adapter. Another example would be the famous power adapter; a
> three-legged plug can't be connected to a two-pronged outlet, it needs to use a power adapter that makes it compatible
> with the two-pronged outlets. Yet another example would be a translator translating words spoken by one person to
> another
In plain words
@@ -46,14 +46,14 @@ First, we have interfaces `RowingBoat` and `FishingBoat`
```java
public interface RowingBoat {
void row();
void row();
}
@Slf4j
public class FishingBoat {
public void sail() {
LOGGER.info("The fishing boat is sailing");
}
public void sail() {
LOGGER.info("The fishing boat is sailing");
}
}
```
@@ -62,16 +62,16 @@ And captain expects an implementation of `RowingBoat` interface to be able to mo
```java
public class Captain {
private final RowingBoat rowingBoat;
private final RowingBoat rowingBoat;
// default constructor and setter for rowingBoat
public Captain(RowingBoat rowingBoat) {
this.rowingBoat = rowingBoat;
}
// default constructor and setter for rowingBoat
public Captain(RowingBoat rowingBoat) {
this.rowingBoat = rowingBoat;
}
public void row() {
rowingBoat.row();
}
public void row() {
rowingBoat.row();
}
}
```
@@ -83,16 +83,16 @@ to create an adapter that allows the captain to operate the fishing boat with hi
@Slf4j
public class FishingBoatAdapter implements RowingBoat {
private final FishingBoat boat;
private final FishingBoat boat;
public FishingBoatAdapter() {
boat = new FishingBoat();
}
public FishingBoatAdapter() {
boat = new FishingBoat();
}
@Override
public void row() {
boat.sail();
}
@Override
public void row() {
boat.sail();
}
}
```
@@ -100,7 +100,7 @@ And now the `Captain` can use the `FishingBoat` to escape the pirates.
```java
var captain=new Captain(new FishingBoatAdapter());
captain.row();
captain.row();
```
## Class diagram
+26 -26
View File
@@ -3,10 +3,10 @@ title: Aggregator Microservices
category: Architectural
language: en
tag:
- API design
- Cloud distributed
- Decoupling
- Microservices
- API design
- Cloud distributed
- Decoupling
- Microservices
---
## Intent
@@ -37,9 +37,9 @@ Let's start from the data model. Here's our `Product`.
```java
public class Product {
private String title;
private int productInventories;
// Getters and setters omitted for brevity ->
private String title;
private int productInventories;
// Getters and setters omitted for brevity ->
...
}
```
@@ -52,27 +52,27 @@ Next we can introduce our `Aggregator` microservice. It contains clients `Produc
@RestController
public class Aggregator {
@Resource
private ProductInformationClient informationClient;
@Resource
private ProductInformationClient informationClient;
@Resource
private ProductInventoryClient inventoryClient;
@Resource
private ProductInventoryClient inventoryClient;
@RequestMapping(path = "/product", method = RequestMethod.GET)
public Product getProduct() {
@RequestMapping(path = "/product", method = RequestMethod.GET)
public Product getProduct() {
var product = new Product();
var productTitle = informationClient.getProductTitle();
var productInventory = inventoryClient.getProductInventories();
var product = new Product();
var productTitle = informationClient.getProductTitle();
var productInventory = inventoryClient.getProductInventories();
//Fallback to error message
product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed"));
//Fallback to error message
product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed"));
//Fallback to default error inventory
product.setProductInventories(requireNonNullElse(productInventory, -1));
//Fallback to default error inventory
product.setProductInventories(requireNonNullElse(productInventory, -1));
return product;
}
return product;
}
}
```
@@ -83,10 +83,10 @@ inventory counts.
@RestController
public class InformationController {
@RequestMapping(value = "/information", method = RequestMethod.GET)
public String getProductTitle() {
return "The Product Title.";
}
@RequestMapping(value = "/information", method = RequestMethod.GET)
public String getProductTitle() {
return "The Product Title.";
}
}
```
+85 -85
View File
@@ -3,8 +3,8 @@ title: Ambassador
category: Structural
language: en
tag:
- Decoupling
- Cloud distributed
- Decoupling
- Cloud distributed
---
## Intent
@@ -43,7 +43,7 @@ by the remote service as well as the ambassador service:
```java
interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception;
long doRemoteFunction(int value) throws Exception;
}
```
@@ -53,30 +53,30 @@ A remote services represented as a singleton.
@Slf4j
public class RemoteService implements RemoteServiceInterface {
private static RemoteService service = null;
private static RemoteService service = null;
static synchronized RemoteService getRemoteService() {
if (service == null) {
service = new RemoteService();
}
return service;
}
private RemoteService() {
}
@Override
public long doRemoteFunction(int value) {
long waitTime = (long) Math.floor(Math.random() * 1000);
try {
sleep(waitTime);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep interrupted", e);
static synchronized RemoteService getRemoteService() {
if (service == null) {
service = new RemoteService();
}
return service;
}
return waitTime >= 200 ? value * 10 : -1;
}
private RemoteService() {
}
@Override
public long doRemoteFunction(int value) {
long waitTime = (long) Math.floor(Math.random() * 1000);
try {
sleep(waitTime);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep interrupted", e);
}
return waitTime >= 200 ? value * 10 : -1;
}
}
```
@@ -86,49 +86,49 @@ A service ambassador adding additional features such as logging, latency checks
@Slf4j
public class ServiceAmbassador implements RemoteServiceInterface {
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
ServiceAmbassador() {
}
@Override
public long doRemoteFunction(int value) {
return safeCall(value);
}
private long checkLatency(int value) {
var startTime = System.currentTimeMillis();
var result = RemoteService.getRemoteService().doRemoteFunction(value);
var timeTaken = System.currentTimeMillis() - startTime;
LOGGER.info("Time taken (ms): " + timeTaken);
return result;
}
private long safeCall(int value) {
var retries = 0;
var result = (long) FAILURE;
for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) {
return FAILURE;
}
if ((result = checkLatency(value)) == FAILURE) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++;
try {
sleep(DELAY_MS);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
}
} else {
break;
}
ServiceAmbassador() {
}
@Override
public long doRemoteFunction(int value) {
return safeCall(value);
}
private long checkLatency(int value) {
var startTime = System.currentTimeMillis();
var result = RemoteService.getRemoteService().doRemoteFunction(value);
var timeTaken = System.currentTimeMillis() - startTime;
LOGGER.info("Time taken (ms): " + timeTaken);
return result;
}
private long safeCall(int value) {
var retries = 0;
var result = (long) FAILURE;
for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) {
return FAILURE;
}
if ((result = checkLatency(value)) == FAILURE) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++;
try {
sleep(DELAY_MS);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
}
} else {
break;
}
}
return result;
}
return result;
}
}
```
@@ -138,13 +138,13 @@ A client has a local service ambassador used to interact with the remote service
@Slf4j
public class Client {
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
long useService(int value) {
var result = serviceAmbassador.doRemoteFunction(value);
LOGGER.info("Service result: " + result);
return result;
}
long useService(int value) {
var result = serviceAmbassador.doRemoteFunction(value);
LOGGER.info("Service result: " + result);
return result;
}
}
```
@@ -152,12 +152,12 @@ Here are two clients using the service.
```java
public class App {
public static void main(String[] args) {
var host1 = new Client();
var host2 = new Client();
host1.useService(12);
host2.useService(73);
}
public static void main(String[] args) {
var host1 = new Client();
var host2 = new Client();
host1.useService(12);
host2.useService(73);
}
}
```
@@ -165,14 +165,14 @@ Here's the output for running the example:
```java
Time taken(ms):111
Service result:120
Time taken(ms):931
Failed to reach remote:(1)
Time taken(ms):665
Failed to reach remote:(2)
Time taken(ms):538
Failed to reach remote:(3)
Service result:-1
Service result:120
Time taken(ms):931
Failed to reach remote:(1)
Time taken(ms):665
Failed to reach remote:(2)
Time taken(ms):538
Failed to reach remote:(3)
Service result:-1
```
## Class diagram
+36 -36
View File
@@ -3,9 +3,9 @@ title: Anti-corruption layer
category: Integration
language: en
tag:
- Architecture
- Decoupling
- Isolation
- Architecture
- Decoupling
- Isolation
---
## Also known as
@@ -67,11 +67,11 @@ systems.
```java
public class LegacyOrder {
private String id;
private String customer;
private String item;
private String qty;
private String price;
private String id;
private String customer;
private String item;
private String qty;
private String price;
}
```
@@ -79,22 +79,22 @@ public class LegacyOrder {
```java
public class ModernOrder {
private String id;
private Customer customer;
private String id;
private Customer customer;
private Shipment shipment;
private Shipment shipment;
private String extra;
private String extra;
}
public class Customer {
private String address;
private String address;
}
public class Shipment {
private String item;
private String qty;
private String price;
private String item;
private String qty;
private String price;
}
```
@@ -103,19 +103,19 @@ public class Shipment {
```java
public class AntiCorruptionLayer {
@Autowired
private ModernShop modernShop;
@Autowired
private ModernShop modernShop;
@Autowired
private LegacyShop legacyShop;
@Autowired
private LegacyShop legacyShop;
public Optional<LegacyOrder> findOrderInModernSystem(String id) {
return modernShop.findOrder(id).map(o -> /* map to legacyOrder*/);
}
public Optional<LegacyOrder> findOrderInModernSystem(String id) {
return modernShop.findOrder(id).map(o -> /* map to legacyOrder*/);
}
public Optional<ModernOrder> findOrderInLegacySystem(String id) {
return legacyShop.findOrder(id).map(o -> /* map to modernOrder*/);
}
public Optional<ModernOrder> findOrderInLegacySystem(String id) {
return legacyShop.findOrder(id).map(o -> /* map to modernOrder*/);
}
}
```
@@ -128,21 +128,21 @@ from the `Modern` system.
```java
public class LegacyShop {
@Autowired
private AntiCorruptionLayer acl;
@Autowired
private AntiCorruptionLayer acl;
public void placeOrder(LegacyOrder legacyOrder) throws ShopException {
public void placeOrder(LegacyOrder legacyOrder) throws ShopException {
String id = legacyOrder.getId();
String id = legacyOrder.getId();
Optional<LegacyOrder> orderInModernSystem = acl.findOrderInModernSystem(id);
Optional<LegacyOrder> orderInModernSystem = acl.findOrderInModernSystem(id);
if (orderInModernSystem.isPresent()) {
// order is already in the modern system
} else {
// place order in the current system
if (orderInModernSystem.isPresent()) {
// order is already in the modern system
} else {
// place order in the current system
}
}
}
}
```
+53 -53
View File
@@ -3,10 +3,10 @@ title: API Gateway
category: Architectural
language: en
tag:
- API design
- Cloud distributed
- Decoupling
- Microservices
- API design
- Cloud distributed
- Decoupling
- Microservices
---
## Intent
@@ -68,27 +68,27 @@ Here's the Image microservice implementation.
```java
public interface ImageClient {
String getImagePath();
String getImagePath();
}
public class ImageClientImpl implements ImageClient {
@Override
public String getImagePath() {
var httpClient = HttpClient.newHttpClient();
var httpGet = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://localhost:50005/image-path"))
.build();
@Override
public String getImagePath() {
var httpClient = HttpClient.newHttpClient();
var httpGet = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://localhost:50005/image-path"))
.build();
try {
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
return httpResponse.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
try {
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
return httpResponse.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
return null;
}
}
```
@@ -96,28 +96,28 @@ Here's the Price microservice implementation.
```java
public interface PriceClient {
String getPrice();
String getPrice();
}
public class PriceClientImpl implements PriceClient {
@Override
public String getPrice() {
var httpClient = HttpClient.newHttpClient();
var httpGet = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://localhost:50006/price"))
.build();
@Override
public String getPrice() {
var httpClient = HttpClient.newHttpClient();
var httpGet = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://localhost:50006/price"))
.build();
try {
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
return httpResponse.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
try {
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
return httpResponse.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
return null;
}
}
```
@@ -126,26 +126,26 @@ Here we can see how API Gateway maps the requests to the microservices.
```java
public class ApiGateway {
@Resource
private ImageClient imageClient;
@Resource
private ImageClient imageClient;
@Resource
private PriceClient priceClient;
@Resource
private PriceClient priceClient;
@RequestMapping(path = "/desktop", method = RequestMethod.GET)
public DesktopProduct getProductDesktop() {
var desktopProduct = new DesktopProduct();
desktopProduct.setImagePath(imageClient.getImagePath());
desktopProduct.setPrice(priceClient.getPrice());
return desktopProduct;
}
@RequestMapping(path = "/desktop", method = RequestMethod.GET)
public DesktopProduct getProductDesktop() {
var desktopProduct = new DesktopProduct();
desktopProduct.setImagePath(imageClient.getImagePath());
desktopProduct.setPrice(priceClient.getPrice());
return desktopProduct;
}
@RequestMapping(path = "/mobile", method = RequestMethod.GET)
public MobileProduct getProductMobile() {
var mobileProduct = new MobileProduct();
mobileProduct.setPrice(priceClient.getPrice());
return mobileProduct;
}
@RequestMapping(path = "/mobile", method = RequestMethod.GET)
public MobileProduct getProductMobile() {
var mobileProduct = new MobileProduct();
mobileProduct.setPrice(priceClient.getPrice());
return mobileProduct;
}
}
```
+61 -61
View File
@@ -3,8 +3,8 @@ title: Arrange/Act/Assert
category: Testing
language: en
tag:
- Idiom
- Testing
- Idiom
- Testing
---
## Also known as
@@ -51,28 +51,28 @@ Let's first introduce our `Cash` class to be unit tested.
```java
public class Cash {
private int amount;
private int amount;
Cash(int amount) {
this.amount = amount;
}
void plus(int addend) {
amount += addend;
}
boolean minus(int subtrahend) {
if (amount >= subtrahend) {
amount -= subtrahend;
return true;
} else {
return false;
Cash(int amount) {
this.amount = amount;
}
}
int count() {
return amount;
}
void plus(int addend) {
amount += addend;
}
boolean minus(int subtrahend) {
if (amount >= subtrahend) {
amount -= subtrahend;
return true;
} else {
return false;
}
}
int count() {
return amount;
}
}
```
@@ -82,49 +82,49 @@ separated steps for each unit test.
```java
class CashAAATest {
@Test
void testPlus() {
//Arrange
var cash = new Cash(3);
//Act
cash.plus(4);
//Assert
assertEquals(7, cash.count());
}
@Test
void testPlus() {
//Arrange
var cash = new Cash(3);
//Act
cash.plus(4);
//Assert
assertEquals(7, cash.count());
}
@Test
void testMinus() {
//Arrange
var cash = new Cash(8);
//Act
var result = cash.minus(5);
//Assert
assertTrue(result);
assertEquals(3, cash.count());
}
@Test
void testMinus() {
//Arrange
var cash = new Cash(8);
//Act
var result = cash.minus(5);
//Assert
assertTrue(result);
assertEquals(3, cash.count());
}
@Test
void testInsufficientMinus() {
//Arrange
var cash = new Cash(1);
//Act
var result = cash.minus(6);
//Assert
assertFalse(result);
assertEquals(1, cash.count());
}
@Test
void testInsufficientMinus() {
//Arrange
var cash = new Cash(1);
//Act
var result = cash.minus(6);
//Assert
assertFalse(result);
assertEquals(1, cash.count());
}
@Test
void testUpdate() {
//Arrange
var cash = new Cash(5);
//Act
cash.plus(6);
var result = cash.minus(3);
//Assert
assertTrue(result);
assertEquals(8, cash.count());
}
@Test
void testUpdate() {
//Arrange
var cash = new Cash(5);
//Act
cash.plus(6);
var result = cash.minus(3);
//Assert
assertTrue(result);
assertEquals(8, cash.count());
}
}
```
+61 -61
View File
@@ -3,9 +3,9 @@ title: Async Method Invocation
category: Concurrency
language: en
tag:
- Asynchronous
- Reactive
- Scalability
- Asynchronous
- Reactive
- Scalability
---
## Intent
@@ -48,29 +48,29 @@ manages the execution of the async tasks.
```java
public interface AsyncResult<T> {
boolean isCompleted();
boolean isCompleted();
T getValue() throws ExecutionException;
T getValue() throws ExecutionException;
void await() throws InterruptedException;
void await() throws InterruptedException;
}
```
```java
public interface AsyncCallback<T> {
void onComplete(T value);
void onComplete(T value);
void onError(Exception ex);
void onError(Exception ex);
}
```
```java
public interface AsyncExecutor {
<T> AsyncResult<T> startProcess(Callable<T> task);
<T> AsyncResult<T> startProcess(Callable<T> task);
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
}
```
@@ -80,35 +80,35 @@ next.
```java
public class ThreadAsyncExecutor implements AsyncExecutor {
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task) {
return startProcess(task, null);
}
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
var result = new CompletableResult<>(callback);
new Thread(
() -> {
try {
result.setValue(task.call());
} catch (Exception ex) {
result.setException(ex);
}
},
"executor-" + idx.incrementAndGet())
.start();
return result;
}
@Override
public <T> T endProcess(AsyncResult<T> asyncResult)
throws ExecutionException, InterruptedException {
if (!asyncResult.isCompleted()) {
asyncResult.await();
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task) {
return startProcess(task, null);
}
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
var result = new CompletableResult<>(callback);
new Thread(
() -> {
try {
result.setValue(task.call());
} catch (Exception ex) {
result.setException(ex);
}
},
"executor-" + idx.incrementAndGet())
.start();
return result;
}
@Override
public <T> T endProcess(AsyncResult<T> asyncResult)
throws ExecutionException, InterruptedException {
if (!asyncResult.isCompleted()) {
asyncResult.await();
}
return asyncResult.getValue();
}
return asyncResult.getValue();
}
}
```
@@ -116,8 +116,8 @@ Then we are ready to launch some rockets to see how everything works together.
```java
public static void main(String[]args)throws Exception{
// construct a new executor that will run async tasks
var executor=new ThreadAsyncExecutor();
// construct a new executor that will run async tasks
var executor=new ThreadAsyncExecutor();
// start few async tasks with varying processing times, two last with callback handlers
final var asyncResult1=executor.startProcess(lazyval(10,500));
@@ -125,40 +125,40 @@ final var asyncResult2=executor.startProcess(lazyval("test",300));
final var asyncResult3=executor.startProcess(lazyval(50L,700));
final var asyncResult4=executor.startProcess(lazyval(20,400),callback("Deploying lunar rover"));
final var asyncResult5=
executor.startProcess(lazyval("callback",600),callback("Deploying lunar rover"));
executor.startProcess(lazyval("callback",600),callback("Deploying lunar rover"));
// emulate processing in the current thread while async tasks are running in their own threads
Thread.sleep(350); // Oh boy, we are working hard here
log("Mission command is sipping coffee");
// emulate processing in the current thread while async tasks are running in their own threads
Thread.sleep(350); // Oh boy, we are working hard here
log("Mission command is sipping coffee");
// wait for completion of the tasks
final var result1=executor.endProcess(asyncResult1);
final var result2=executor.endProcess(asyncResult2);
final var result3=executor.endProcess(asyncResult3);
asyncResult4.await();
asyncResult5.await();
asyncResult4.await();
asyncResult5.await();
// log the results of the tasks, callbacks log immediately when complete
log("Space rocket <"+result1+"> launch complete");
log("Space rocket <"+result2+"> launch complete");
log("Space rocket <"+result3+"> launch complete");
}
// log the results of the tasks, callbacks log immediately when complete
log("Space rocket <"+result1+"> launch complete");
log("Space rocket <"+result2+"> launch complete");
log("Space rocket <"+result3+"> launch complete");
}
```
Here's the program console output.
```java
21:47:08.227[executor-2]INFO com.iluwatar.async.method.invocation.App-Space rocket<test> launched successfully
21:47:08.269[main]INFO com.iluwatar.async.method.invocation.App-Mission command is sipping coffee
21:47:08.318[executor-4]INFO com.iluwatar.async.method.invocation.App-Space rocket<20>launched successfully
21:47:08.335[executor-4]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<20>
21:47:08.414[executor-1]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Space rocket<callback> launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<callback>
21:47:08.269[main]INFO com.iluwatar.async.method.invocation.App-Mission command is sipping coffee
21:47:08.318[executor-4]INFO com.iluwatar.async.method.invocation.App-Space rocket<20>launched successfully
21:47:08.335[executor-4]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<20>
21:47:08.414[executor-1]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Space rocket<callback> launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<callback>
21:47:08.616[executor-3]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launched successfully
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launch complete
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<test> launch complete
21:47:08.618[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launch complete
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launch complete
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<test> launch complete
21:47:08.618[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launch complete
```
# Class diagram
+41 -41
View File
@@ -3,7 +3,7 @@ title: Balking
category: Concurrency
language: en
tag:
- Decoupling
- Decoupling
---
## Intent
@@ -44,36 +44,36 @@ Here are the relevant parts of the `WashingMachine` class.
@Slf4j
public class WashingMachine {
private final DelayProvider delayProvider;
private WashingMachineState washingMachineState;
private final DelayProvider delayProvider;
private WashingMachineState washingMachineState;
public WashingMachine(DelayProvider delayProvider) {
this.delayProvider = delayProvider;
this.washingMachineState = WashingMachineState.ENABLED;
}
public WashingMachineState getWashingMachineState() {
return washingMachineState;
}
public void wash() {
synchronized (this) {
var machineState = getWashingMachineState();
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
if (this.washingMachineState == WashingMachineState.WASHING) {
LOGGER.error("Cannot wash if the machine has been already washing!");
return;
}
this.washingMachineState = WashingMachineState.WASHING;
public WashingMachine(DelayProvider delayProvider) {
this.delayProvider = delayProvider;
this.washingMachineState = WashingMachineState.ENABLED;
}
LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
}
public synchronized void endOfWashing() {
washingMachineState = WashingMachineState.ENABLED;
LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
}
public WashingMachineState getWashingMachineState() {
return washingMachineState;
}
public void wash() {
synchronized (this) {
var machineState = getWashingMachineState();
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
if (this.washingMachineState == WashingMachineState.WASHING) {
LOGGER.error("Cannot wash if the machine has been already washing!");
return;
}
this.washingMachineState = WashingMachineState.WASHING;
}
LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
}
public synchronized void endOfWashing() {
washingMachineState = WashingMachineState.ENABLED;
LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
}
}
```
@@ -81,7 +81,7 @@ Here's the simple `DelayProvider` interface used by the `WashingMachine`.
```java
public interface DelayProvider {
void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
}
```
@@ -90,18 +90,18 @@ Now we introduce the application using the `WashingMachine`.
```java
public static void main(String...args){
final var washingMachine=new WashingMachine();
var executorService=Executors.newFixedThreadPool(3);
for(int i=0;i< 3;i++){
executorService.execute(washingMachine::wash);
}
executorService.shutdown();
try{
executorService.awaitTermination(10,TimeUnit.SECONDS);
}catch(InterruptedException ie){
LOGGER.error("ERROR: Waiting on executor service shutdown!");
Thread.currentThread().interrupt();
}
}
var executorService=Executors.newFixedThreadPool(3);
for(int i=0;i< 3;i++){
executorService.execute(washingMachine::wash);
}
executorService.shutdown();
try{
executorService.awaitTermination(10,TimeUnit.SECONDS);
}catch(InterruptedException ie){
LOGGER.error("ERROR: Waiting on executor service shutdown!");
Thread.currentThread().interrupt();
}
}
```
Here is the console output of the program.
+91 -91
View File
@@ -3,9 +3,9 @@ title: Bridge
category: Structural
language: en
tag:
- Decoupling
- Extensibility
- Gang of Four
- Decoupling
- Extensibility
- Gang of Four
---
## Also known as
@@ -21,8 +21,8 @@ Decouple an abstraction from its implementation so that the two can vary indepen
Real-world example
> Consider you have a weapon with different enchantments, and you are supposed to allow mixing different weapons with
> different enchantments. What would you do? Create multiple copies of each of the weapons for each of the enchantments or
> would you just create separate enchantment and set it for the weapon as needed? Bridge pattern allows you to do the
> different enchantments. What would you do? Create multiple copies of each of the weapons for each of the enchantments
> or would you just create separate enchantment and set it for the weapon as needed? Bridge pattern allows you to do the
> second.
In Plain Words
@@ -41,77 +41,77 @@ Translating our weapon example from above. Here we have the `Weapon` hierarchy:
```java
public interface Weapon {
void wield();
void wield();
void swing();
void swing();
void unwield();
void unwield();
Enchantment getEnchantment();
Enchantment getEnchantment();
}
public class Sword implements Weapon {
private final Enchantment enchantment;
private final Enchantment enchantment;
public Sword(Enchantment enchantment) {
this.enchantment = enchantment;
}
public Sword(Enchantment enchantment) {
this.enchantment = enchantment;
}
@Override
public void wield() {
LOGGER.info("The sword is wielded.");
enchantment.onActivate();
}
@Override
public void wield() {
LOGGER.info("The sword is wielded.");
enchantment.onActivate();
}
@Override
public void swing() {
LOGGER.info("The sword is swung.");
enchantment.apply();
}
@Override
public void swing() {
LOGGER.info("The sword is swung.");
enchantment.apply();
}
@Override
public void unwield() {
LOGGER.info("The sword is unwielded.");
enchantment.onDeactivate();
}
@Override
public void unwield() {
LOGGER.info("The sword is unwielded.");
enchantment.onDeactivate();
}
@Override
public Enchantment getEnchantment() {
return enchantment;
}
@Override
public Enchantment getEnchantment() {
return enchantment;
}
}
public class Hammer implements Weapon {
private final Enchantment enchantment;
private final Enchantment enchantment;
public Hammer(Enchantment enchantment) {
this.enchantment = enchantment;
}
public Hammer(Enchantment enchantment) {
this.enchantment = enchantment;
}
@Override
public void wield() {
LOGGER.info("The hammer is wielded.");
enchantment.onActivate();
}
@Override
public void wield() {
LOGGER.info("The hammer is wielded.");
enchantment.onActivate();
}
@Override
public void swing() {
LOGGER.info("The hammer is swung.");
enchantment.apply();
}
@Override
public void swing() {
LOGGER.info("The hammer is swung.");
enchantment.apply();
}
@Override
public void unwield() {
LOGGER.info("The hammer is unwielded.");
enchantment.onDeactivate();
}
@Override
public void unwield() {
LOGGER.info("The hammer is unwielded.");
enchantment.onDeactivate();
}
@Override
public Enchantment getEnchantment() {
return enchantment;
}
@Override
public Enchantment getEnchantment() {
return enchantment;
}
}
```
@@ -119,47 +119,47 @@ Here's the separate enchantment hierarchy:
```java
public interface Enchantment {
void onActivate();
void onActivate();
void apply();
void apply();
void onDeactivate();
void onDeactivate();
}
public class FlyingEnchantment implements Enchantment {
@Override
public void onActivate() {
LOGGER.info("The item begins to glow faintly.");
}
@Override
public void onActivate() {
LOGGER.info("The item begins to glow faintly.");
}
@Override
public void apply() {
LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand.");
}
@Override
public void apply() {
LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand.");
}
@Override
public void onDeactivate() {
LOGGER.info("The item's glow fades.");
}
@Override
public void onDeactivate() {
LOGGER.info("The item's glow fades.");
}
}
public class SoulEatingEnchantment implements Enchantment {
@Override
public void onActivate() {
LOGGER.info("The item spreads bloodlust.");
}
@Override
public void onActivate() {
LOGGER.info("The item spreads bloodlust.");
}
@Override
public void apply() {
LOGGER.info("The item eats the soul of enemies.");
}
@Override
public void apply() {
LOGGER.info("The item eats the soul of enemies.");
}
@Override
public void onDeactivate() {
LOGGER.info("Bloodlust slowly disappears.");
}
@Override
public void onDeactivate() {
LOGGER.info("Bloodlust slowly disappears.");
}
}
```
@@ -167,16 +167,16 @@ Here are both hierarchies in action:
```java
LOGGER.info("The knight receives an enchanted sword.");
var enchantedSword=new Sword(new SoulEatingEnchantment());
enchantedSword.wield();
enchantedSword.swing();
enchantedSword.unwield();
var enchantedSword=new Sword(new SoulEatingEnchantment());
enchantedSword.wield();
enchantedSword.swing();
enchantedSword.unwield();
LOGGER.info("The valkyrie receives an enchanted hammer.");
var hammer=new Hammer(new FlyingEnchantment());
hammer.wield();
hammer.swing();
hammer.unwield();
LOGGER.info("The valkyrie receives an enchanted hammer.");
var hammer=new Hammer(new FlyingEnchantment());
hammer.wield();
hammer.swing();
hammer.unwield();
```
Here's the console output.
+47 -47
View File
@@ -3,7 +3,7 @@ title: Builder
category: Creational
language: en
tag:
- Gang of Four
- Gang of Four
---
## Intent
@@ -34,7 +34,7 @@ all seen a constructor like below:
```java
public Hero(Profession profession,String name,HairType hairType,HairColor hairColor,Armor armor,Weapon weapon){
}
}
```
As you can see the number of constructor parameters can quickly get out of hand, and it may become difficult to
@@ -47,21 +47,21 @@ The sane alternative is to use the Builder pattern. First of all, we have our he
```java
public final class Hero {
private final Profession profession;
private final String name;
private final HairType hairType;
private final HairColor hairColor;
private final Armor armor;
private final Weapon weapon;
private final Profession profession;
private final String name;
private final HairType hairType;
private final HairColor hairColor;
private final Armor armor;
private final Weapon weapon;
private Hero(Builder builder) {
this.profession = builder.profession;
this.name = builder.name;
this.hairColor = builder.hairColor;
this.hairType = builder.hairType;
this.weapon = builder.weapon;
this.armor = builder.armor;
}
private Hero(Builder builder) {
this.profession = builder.profession;
this.name = builder.name;
this.hairColor = builder.hairColor;
this.hairType = builder.hairType;
this.weapon = builder.weapon;
this.armor = builder.armor;
}
}
```
@@ -69,44 +69,44 @@ Then we have the builder:
```java
public static class Builder {
private final Profession profession;
private final String name;
private HairType hairType;
private HairColor hairColor;
private Armor armor;
private Weapon weapon;
private final Profession profession;
private final String name;
private HairType hairType;
private HairColor hairColor;
private Armor armor;
private Weapon weapon;
public Builder(Profession profession, String name) {
if (profession == null || name == null) {
throw new IllegalArgumentException("profession and name can not be null");
public Builder(Profession profession, String name) {
if (profession == null || name == null) {
throw new IllegalArgumentException("profession and name can not be null");
}
this.profession = profession;
this.name = name;
}
this.profession = profession;
this.name = name;
}
public Builder withHairType(HairType hairType) {
this.hairType = hairType;
return this;
}
public Builder withHairType(HairType hairType) {
this.hairType = hairType;
return this;
}
public Builder withHairColor(HairColor hairColor) {
this.hairColor = hairColor;
return this;
}
public Builder withHairColor(HairColor hairColor) {
this.hairColor = hairColor;
return this;
}
public Builder withArmor(Armor armor) {
this.armor = armor;
return this;
}
public Builder withArmor(Armor armor) {
this.armor = armor;
return this;
}
public Builder withWeapon(Weapon weapon) {
this.weapon = weapon;
return this;
}
public Builder withWeapon(Weapon weapon) {
this.weapon = weapon;
return this;
}
public Hero build() {
return new Hero(this);
}
public Hero build() {
return new Hero(this);
}
}
```
+41 -41
View File
@@ -3,7 +3,7 @@ title: Business Delegate
category: Structural
language: en
tag:
- Decoupling
- Decoupling
---
## Intent
@@ -41,23 +41,23 @@ First, we have an abstraction for video streaming services and a couple of imple
```java
public interface VideoStreamingService {
void doProcessing();
void doProcessing();
}
@Slf4j
public class NetflixService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("NetflixService is now processing");
}
@Override
public void doProcessing() {
LOGGER.info("NetflixService is now processing");
}
}
@Slf4j
public class YouTubeService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("YouTubeService is now processing");
}
@Override
public void doProcessing() {
LOGGER.info("YouTubeService is now processing");
}
}
```
@@ -68,16 +68,16 @@ Then, we have a lookup service that decides which video streaming service to use
@Setter
public class BusinessLookup {
private NetflixService netflixService;
private YouTubeService youTubeService;
private NetflixService netflixService;
private YouTubeService youTubeService;
public VideoStreamingService getBusinessService(String movie) {
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
return netflixService;
} else {
return youTubeService;
public VideoStreamingService getBusinessService(String movie) {
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
return netflixService;
} else {
return youTubeService;
}
}
}
}
```
@@ -89,12 +89,12 @@ video streaming service.
@Setter
public class BusinessDelegate {
private BusinessLookup lookupService;
private BusinessLookup lookupService;
public void playbackMovie(String movie) {
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
videoStreamingService.doProcessing();
}
public void playbackMovie(String movie) {
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
videoStreamingService.doProcessing();
}
}
```
@@ -103,15 +103,15 @@ The mobile client utilizes Business Delegate to call the business tier.
```java
public class MobileClient {
private final BusinessDelegate businessDelegate;
private final BusinessDelegate businessDelegate;
public MobileClient(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public MobileClient(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public void playbackMovie(String movie) {
businessDelegate.playbackMovie(movie);
}
public void playbackMovie(String movie) {
businessDelegate.playbackMovie(movie);
}
}
```
@@ -120,18 +120,18 @@ Finally, we can demonstrate the complete example in action.
```java
public static void main(String[]args){
// prepare the objects
var businessDelegate=new BusinessDelegate();
var businessLookup=new BusinessLookup();
businessLookup.setNetflixService(new NetflixService());
businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
// prepare the objects
var businessDelegate=new BusinessDelegate();
var businessLookup=new BusinessLookup();
businessLookup.setNetflixService(new NetflixService());
businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
// create the client and use the Business Delegate
var client=new MobileClient(businessDelegate);
client.playbackMovie("Die Hard 2");
client.playbackMovie("Maradona: The Greatest Ever");
}
// create the client and use the Business Delegate
var client=new MobileClient(businessDelegate);
client.playbackMovie("Die Hard 2");
client.playbackMovie("Maradona: The Greatest Ever");
}
```
Here is the console output.
+123 -123
View File
@@ -3,7 +3,7 @@ title: Bytecode
category: Behavioral
language: en
tag:
- Game programming
- Game programming
---
## Intent
@@ -15,8 +15,8 @@ Allows encoding behavior as instructions for a virtual machine.
Real world example
> A team is working on a new game where wizards battle against each other. The wizard behavior needs to be carefully
> adjusted and iterated hundreds of times through playtesting. It's not optimal to ask the programmer to make changes each
> time the game designer wants to vary the behavior, so the wizard behavior is implemented as a data-driven virtual
> adjusted and iterated hundreds of times through playtesting. It's not optimal to ask the programmer to make changes
> each time the game designer wants to vary the behavior, so the wizard behavior is implemented as a data-driven virtual
> machine.
In plain words
@@ -42,21 +42,21 @@ One of the most important game objects is the `Wizard` class.
@Slf4j
public class Wizard {
private int health;
private int agility;
private int wisdom;
private int numberOfPlayedSounds;
private int numberOfSpawnedParticles;
private int health;
private int agility;
private int wisdom;
private int numberOfPlayedSounds;
private int numberOfSpawnedParticles;
public void playSound() {
LOGGER.info("Playing sound");
numberOfPlayedSounds++;
}
public void playSound() {
LOGGER.info("Playing sound");
numberOfPlayedSounds++;
}
public void spawnParticles() {
LOGGER.info("Spawning particles");
numberOfSpawnedParticles++;
}
public void spawnParticles() {
LOGGER.info("Spawning particles");
numberOfSpawnedParticles++;
}
}
```
@@ -70,18 +70,18 @@ together and pushes the result to the stack.
@Getter
public enum Instruction {
LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
ADD(10), // e.g. "ADD", pop 2 values, push their sum
DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
// ...
LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
ADD(10), // e.g. "ADD", pop 2 values, push their sum
DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
// ...
}
```
@@ -94,89 +94,89 @@ the game object behavior.
@Slf4j
public class VirtualMachine {
private final Stack<Integer> stack = new Stack<>();
private final Stack<Integer> stack = new Stack<>();
private final Wizard[] wizards = new Wizard[2];
private final Wizard[] wizards = new Wizard[2];
public VirtualMachine() {
wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
0, 0);
wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
0, 0);
}
public VirtualMachine(Wizard wizard1, Wizard wizard2) {
wizards[0] = wizard1;
wizards[1] = wizard2;
}
public void execute(int[] bytecode) {
for (var i = 0; i < bytecode.length; i++) {
Instruction instruction = Instruction.getInstruction(bytecode[i]);
switch (instruction) {
case LITERAL:
// Read the next byte from the bytecode.
int value = bytecode[++i];
// Push the next value to stack
stack.push(value);
break;
case SET_AGILITY:
var amount = stack.pop();
var wizard = stack.pop();
setAgility(wizard, amount);
break;
case SET_WISDOM:
amount = stack.pop();
wizard = stack.pop();
setWisdom(wizard, amount);
break;
case SET_HEALTH:
amount = stack.pop();
wizard = stack.pop();
setHealth(wizard, amount);
break;
case GET_HEALTH:
wizard = stack.pop();
stack.push(getHealth(wizard));
break;
case GET_AGILITY:
wizard = stack.pop();
stack.push(getAgility(wizard));
break;
case GET_WISDOM:
wizard = stack.pop();
stack.push(getWisdom(wizard));
break;
case ADD:
var a = stack.pop();
var b = stack.pop();
stack.push(a + b);
break;
case DIVIDE:
a = stack.pop();
b = stack.pop();
stack.push(b / a);
break;
case PLAY_SOUND:
wizard = stack.pop();
getWizards()[wizard].playSound();
break;
case SPAWN_PARTICLES:
wizard = stack.pop();
getWizards()[wizard].spawnParticles();
break;
default:
throw new IllegalArgumentException("Invalid instruction value");
}
LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());
public VirtualMachine() {
wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
0, 0);
wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
0, 0);
}
}
public void setHealth(int wizard, int amount) {
wizards[wizard].setHealth(amount);
}
// other setters ->
// ...
public VirtualMachine(Wizard wizard1, Wizard wizard2) {
wizards[0] = wizard1;
wizards[1] = wizard2;
}
public void execute(int[] bytecode) {
for (var i = 0; i < bytecode.length; i++) {
Instruction instruction = Instruction.getInstruction(bytecode[i]);
switch (instruction) {
case LITERAL:
// Read the next byte from the bytecode.
int value = bytecode[++i];
// Push the next value to stack
stack.push(value);
break;
case SET_AGILITY:
var amount = stack.pop();
var wizard = stack.pop();
setAgility(wizard, amount);
break;
case SET_WISDOM:
amount = stack.pop();
wizard = stack.pop();
setWisdom(wizard, amount);
break;
case SET_HEALTH:
amount = stack.pop();
wizard = stack.pop();
setHealth(wizard, amount);
break;
case GET_HEALTH:
wizard = stack.pop();
stack.push(getHealth(wizard));
break;
case GET_AGILITY:
wizard = stack.pop();
stack.push(getAgility(wizard));
break;
case GET_WISDOM:
wizard = stack.pop();
stack.push(getWisdom(wizard));
break;
case ADD:
var a = stack.pop();
var b = stack.pop();
stack.push(a + b);
break;
case DIVIDE:
a = stack.pop();
b = stack.pop();
stack.push(b / a);
break;
case PLAY_SOUND:
wizard = stack.pop();
getWizards()[wizard].playSound();
break;
case SPAWN_PARTICLES:
wizard = stack.pop();
getWizards()[wizard].spawnParticles();
break;
default:
throw new IllegalArgumentException("Invalid instruction value");
}
LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());
}
}
public void setHealth(int wizard, int amount) {
wizards[wizard].setHealth(amount);
}
// other setters ->
// ...
}
```
@@ -185,23 +185,23 @@ Now we can show the full example utilizing the virtual machine.
```java
public static void main(String[]args){
var vm=new VirtualMachine(
new Wizard(45,7,11,0,0),
new Wizard(36,18,8,0,0));
var vm=new VirtualMachine(
new Wizard(45,7,11,0,0),
new Wizard(36,18,8,0,0));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM"));
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2"));
vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE"));
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH"));
}
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM"));
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2"));
vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE"));
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH"));
}
```
Here is the console output.
+177 -177
View File
@@ -3,9 +3,9 @@ title: Caching
category: Performance optimization
language: en
tag:
- Caching
- Performance
- Cloud distributed
- Caching
- Performance
- Cloud distributed
---
## Intent
@@ -24,9 +24,9 @@ again.
Real world example
> A team is working on a website that provides new homes for abandoned cats. People can post their cats on the website
> after registering, but all the new posts require approval from one of the site moderators. The user accounts of the site
> moderators contain a specific flag and the data is stored in a MongoDB database. Checking for the moderator flag each
> time a post is viewed becomes expensive, and it's a good idea to utilize caching here.
> after registering, but all the new posts require approval from one of the site moderators. The user accounts of the
> site moderators contain a specific flag and the data is stored in a MongoDB database. Checking for the moderator flag
> each time a post is viewed becomes expensive, and it's a good idea to utilize caching here.
In plain words
@@ -36,9 +36,9 @@ Wikipedia says:
> In computing, a cache is a hardware or software component that stores data so that future requests for that data can
> be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored
> elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it cannot.
> Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower
> data store; thus, the more requests that can be served from the cache, the faster the system performs.
> elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it
> cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading
> from a slower data store; thus, the more requests that can be served from the cache, the faster the system performs.
**Programmatic Example**
@@ -53,24 +53,24 @@ to/from database.
@ToString
@EqualsAndHashCode
public class UserAccount {
private String userId;
private String userName;
private String additionalInfo;
private String userId;
private String userName;
private String additionalInfo;
}
public interface DbManager {
void connect();
void connect();
void disconnect();
void disconnect();
UserAccount readFromDb(String userId);
UserAccount readFromDb(String userId);
UserAccount writeToDb(UserAccount userAccount);
UserAccount writeToDb(UserAccount userAccount);
UserAccount updateDb(UserAccount userAccount);
UserAccount updateDb(UserAccount userAccount);
UserAccount upsertDb(UserAccount userAccount);
UserAccount upsertDb(UserAccount userAccount);
}
```
@@ -96,73 +96,73 @@ always at the end of the list.
@Slf4j
public class LruCache {
static class Node {
String userId;
UserAccount userAccount;
Node previous;
Node next;
static class Node {
String userId;
UserAccount userAccount;
Node previous;
Node next;
public Node(String userId, UserAccount userAccount) {
this.userId = userId;
this.userAccount = userAccount;
public Node(String userId, UserAccount userAccount) {
this.userId = userId;
this.userAccount = userAccount;
}
}
}
/* ... omitted details ... */
/* ... omitted details ... */
public LruCache(int capacity) {
this.capacity = capacity;
}
public UserAccount get(String userId) {
if (cache.containsKey(userId)) {
var node = cache.get(userId);
remove(node);
setHead(node);
return node.userAccount;
public LruCache(int capacity) {
this.capacity = capacity;
}
return null;
}
public void set(String userId, UserAccount userAccount) {
if (cache.containsKey(userId)) {
var old = cache.get(userId);
old.userAccount = userAccount;
remove(old);
setHead(old);
} else {
var newNode = new Node(userId, userAccount);
if (cache.size() >= capacity) {
LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
cache.remove(end.userId); // remove LRU data from cache.
remove(end);
setHead(newNode);
} else {
setHead(newNode);
}
cache.put(userId, newNode);
public UserAccount get(String userId) {
if (cache.containsKey(userId)) {
var node = cache.get(userId);
remove(node);
setHead(node);
return node.userAccount;
}
return null;
}
}
public boolean contains(String userId) {
return cache.containsKey(userId);
}
public void set(String userId, UserAccount userAccount) {
if (cache.containsKey(userId)) {
var old = cache.get(userId);
old.userAccount = userAccount;
remove(old);
setHead(old);
} else {
var newNode = new Node(userId, userAccount);
if (cache.size() >= capacity) {
LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
cache.remove(end.userId); // remove LRU data from cache.
remove(end);
setHead(newNode);
} else {
setHead(newNode);
}
cache.put(userId, newNode);
}
}
public void remove(Node node) { /* ... */ }
public boolean contains(String userId) {
return cache.containsKey(userId);
}
public void setHead(Node node) { /* ... */ }
public void remove(Node node) { /* ... */ }
public void invalidate(String userId) { /* ... */ }
public void setHead(Node node) { /* ... */ }
public boolean isFull() { /* ... */ }
public void invalidate(String userId) { /* ... */ }
public UserAccount getLruData() { /* ... */ }
public boolean isFull() { /* ... */ }
public void clear() { /* ... */ }
public UserAccount getLruData() { /* ... */ }
public List<UserAccount> getCacheDataInListForm() { /* ... */ }
public void clear() { /* ... */ }
public void setCapacity(int newCapacity) { /* ... */ }
public List<UserAccount> getCacheDataInListForm() { /* ... */ }
public void setCapacity(int newCapacity) { /* ... */ }
}
```
@@ -173,58 +173,58 @@ The next layer we are going to look at is `CacheStore` which implements the diff
@Slf4j
public class CacheStore {
private static final int CAPACITY = 3;
private static LruCache cache;
private final DbManager dbManager;
private static final int CAPACITY = 3;
private static LruCache cache;
private final DbManager dbManager;
/* ... details omitted ... */
/* ... details omitted ... */
public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# Found in Cache!");
return cache.get(userId);
public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# Found in Cache!");
return cache.get(userId);
}
LOGGER.info("# Not found in cache! Go to DB!!");
UserAccount userAccount = dbManager.readFromDb(userId);
cache.set(userId, userAccount);
return userAccount;
}
LOGGER.info("# Not found in cache! Go to DB!!");
UserAccount userAccount = dbManager.readFromDb(userId);
cache.set(userId, userAccount);
return userAccount;
}
public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
} else {
dbManager.writeToDb(userAccount);
public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
} else {
dbManager.writeToDb(userAccount);
}
cache.set(userAccount.getUserId(), userAccount);
}
cache.set(userAccount.getUserId(), userAccount);
}
public void writeAround(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
// Cache data has been updated -- remove older
cache.invalidate(userAccount.getUserId());
// version from cache.
} else {
dbManager.writeToDb(userAccount);
public void writeAround(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
// Cache data has been updated -- remove older
cache.invalidate(userAccount.getUserId());
// version from cache.
} else {
dbManager.writeToDb(userAccount);
}
}
}
public static void clearCache() {
if (cache != null) {
cache.clear();
public static void clearCache() {
if (cache != null) {
cache.clear();
}
}
}
public static void flushCache() {
LOGGER.info("# flushCache...");
Optional.ofNullable(cache)
.map(LruCache::getCacheDataInListForm)
.orElse(List.of())
.forEach(DbManager::updateDb);
}
public static void flushCache() {
LOGGER.info("# flushCache...");
Optional.ofNullable(cache)
.map(LruCache::getCacheDataInListForm)
.orElse(List.of())
.forEach(DbManager::updateDb);
}
/* ... omitted the implementation of other caching strategies ... */
/* ... omitted the implementation of other caching strategies ... */
}
```
@@ -239,50 +239,50 @@ the appropriate function in the `CacheStore` class.
@Slf4j
public final class AppManager {
private static CachingPolicy cachingPolicy;
private final DbManager dbManager;
private final CacheStore cacheStore;
private static CachingPolicy cachingPolicy;
private final DbManager dbManager;
private final CacheStore cacheStore;
private AppManager() {
}
public void initDb() { /* ... */ }
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
public static void initCacheCapacity(int capacity) { /* ... */ }
public UserAccount find(final String userId) {
LOGGER.info("Trying to find {} in cache", userId);
if (cachingPolicy == CachingPolicy.THROUGH
|| cachingPolicy == CachingPolicy.AROUND) {
return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
return cacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
private AppManager() {
}
return null;
}
public void save(final UserAccount userAccount) {
LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
cacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
public void initDb() { /* ... */ }
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
public static void initCacheCapacity(int capacity) { /* ... */ }
public UserAccount find(final String userId) {
LOGGER.info("Trying to find {} in cache", userId);
if (cachingPolicy == CachingPolicy.THROUGH
|| cachingPolicy == CachingPolicy.AROUND) {
return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
return cacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
}
return null;
}
}
public static String printCacheContent() {
return CacheStore.print();
}
public void save(final UserAccount userAccount) {
LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
cacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
}
}
/* ... details omitted ... */
public static String printCacheContent() {
return CacheStore.print();
}
/* ... details omitted ... */
}
```
@@ -293,42 +293,42 @@ Here is what we do in the main class of the application.
@Slf4j
public class App {
public static void main(final String[] args) {
boolean isDbMongo = isDbMongo(args);
if (isDbMongo) {
LOGGER.info("Using the Mongo database engine to run the application.");
} else {
LOGGER.info("Using the 'in Memory' database to run the application.");
public static void main(final String[] args) {
boolean isDbMongo = isDbMongo(args);
if (isDbMongo) {
LOGGER.info("Using the Mongo database engine to run the application.");
} else {
LOGGER.info("Using the 'in Memory' database to run the application.");
}
App app = new App(isDbMongo);
app.useReadAndWriteThroughStrategy();
String splitLine = "==============================================";
LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
LOGGER.info(splitLine);
app.useCacheAsideStategy();
LOGGER.info(splitLine);
}
App app = new App(isDbMongo);
app.useReadAndWriteThroughStrategy();
String splitLine = "==============================================";
LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
LOGGER.info(splitLine);
app.useCacheAsideStategy();
LOGGER.info(splitLine);
}
public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH");
appManager.initCachingPolicy(CachingPolicy.THROUGH);
public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH");
appManager.initCachingPolicy(CachingPolicy.THROUGH);
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
appManager.save(userAccount1);
LOGGER.info(appManager.printCacheContent());
appManager.find("001");
appManager.find("001");
}
appManager.save(userAccount1);
LOGGER.info(appManager.printCacheContent());
appManager.find("001");
appManager.find("001");
}
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
public void useReadThroughAndWriteBehindStrategy() { /* ... */ }
public void useReadThroughAndWriteBehindStrategy() { /* ... */ }
public void useCacheAsideStrategy() { /* ... */ }
public void useCacheAsideStrategy() { /* ... */ }
}
```
+15 -15
View File
@@ -3,10 +3,10 @@ title: Callback
category: Functional
language: en
tag:
- Asynchronous
- Decoupling
- Idiom
- Reactive
- Asynchronous
- Decoupling
- Idiom
- Reactive
---
## Intent
@@ -42,7 +42,7 @@ Callback is a simple interface with single method.
```java
public interface Callback {
void call();
void call();
}
```
@@ -51,21 +51,21 @@ Next we define a task that will execute the callback after the task execution ha
```java
public abstract class Task {
final void executeWith(Callback callback) {
execute();
Optional.ofNullable(callback).ifPresent(Callback::call);
}
final void executeWith(Callback callback) {
execute();
Optional.ofNullable(callback).ifPresent(Callback::call);
}
public abstract void execute();
public abstract void execute();
}
@Slf4j
public final class SimpleTask extends Task {
@Override
public void execute() {
LOGGER.info("Perform some important activity and after call the callback method.");
}
@Override
public void execute() {
LOGGER.info("Perform some important activity and after call the callback method.");
}
}
```
@@ -73,7 +73,7 @@ Finally, here's how we execute a task and receive a callback when it's finished.
```java
var task=new SimpleTask();
task.executeWith(()->LOGGER.info("I'm done now."));
task.executeWith(()->LOGGER.info("I'm done now."));
```
## Class diagram
+56 -56
View File
@@ -3,8 +3,8 @@ title: Chain of responsibility
category: Behavioral
language: en
tag:
- Gang of Four
- Decoupling
- Gang of Four
- Decoupling
---
## Also known as
@@ -48,27 +48,27 @@ import lombok.Getter;
@Getter
public class Request {
private final RequestType requestType;
private final String requestDescription;
private boolean handled;
private final RequestType requestType;
private final String requestDescription;
private boolean handled;
public Request(final RequestType requestType, final String requestDescription) {
this.requestType = Objects.requireNonNull(requestType);
this.requestDescription = Objects.requireNonNull(requestDescription);
}
public Request(final RequestType requestType, final String requestDescription) {
this.requestType = Objects.requireNonNull(requestType);
this.requestDescription = Objects.requireNonNull(requestDescription);
}
public void markHandled() {
this.handled = true;
}
public void markHandled() {
this.handled = true;
}
@Override
public String toString() {
return getRequestDescription();
}
@Override
public String toString() {
return getRequestDescription();
}
}
public enum RequestType {
DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
}
```
@@ -77,37 +77,37 @@ Next, we show the request handler hierarchy.
```java
public interface RequestHandler {
boolean canHandleRequest(Request req);
boolean canHandleRequest(Request req);
int getPriority();
int getPriority();
void handle(Request req);
void handle(Request req);
String name();
String name();
}
@Slf4j
public class OrcCommander implements RequestHandler {
@Override
public boolean canHandleRequest(Request req) {
return req.getRequestType() == RequestType.DEFEND_CASTLE;
}
@Override
public boolean canHandleRequest(Request req) {
return req.getRequestType() == RequestType.DEFEND_CASTLE;
}
@Override
public int getPriority() {
return 2;
}
@Override
public int getPriority() {
return 2;
}
@Override
public void handle(Request req) {
req.markHandled();
LOGGER.info("{} handling request \"{}\"", name(), req);
}
@Override
public void handle(Request req) {
req.markHandled();
LOGGER.info("{} handling request \"{}\"", name(), req);
}
@Override
public String name() {
return "Orc commander";
}
@Override
public String name() {
return "Orc commander";
}
}
// OrcOfficer and OrcSoldier are defined similarly as OrcCommander
@@ -119,24 +119,24 @@ The Orc King gives the orders and forms the chain.
```java
public class OrcKing {
private List<RequestHandler> handlers;
private List<RequestHandler> handlers;
public OrcKing() {
buildChain();
}
public OrcKing() {
buildChain();
}
private void buildChain() {
handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier());
}
private void buildChain() {
handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier());
}
public void makeRequest(Request req) {
handlers
.stream()
.sorted(Comparator.comparing(RequestHandler::getPriority))
.filter(handler -> handler.canHandleRequest(req))
.findFirst()
.ifPresent(handler -> handler.handle(req));
}
public void makeRequest(Request req) {
handlers
.stream()
.sorted(Comparator.comparing(RequestHandler::getPriority))
.filter(handler -> handler.canHandleRequest(req))
.findFirst()
.ifPresent(handler -> handler.handle(req));
}
}
```
@@ -144,9 +144,9 @@ The chain of responsibility in action.
```java
var king=new OrcKing();
king.makeRequest(new Request(RequestType.DEFEND_CASTLE,"defend castle"));
king.makeRequest(new Request(RequestType.TORTURE_PRISONER,"torture prisoner"));
king.makeRequest(new Request(RequestType.COLLECT_TAX,"collect tax"));
king.makeRequest(new Request(RequestType.DEFEND_CASTLE,"defend castle"));
king.makeRequest(new Request(RequestType.TORTURE_PRISONER,"torture prisoner"));
king.makeRequest(new Request(RequestType.COLLECT_TAX,"collect tax"));
```
The console output.
+196 -197
View File
@@ -3,9 +3,9 @@ title: Circuit Breaker
category: Resilience
language: en
tag:
- Cloud distributed
- Fault tolerance
- Microservices
- Cloud distributed
- Fault tolerance
- Microservices
---
## Also known as
@@ -28,10 +28,9 @@ Real world example
> services is slow or not responding successfully, our application will try to fetch response from
> the remote service using multiple threads/processes, soon all of them will hang (also called
> [thread starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science))) causing our entire web application
> to crash. We should be able to detect
> this situation and show the user an appropriate message so that he/she can explore other parts of
> the app unaffected by the remote service failure. Meanwhile, the other services that are working
> normally, should keep functioning unaffected by this failure.
> to crash. We should be able to detect this situation and show the user an appropriate message so that he/she can
> explore other parts of the app unaffected by the remote serv'ice failure. Meanwhile, the other services that are
> working normally, should keep functioning unaffected by this failure.
In plain words
@@ -62,59 +61,59 @@ In terms of code, the end user application is:
@Slf4j
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
var serverStartTime = System.nanoTime();
var serverStartTime = System.nanoTime();
var delayedService = new DelayedRemoteService(serverStartTime, 5);
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
2000 * 1000 * 1000);
var delayedService = new DelayedRemoteService(serverStartTime, 5);
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
2000 * 1000 * 1000);
var quickService = new QuickRemoteService();
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
2000 * 1000 * 1000);
var quickService = new QuickRemoteService();
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
2000 * 1000 * 1000);
//Create an object of monitoring service which makes both local and remote calls
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
quickServiceCircuitBreaker);
//Create an object of monitoring service which makes both local and remote calls
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
quickServiceCircuitBreaker);
//Fetch response from local resource
LOGGER.info(monitoringService.localResourceResponse());
//Fetch response from local resource
LOGGER.info(monitoringService.localResourceResponse());
//Fetch response from delayed service 2 times, to meet the failure threshold
LOGGER.info(monitoringService.delayedServiceResponse());
LOGGER.info(monitoringService.delayedServiceResponse());
//Fetch response from delayed service 2 times, to meet the failure threshold
LOGGER.info(monitoringService.delayedServiceResponse());
LOGGER.info(monitoringService.delayedServiceResponse());
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
//which is OPEN now
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
//which is OPEN now
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
LOGGER.info(monitoringService.quickServiceResponse());
LOGGER.info(quickServiceCircuitBreaker.getState());
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
LOGGER.info(monitoringService.quickServiceResponse());
LOGGER.info(quickServiceCircuitBreaker.getState());
//Wait for the delayed service to become responsive
try {
LOGGER.info("Waiting for delayed service to become responsive");
Thread.sleep(5000);
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
//Wait for the delayed service to become responsive
try {
LOGGER.info("Waiting for delayed service to become responsive");
Thread.sleep(5000);
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
}
//Check the state of delayed circuit breaker, should be HALF_OPEN
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Fetch response from delayed service, which should be healthy by now
LOGGER.info(monitoringService.delayedServiceResponse());
//As successful response is fetched, it should be CLOSED again.
LOGGER.info(delayedServiceCircuitBreaker.getState());
}
//Check the state of delayed circuit breaker, should be HALF_OPEN
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Fetch response from delayed service, which should be healthy by now
LOGGER.info(monitoringService.delayedServiceResponse());
//As successful response is fetched, it should be CLOSED again.
LOGGER.info(delayedServiceCircuitBreaker.getState());
}
}
```
@@ -123,45 +122,45 @@ The monitoring service:
```java
public class MonitoringService {
private final CircuitBreaker delayedService;
private final CircuitBreaker delayedService;
private final CircuitBreaker quickService;
private final CircuitBreaker quickService;
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
this.delayedService = delayedService;
this.quickService = quickService;
}
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
public String localResourceResponse() {
return "Local Service is working";
}
/**
* Fetch response from the delayed service (with some simulated startup time).
*
* @return response string
*/
public String delayedServiceResponse() {
try {
return this.delayedService.attemptRequest();
} catch (RemoteServiceException e) {
return e.getMessage();
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
this.delayedService = delayedService;
this.quickService = quickService;
}
}
/**
* Fetches response from a healthy service without any failure.
*
* @return response string
*/
public String quickServiceResponse() {
try {
return this.quickService.attemptRequest();
} catch (RemoteServiceException e) {
return e.getMessage();
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
public String localResourceResponse() {
return "Local Service is working";
}
/**
* Fetch response from the delayed service (with some simulated startup time).
*
* @return response string
*/
public String delayedServiceResponse() {
try {
return this.delayedService.attemptRequest();
} catch (RemoteServiceException e) {
return e.getMessage();
}
}
/**
* Fetches response from a healthy service without any failure.
*
* @return response string
*/
public String quickServiceResponse() {
try {
return this.quickService.attemptRequest();
} catch (RemoteServiceException e) {
return e.getMessage();
}
}
}
}
```
@@ -171,128 +170,128 @@ remote (costly) service in a circuit breaker object, which prevents faults as fo
```java
public class DefaultCircuitBreaker implements CircuitBreaker {
private final long timeout;
private final long retryTimePeriod;
private final RemoteService service;
long lastFailureTime;
private String lastFailureResponse;
int failureCount;
private final int failureThreshold;
private State state;
// Future time offset, in nanoseconds
private final long futureTime = 1_000_000_000_000L;
private final long timeout;
private final long retryTimePeriod;
private final RemoteService service;
long lastFailureTime;
private String lastFailureResponse;
int failureCount;
private final int failureThreshold;
private State state;
// Future time offset, in nanoseconds
private final long futureTime = 1_000_000_000_000L;
/**
* Constructor to create an instance of Circuit Breaker.
*
* @param timeout Timeout for the API request. Not necessary for this simple example
* @param failureThreshold Number of failures we receive from the depended service before changing
* state to 'OPEN'
* @param retryTimePeriod Time period after which a new request is made to remote service for
* status check.
*/
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
long retryTimePeriod) {
this.service = serviceToCall;
// We start in a closed state hoping that everything is fine
this.state = State.CLOSED;
this.failureThreshold = failureThreshold;
// Timeout for the API request.
// Used to break the calls made to remote resource if it exceeds the limit
this.timeout = timeout;
this.retryTimePeriod = retryTimePeriod;
//An absurd amount of time in future which basically indicates the last failure never happened
this.lastFailureTime = System.nanoTime() + futureTime;
this.failureCount = 0;
}
// Reset everything to defaults
@Override
public void recordSuccess() {
this.failureCount = 0;
this.lastFailureTime = System.nanoTime() + futureTime;
this.state = State.CLOSED;
}
@Override
public void recordFailure(String response) {
failureCount = failureCount + 1;
this.lastFailureTime = System.nanoTime();
// Cache the failure response for returning on open state
this.lastFailureResponse = response;
}
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
protected void evaluateState() {
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
//We have waited long enough and should try checking if service is up
state = State.HALF_OPEN;
} else {
//Service would still probably be down
state = State.OPEN;
}
} else {
//Everything is working fine
state = State.CLOSED;
/**
* Constructor to create an instance of Circuit Breaker.
*
* @param timeout Timeout for the API request. Not necessary for this simple example
* @param failureThreshold Number of failures we receive from the depended service before changing
* state to 'OPEN'
* @param retryTimePeriod Time period after which a new request is made to remote service for
* status check.
*/
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
long retryTimePeriod) {
this.service = serviceToCall;
// We start in a closed state hoping that everything is fine
this.state = State.CLOSED;
this.failureThreshold = failureThreshold;
// Timeout for the API request.
// Used to break the calls made to remote resource if it exceeds the limit
this.timeout = timeout;
this.retryTimePeriod = retryTimePeriod;
//An absurd amount of time in future which basically indicates the last failure never happened
this.lastFailureTime = System.nanoTime() + futureTime;
this.failureCount = 0;
}
}
@Override
public String getState() {
evaluateState();
return state.name();
}
// Reset everything to defaults
@Override
public void recordSuccess() {
this.failureCount = 0;
this.lastFailureTime = System.nanoTime() + futureTime;
this.state = State.CLOSED;
}
/**
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
* service comes online before expected.
*
* @param state State at which circuit is in
*/
@Override
public void setState(State state) {
this.state = state;
switch (state) {
case OPEN -> {
this.failureCount = failureThreshold;
@Override
public void recordFailure(String response) {
failureCount = failureCount + 1;
this.lastFailureTime = System.nanoTime();
}
case HALF_OPEN -> {
this.failureCount = failureThreshold;
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
}
default -> this.failureCount = 0;
// Cache the failure response for returning on open state
this.lastFailureResponse = response;
}
}
/**
* Executes service call.
*
* @return Value from the remote resource, stale response or a custom exception
*/
@Override
public String attemptRequest() throws RemoteServiceException {
evaluateState();
if (state == State.OPEN) {
// return cached response if the circuit is in OPEN state
return this.lastFailureResponse;
} else {
// Make the API request if the circuit is not OPEN
try {
//In a real application, this would be run in a thread and the timeout
//parameter of the circuit breaker would be utilized to know if service
//is working. Here, we simulate that based on server response itself
var response = service.call();
// Yay!! the API responded fine. Let's reset everything.
recordSuccess();
return response;
} catch (RemoteServiceException ex) {
recordFailure(ex.getMessage());
throw ex;
}
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
protected void evaluateState() {
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
//We have waited long enough and should try checking if service is up
state = State.HALF_OPEN;
} else {
//Service would still probably be down
state = State.OPEN;
}
} else {
//Everything is working fine
state = State.CLOSED;
}
}
@Override
public String getState() {
evaluateState();
return state.name();
}
/**
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
* service comes online before expected.
*
* @param state State at which circuit is in
*/
@Override
public void setState(State state) {
this.state = state;
switch (state) {
case OPEN -> {
this.failureCount = failureThreshold;
this.lastFailureTime = System.nanoTime();
}
case HALF_OPEN -> {
this.failureCount = failureThreshold;
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
}
default -> this.failureCount = 0;
}
}
/**
* Executes service call.
*
* @return Value from the remote resource, stale response or a custom exception
*/
@Override
public String attemptRequest() throws RemoteServiceException {
evaluateState();
if (state == State.OPEN) {
// return cached response if the circuit is in OPEN state
return this.lastFailureResponse;
} else {
// Make the API request if the circuit is not OPEN
try {
//In a real application, this would be run in a thread and the timeout
//parameter of the circuit breaker would be utilized to know if service
//is working. Here, we simulate that based on server response itself
var response = service.call();
// Yay!! the API responded fine. Let's reset everything.
recordSuccess();
return response;
} catch (RemoteServiceException ex) {
recordFailure(ex.getMessage());
throw ex;
}
}
}
}
}
```
+27 -27
View File
@@ -3,8 +3,8 @@ title: Client Session
category: Behavioral
language: en
tags:
- Session management
- Web development
- Session management
- Web development
---
## Also known as
@@ -21,17 +21,17 @@ application, ensuring a continuous and personalized user experience.
Real-World Example
> You're looking to create a data management app allowing users to send requests to the server to modify and make
> changes to data stored on their devices. These requests are small and the data is individual to each user, negating the
> need for a large scale database implementation. Using the client session pattern, you are able to handle multiple
> changes to data stored on their devices. These requests are small and the data is individual to each user, negating
> the need for a large scale database implementation. Using the client session pattern, you are able to handle multiple
> concurrent requests, load balancing clients across different servers with ease due to servers remaining stateless. You
> also remove the need to store session IDs on the server side due to clients providing all the information that a server
> needs to perform their process.
> also remove the need to store session IDs on the server side due to clients providing all the information that a
> server needs to perform their process.
In Plain words
> Instead of storing information about the current client and the information being accessed on the server, it is
> maintained client side only. Client has to send session data with each request to the server and has to send an updated
> state back to the client, which is stored on the clients machine. The server doesn't have to store the client
> maintained client side only. Client has to send session data with each request to the server and has to send an
> updated state back to the client, which is stored on the clients machine. The server doesn't have to store the client
> information. ([ref](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client))
**Programmatic Example**
@@ -46,30 +46,30 @@ session information in every request helps the server identify the client and pr
```java
public class App {
public static void main(String[] args) {
var server = new Server("localhost", 8080);
var session1 = server.getSession("Session1");
var session2 = server.getSession("Session2");
var request1 = new Request("Data1", session1);
var request2 = new Request("Data2", session2);
server.process(request1);
server.process(request2);
}
public static void main(String[] args) {
var server = new Server("localhost", 8080);
var session1 = server.getSession("Session1");
var session2 = server.getSession("Session2");
var request1 = new Request("Data1", session1);
var request2 = new Request("Data2", session2);
server.process(request1);
server.process(request2);
}
}
@Data
@AllArgsConstructor
public class Session {
/**
* Session id.
*/
private String id;
/**
* Session id.
*/
private String id;
/**
* Client name.
*/
private String clientName;
/**
* Client name.
*/
private String clientName;
}
@@ -77,9 +77,9 @@ public class Session {
@AllArgsConstructor
public class Request {
private String data;
private String data;
private Session session;
private Session session;
}
```
+74 -70
View File
@@ -3,8 +3,8 @@ title: Collecting Parameter
category: Behavioral
language: en
tag:
- Accumulation
- Generic
- Accumulation
- Generic
---
## Also known as
@@ -55,33 +55,33 @@ import java.util.LinkedList;
import java.util.Queue;
public class App {
static PrinterQueue printerQueue = PrinterQueue.getInstance();
static PrinterQueue printerQueue = PrinterQueue.getInstance();
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
/*
Initialising the printer queue with jobs
*/
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A4, 5, false, false));
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A3, 2, false, false));
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A2, 5, false, false));
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A4, 5, false, false));
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A3, 2, false, false));
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A2, 5, false, false));
/*
This variable is the collecting parameter.
*/
var result = new LinkedList<PrinterItem>();
var result = new LinkedList<PrinterItem>();
/*
* Using numerous sub-methods to collaboratively add information to the result collecting parameter
*/
addA4Papers(result);
addA3Papers(result);
addA2Papers(result);
}
/*
* Using numerous sub-methods to collaboratively add information to the result collecting parameter
*/
addA4Papers(result);
addA3Papers(result);
addA2Papers(result);
}
}
```
@@ -90,71 +90,75 @@ appropriate print jobs as per the policy described previously. The three policie
```java
public class App {
static PrinterQueue printerQueue = PrinterQueue.getInstance();
static PrinterQueue printerQueue = PrinterQueue.getInstance();
/**
* Adds A4 document jobs to the collecting parameter according to some policy that can be whatever the client
* (the print center) wants.
*
* @param printerItemsCollection the collecting parameter
*/
public static void addA4Papers(Queue<PrinterItem> printerItemsCollection) {
/**
* Adds A4 document jobs to the collecting parameter according to some policy that can be whatever the client
* (the print center) wants.
*
* @param printerItemsCollection the collecting parameter
*/
public static void addA4Papers(Queue<PrinterItem> printerItemsCollection) {
/*
Iterate through the printer queue, and add A4 papers according to the correct policy to the collecting parameter,
which is 'printerItemsCollection' in this case.
*/
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
if (nextItem.paperSize.equals(PaperSizes.A4)) {
var isColouredAndSingleSided = nextItem.isColour && !nextItem.isDoubleSided;
if (isColouredAndSingleSided) {
printerItemsCollection.add(nextItem);
} else if (!nextItem.isColour) {
printerItemsCollection.add(nextItem);
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
if (nextItem.paperSize.equals(PaperSizes.A4)) {
var isColouredAndSingleSided =
nextItem.isColour && !nextItem.isDoubleSided;
if (isColouredAndSingleSided) {
printerItemsCollection.add(nextItem);
} else if (!nextItem.isColour) {
printerItemsCollection.add(nextItem);
}
}
}
}
}
}
/**
* Adds A3 document jobs to the collecting parameter according to some policy that can be whatever the client
* (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate
* the wants of the client.
*
* @param printerItemsCollection the collecting parameter
*/
public static void addA3Papers(Queue<PrinterItem> printerItemsCollection) {
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
if (nextItem.paperSize.equals(PaperSizes.A3)) {
/**
* Adds A3 document jobs to the collecting parameter according to some policy that can be whatever the client
* (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate
* the wants of the client.
*
* @param printerItemsCollection the collecting parameter
*/
public static void addA3Papers(Queue<PrinterItem> printerItemsCollection) {
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
if (nextItem.paperSize.equals(PaperSizes.A3)) {
// Encoding the policy into a Boolean: the A3 paper cannot be coloured and double-sided at the same time
var isNotColouredAndSingleSided = !nextItem.isColour && !nextItem.isDoubleSided;
if (isNotColouredAndSingleSided) {
printerItemsCollection.add(nextItem);
// Encoding the policy into a Boolean: the A3 paper cannot be coloured and double-sided at the same time
var isNotColouredAndSingleSided =
!nextItem.isColour && !nextItem.isDoubleSided;
if (isNotColouredAndSingleSided) {
printerItemsCollection.add(nextItem);
}
}
}
}
}
}
/**
* Adds A2 document jobs to the collecting parameter according to some policy that can be whatever the client
* (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate
* the wants of the client.
*
* @param printerItemsCollection the collecting parameter
*/
public static void addA2Papers(Queue<PrinterItem> printerItemsCollection) {
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
if (nextItem.paperSize.equals(PaperSizes.A2)) {
/**
* Adds A2 document jobs to the collecting parameter according to some policy that can be whatever the client
* (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate
* the wants of the client.
*
* @param printerItemsCollection the collecting parameter
*/
public static void addA2Papers(Queue<PrinterItem> printerItemsCollection) {
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
if (nextItem.paperSize.equals(PaperSizes.A2)) {
// Encoding the policy into a Boolean: the A2 paper must be single page, single-sided, and non-coloured.
var isNotColouredSingleSidedAndOnePage = nextItem.pageCount == 1 && !nextItem.isDoubleSided
&& !nextItem.isColour;
if (isNotColouredSingleSidedAndOnePage) {
printerItemsCollection.add(nextItem);
// Encoding the policy into a Boolean: the A2 paper must be single page, single-sided, and non-coloured.
var isNotColouredSingleSidedAndOnePage =
nextItem.pageCount == 1 &&
!nextItem.isDoubleSided
&& !nextItem.isColour;
if (isNotColouredSingleSidedAndOnePage) {
printerItemsCollection.add(nextItem);
}
}
}
}
}
}
}
```