diff --git a/.all-contributorsrc b/.all-contributorsrc index 24343881a7ba..b2fef6640890 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1548,7 +1548,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/17254162?v=4", "profile": "https://www.linkedin.com/in/souzasamuel/", "contributions": [ - "code" + "code", + "doc" ] }, { @@ -2546,7 +2547,7 @@ "login": "tiennm99", "name": "Tien Nguyen Minh", "avatar_url": "https://avatars.githubusercontent.com/u/39063457?v=4", - "profile": "http://miti99.dev", + "profile": "https://github.com/tiennm99", "contributions": [ "code", "translation" @@ -2567,7 +2568,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/101238933?v=4", "profile": "https://surjendu-pal.netlify.app/", "contributions": [ - "translation" + "translation", + "code" ] }, { @@ -2963,7 +2965,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/76624290?v=4", "profile": "https://github.com/MaverickWingman", "contributions": [ - "doc" + "doc", + "code" ] }, { @@ -2990,7 +2993,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/44934142?v=4", "profile": "https://github.com/luismateoh", "contributions": [ - "translation" + "translation", + "review" ] }, { @@ -3028,9 +3032,470 @@ "contributions": [ "code" ] + }, + { + "login": "junhkang", + "name": "Jun Kang", + "avatar_url": "https://avatars.githubusercontent.com/u/20232155?v=4", + "profile": "https://junhkang.tistory.com/", + "contributions": [ + "code" + ] + }, + { + "login": "KishalayP", + "name": "Kishalay Pandey", + "avatar_url": "https://avatars.githubusercontent.com/u/71012321?v=4", + "profile": "https://github.com/KishalayP", + "contributions": [ + "code" + ] + }, + { + "login": "drishtii7", + "name": "drishtii7", + "avatar_url": "https://avatars.githubusercontent.com/u/82076566?v=4", + "profile": "https://github.com/drishtii7", + "contributions": [ + "code" + ] + }, + { + "login": "DavidMedinaO", + "name": "David Medina Orozco", + "avatar_url": "https://avatars.githubusercontent.com/u/53974843?v=4", + "profile": "https://github.com/DavidMedinaO", + "contributions": [ + "translation", + "review" + ] + }, + { + "login": "Romo4ka-bot", + "name": "Roman Leontev", + "avatar_url": "https://avatars.githubusercontent.com/u/61774094?v=4", + "profile": "https://github.com/Romo4ka-bot", + "contributions": [ + "code" + ] + }, + { + "login": "Ehspresso", + "name": "Riley", + "avatar_url": "https://avatars.githubusercontent.com/u/144370752?v=4", + "profile": "https://github.com/Ehspresso", + "contributions": [ + "code" + ] + }, + { + "login": "k1w1dev", + "name": "k1w1dev", + "avatar_url": "https://avatars.githubusercontent.com/u/121696782?v=4", + "profile": "https://github.com/k1w1dev", + "contributions": [ + "code" + ] + }, + { + "login": "dev-yugantar", + "name": "dev-yugantar", + "avatar_url": "https://avatars.githubusercontent.com/u/153066190?v=4", + "profile": "https://github.com/dev-yugantar", + "contributions": [ + "code" + ] + }, + { + "login": "Adelechka", + "name": "Adelya", + "avatar_url": "https://avatars.githubusercontent.com/u/65678470?v=4", + "profile": "https://github.com/Adelechka", + "contributions": [ + "code" + ] + }, + { + "login": "gatlanagaprasanna", + "name": "gatlanagaprasanna", + "avatar_url": "https://avatars.githubusercontent.com/u/154739216?v=4", + "profile": "https://github.com/gatlanagaprasanna", + "contributions": [ + "doc" + ] + }, + { + "login": "Avinash2110", + "name": "Avinash Shukla", + "avatar_url": "https://avatars.githubusercontent.com/u/37360069?v=4", + "profile": "https://github.com/Avinash2110", + "contributions": [ + "code" + ] + }, + { + "login": "Mayankchoudhary294", + "name": "Mayank Choudhary", + "avatar_url": "https://avatars.githubusercontent.com/u/97609699?v=4", + "profile": "https://github.com/Mayankchoudhary294", + "contributions": [ + "code" + ] + }, + { + "login": "romannimets", + "name": "romannimets", + "avatar_url": "https://avatars.githubusercontent.com/u/137268574?v=4", + "profile": "https://github.com/romannimets", + "contributions": [ + "code" + ] + }, + { + "login": "Joel-Dandin", + "name": "Joel", + "avatar_url": "https://avatars.githubusercontent.com/u/60149879?v=4", + "profile": "https://github.com/Joel-Dandin", + "contributions": [ + "code" + ] + }, + { + "login": "244Walyson", + "name": "Walyson Moises", + "avatar_url": "https://avatars.githubusercontent.com/u/125759796?v=4", + "profile": "https://github.com/244Walyson", + "contributions": [ + "code" + ] + }, + { + "login": "Xcyq", + "name": "Xcyq", + "avatar_url": "https://avatars.githubusercontent.com/u/39086666?v=4", + "profile": "https://github.com/Xcyq", + "contributions": [ + "code" + ] + }, + { + "login": "Ritabrata1080", + "name": "Ritabrata", + "avatar_url": "https://avatars.githubusercontent.com/u/60834250?v=4", + "profile": "https://github.com/Ritabrata1080", + "contributions": [ + "review" + ] + }, + { + "login": "trivikr", + "name": "Trivikram Kamat", + "avatar_url": "https://avatars.githubusercontent.com/u/16024985?v=4", + "profile": "https://github.com/trivikr", + "contributions": [ + "code" + ] + }, + { + "login": "vvanghelle", + "name": "Vincent Vanghelle", + "avatar_url": "https://avatars.githubusercontent.com/u/3204600?v=4", + "profile": "https://github.com/vvanghelle", + "contributions": [ + "translation" + ] + }, + { + "login": "antoheri", + "name": "Antoine Héritier", + "avatar_url": "https://avatars.githubusercontent.com/u/79988396?v=4", + "profile": "https://github.com/antoheri", + "contributions": [ + "translation" + ] + }, + { + "login": "fishandsheep", + "name": "QinShower", + "avatar_url": "https://avatars.githubusercontent.com/u/43347407?v=4", + "profile": "https://github.com/fishandsheep", + "contributions": [ + "translation" + ] + }, + { + "login": "LakshyaPunyani-01", + "name": "LakshyaPunyani-01", + "avatar_url": "https://avatars.githubusercontent.com/u/103628913?v=4", + "profile": "https://github.com/LakshyaPunyani-01", + "contributions": [ + "code" + ] + }, + { + "login": "jasonjyu", + "name": "jasonjyu", + "avatar_url": "https://avatars.githubusercontent.com/u/10333076?v=4", + "profile": "https://github.com/jasonjyu", + "contributions": [ + "code" + ] + }, + { + "login": "jeffmorrison", + "name": "jeffmorrison", + "avatar_url": "https://avatars.githubusercontent.com/u/26047158?v=4", + "profile": "https://github.com/jeffmorrison", + "contributions": [ + "code" + ] + }, + { + "login": "dmgodoy", + "name": "David M.", + "avatar_url": "https://avatars.githubusercontent.com/u/10840681?v=4", + "profile": "https://github.com/dmgodoy", + "contributions": [ + "code" + ] + }, + { + "login": "apophizzz", + "name": "Patrick Kleindienst", + "avatar_url": "https://avatars.githubusercontent.com/u/12052783?v=4", + "profile": "https://github.com/apophizzz", + "contributions": [ + "code" + ] + }, + { + "login": "proceane", + "name": "Juyeon", + "avatar_url": "https://avatars.githubusercontent.com/u/62143949?v=4", + "profile": "https://github.com/proceane", + "contributions": [ + "translation" + ] + }, + { + "login": "mammadyahyayev", + "name": "Mammad Yahyayev", + "avatar_url": "https://avatars.githubusercontent.com/u/66476643?v=4", + "profile": "https://mammadyahya.vercel.app", + "contributions": [ + "doc" + ] + }, + { + "login": "SalmaAzeem", + "name": "Salma", + "avatar_url": "https://avatars.githubusercontent.com/u/121863224?v=4", + "profile": "https://github.com/SalmaAzeem", + "contributions": [ + "code" + ] + }, + { + "login": "CodeMaverick-143", + "name": "Arpit Sarang", + "avatar_url": "https://avatars.githubusercontent.com/u/182847716?v=4", + "profile": "https://codemaverick-143.github.io/My-Portfolio/", + "contributions": [ + "code" + ] + }, + { + "login": "mayatarek", + "name": "Maya", + "avatar_url": "https://avatars.githubusercontent.com/u/111644421?v=4", + "profile": "https://github.com/mayatarek", + "contributions": [ + "translation" + ] + }, + { + "login": "HabibaMekay", + "name": "HabibaMekay", + "avatar_url": "https://avatars.githubusercontent.com/u/133516736?v=4", + "profile": "https://github.com/HabibaMekay", + "contributions": [ + "code" + ] + }, + { + "login": "Ahmed-Taha-981", + "name": "Ahmed-Taha-981", + "avatar_url": "https://avatars.githubusercontent.com/u/122402269?v=4", + "profile": "https://github.com/Ahmed-Taha-981", + "contributions": [ + "code" + ] + }, + { + "login": "malak-elbanna", + "name": "Malak Elbanna", + "avatar_url": "https://avatars.githubusercontent.com/u/67643605?v=4", + "profile": "https://malakelbanna.netlify.app/", + "contributions": [ + "code" + ] + }, + { + "login": "depthlending", + "name": "BiKangNing", + "avatar_url": "https://avatars.githubusercontent.com/u/164312726?v=4", + "profile": "https://github.com/depthlending", + "contributions": [ + "doc" + ] + }, + { + "login": "TarunVishwakarma1", + "name": "Tarun Vishwakarma", + "avatar_url": "https://avatars.githubusercontent.com/u/138651451?v=4", + "profile": "https://github.com/TarunVishwakarma1", + "contributions": [ + "code" + ] + }, + { + "login": "shahdhoss", + "name": "Shahd Hossam", + "avatar_url": "https://avatars.githubusercontent.com/u/132148556?v=4", + "profile": "https://github.com/shahdhoss", + "contributions": [ + "code" + ] + }, + { + "login": "mehdirahimi", + "name": "Mehdi Rahimi", + "avatar_url": "https://avatars.githubusercontent.com/u/24210842?v=4", + "profile": "https://mehdirahimi.github.io", + "contributions": [ + "code" + ] + }, + { + "login": "clintaire", + "name": "Clint Airé", + "avatar_url": "https://avatars.githubusercontent.com/u/111376518?v=4", + "profile": "https://github.com/clintaire", + "contributions": [ + "code" + ] + }, + { + "login": "darkhyper24", + "name": "darkhyper24", + "avatar_url": "https://avatars.githubusercontent.com/u/132711528?v=4", + "profile": "https://github.com/darkhyper24", + "contributions": [ + "code" + ] + }, + { + "login": "MohanedAtef238", + "name": "Mohaned Atef", + "avatar_url": "https://avatars.githubusercontent.com/u/105852138?v=4", + "profile": "https://github.com/MohanedAtef238", + "contributions": [ + "code" + ] + }, + { + "login": "maximevtush", + "name": "Maxim Evtush", + "avatar_url": "https://avatars.githubusercontent.com/u/154841002?v=4", + "profile": "https://github.com/maximevtush", + "contributions": [ + "code" + ] + }, + { + "login": "hvgh88", + "name": "Harshita Vidapanakal", + "avatar_url": "https://avatars.githubusercontent.com/u/65297242?v=4", + "profile": "https://github.com/hvgh88", + "contributions": [ + "code" + ] + }, + { + "login": "smile-ab", + "name": "smile-ab", + "avatar_url": "https://avatars.githubusercontent.com/u/202159894?v=4", + "profile": "https://github.com/smile-ab", + "contributions": [ + "translation", + "code" + ] + }, + { + "login": "Francisco-G-P", + "name": "Francisco-G-P", + "avatar_url": "https://avatars.githubusercontent.com/u/186766789?v=4", + "profile": "https://github.com/Francisco-G-P", + "contributions": [ + "translation" + ] + }, + { + "login": "Duartegdm", + "name": "Gabriel Duarte", + "avatar_url": "https://avatars.githubusercontent.com/u/137895372?v=4", + "profile": "https://github.com/Duartegdm", + "contributions": [ + "doc" + ] + }, + { + "login": "DenizAltunkapan", + "name": "Deniz Altunkapan", + "avatar_url": "https://avatars.githubusercontent.com/u/93663085?v=4", + "profile": "https://github.com/DenizAltunkapan", + "contributions": [ + "translation" + ] + }, + { + "login": "johnklint81", + "name": "John Klint", + "avatar_url": "https://avatars.githubusercontent.com/u/70539458?v=4", + "profile": "https://github.com/johnklint81", + "contributions": [ + "code" + ] + }, + { + "login": "sanurah", + "name": "Sanura Hettiarachchi", + "avatar_url": "https://avatars.githubusercontent.com/u/16178588?v=4", + "profile": "https://github.com/sanurah", + "contributions": [ + "code" + ] + }, + { + "login": "2897robo", + "name": "Kim Gi Uk", + "avatar_url": "https://avatars.githubusercontent.com/u/31699375?v=4", + "profile": "https://github.com/2897robo", + "contributions": [ + "code" + ] + }, + { + "login": "Suchismita-Deb", + "name": "Suchismita Deb", + "avatar_url": "https://avatars.githubusercontent.com/u/68535074?v=4", + "profile": "https://github.com/Suchismita-Deb", + "contributions": [ + "code" + ] } ], - "contributorsPerLine": 7, + "contributorsPerLine": 6, "projectName": "java-design-patterns", "projectOwner": "iluwatar", "repoType": "github", diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..4afde59eb070 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,366 @@ +# +# This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). +# +# The MIT License +# Copyright © 2014-2022 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = off +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_line_length = off +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 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e0dcabf0b188..1f63bf5ff8b3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,18 +8,4 @@ updates: - package-ecosystem: "maven" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "weekly" - allow: - - dependency-name: "org.springframework.boot:spring-boot-dependencies" - - dependency-name: "commons-dbcp:commons-dbcp" - - dependency-name: "net.sourceforge.htmlunit:htmlunit" - - dependency-name: "com.google.code.gson:gson" - - dependency-name: "com.google.inject:guice" - - dependency-name: "com.github.stefanbirkner:system-lambda" - - dependency-name: "org.apache.maven.plugins:maven-surefire-plugin" - - dependency-name: "org.apache.maven.plugins:maven-assembly-plugin" - - dependency-name: "org.sonarsource.scanner.maven:sonar-maven-plugin" - - dependency-name: "org.apache.maven.plugins:maven-checkstyle-plugin" - - dependency-name: "com.mycila:license-maven-plugin" - - dependency-name: "org.jacoco:jacoco-maven-plugin" - - dependency-name: "com.iluwatar.urm:urm-maven-plugin" + interval: "weekly" \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 992d8cb25e94..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,61 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: false - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - "info: help wanted" - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: "status: stale" - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. The issue will be unassigned if no further activity occurs. Thank you - for your contributions. - -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -pulls: - daysUntilStale: 30 - daysUntilClose: 45 - markComment: > - This pull request has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. - closeComment: > - Closed due to inactivity. Thank you for your contributions. - -# issues: -# exemptLabels: -# - confirmed diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml index f270d297450e..5d2812e72fb7 100644 --- a/.github/workflows/maven-ci.yml +++ b/.github/workflows/maven-ci.yml @@ -1,32 +1,3 @@ -# -# The MIT License -# Copyright © 2014-2021 Ilkka Seppälä -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# - -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -# We are using two jobs here for testing our code on the latest JDK 11 build as well as a more satble build version of 11.0.3 -# You can see the full discussion here https://github.com/iluwatar/java-design-patterns/pull/1868#issue-1029459688 - name: Java CI on: @@ -37,46 +8,46 @@ jobs: build-and-analyze: - name: Build and Run Sonar analysis on JDK 17 - runs-on: ubuntu-20.04 + name: Build and Run Sonar analysis on JDK 21 + runs-on: ubuntu-22.04 steps: - - name: Checkout Code - uses: actions/checkout@v3 - with: - # Disabling shallow clone for improving relevancy of SonarQube reporting - fetch-depth: 0 - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - - name: Cache local Maven repository - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - # Cache Sonar packages which as used to run analysis and collect metrics - - name: Cache SonarCloud packages - uses: actions/cache@v3 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - # Some tests need screen access - - name: Install xvfb - run: sudo apt-get install -y xvfb - - - name: Build with Maven and run SonarQube analysis - run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar - env: - # These two env variables are needed for sonar analysis - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + - name: Checkout Code + uses: actions/checkout@v4 + with: + # Disabling shallow clone for improving relevancy of SonarQube reporting + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Cache Sonar packages which are used to run analysis and collect metrics + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + # Some tests need screen access + - name: Install xvfb + run: sudo apt-get install -y xvfb + + - name: Build with Maven and run SonarQube analysis + run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + env: + # These two env variables are needed for sonar analysis + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/maven-pr-builder.yml b/.github/workflows/maven-pr-builder.yml index be5eb344d874..99ee85152962 100644 --- a/.github/workflows/maven-pr-builder.yml +++ b/.github/workflows/maven-pr-builder.yml @@ -1,29 +1,3 @@ -# -# The MIT License -# Copyright © 2014-2021 Ilkka Seppälä -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# - -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - name: Java PR Builder on: @@ -37,47 +11,47 @@ permissions: jobs: build-and-analyze: - name: Build on JDK 17 - runs-on: ubuntu-20.04 + name: Build on JDK 21 + runs-on: ubuntu-22.04 steps: - - - name: Checkout Code - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - - name: Cache local Maven repository - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - # Cache Sonar packages which as used to run analysis and collect metrics - - name: Cache SonarCloud packages - uses: actions/cache@v3 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - # Some tests need screen access - - name: Install xvfb - run: sudo apt-get install -y xvfb - - name: Build with Maven and run SonarQube analysis - env: - # Intermediate variable - HEAD_REF: ${{ github.head_ref }} - # These two env variables are needed for sonar analysis - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=iluwatar -Dsonar.projectKey=iluwatar_java-design-patterns -Dsonar.pullrequest.branch=$HEAD_REF -Dsonar.pullrequest.base=${{ github.base_ref }} -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} + - name: Checkout Code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Cache Sonar packages which are used to run analysis and collect metrics + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + # Some tests need screen access + - name: Install xvfb + run: sudo apt-get install -y xvfb + + - name: Build with Maven and run SonarQube analysis + env: + # Intermediate variable + HEAD_REF: ${{ github.head_ref }} + # These two env variables are needed for sonar analysis + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=iluwatar -Dsonar.projectKey=iluwatar_java-design-patterns -Dsonar.pullrequest.branch=$HEAD_REF -Dsonar.pullrequest.base=${{ github.base_ref }} -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} \ No newline at end of file diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml new file mode 100644 index 000000000000..cac1250b70e8 --- /dev/null +++ b/.github/workflows/presubmit.yml @@ -0,0 +1,35 @@ +name: Presubmit.ai + +permissions: + contents: read + pull-requests: write + issues: write + +on: + pull_request_target: # Handle forked repository PRs in the base repository context + types: [opened, synchronize] + pull_request_review_comment: # Handle review comments + types: [created] + +jobs: + review: + runs-on: ubuntu-latest + steps: + - name: Check required secrets + run: | + if [ -z "${{ secrets.LLM_API_KEY }}" ]; then + echo "Error: LLM_API_KEY secret is not configured" + exit 1 + fi + + - name: Check out PR code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Run AI Reviewer + uses: presubmit/ai-reviewer@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + LLM_MODEL: "gemini-1.5-flash" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..48a4271e470b --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,20 @@ +name: 'Comment on stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: 'This issue is stale because it has been open 60 days with no activity.' + stale-pr-message: 'This PR is stale because it has been open 60 days with no activity.' + close-issue-message: 'This issue was closed because it has been stalled for too long with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for too long with no activity.' + days-before-issue-stale: 60 + days-before-pr-stale: 60 + days-before-issue-close: -1 + days-before-pr-close: -1 + exempt-issue-labels: 'info: help wanted' diff --git a/.gitignore b/.gitignore index 431eba612aae..166aa2ede438 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ build/ #################### Java Design Patterns ####### etc/Java Design Patterns.urm.puml serialized-entity/output.txt +fish1.out +fish2.out diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index cfc696f0122a..000000000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * The MIT License - * Copyright © 2014-2021 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 8c79a83ae43f..4f15c4db1572 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -5,14 +5,14 @@ # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# +# +# https://www.apache.org/licenses/LICENSE-2.0 +# # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD index 39087fbf12d3..8346d331ebfd 100644 --- a/CONTRIBUTING.MD +++ b/CONTRIBUTING.MD @@ -1,4 +1,3 @@ -This is great you have something to contribute! +The project guidelines can be found in [Java Design Patterns developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). -Before going any further please read the [wiki](https://github.com/iluwatar/java-design-patterns/wiki) -with conventions and rules we used for this project. +A good place to start is: https://github.com/iluwatar/java-design-patterns/wiki/01.-How-to-contribute diff --git a/LICENSE.md b/LICENSE.md index b239e798719e..bcae7daadee9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright 2014-2021 Ilkka Seppl +Copyright � 2014-2024 Ilkka Seppälä Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 097054fe54b3..bd5b40615e7f 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,15 +1,7 @@ +# Pull Request Template -Pull request title +## What does this PR do? -- Clearly and concisely describes what it does -- Refer to the issue that it solves, if available + - -Pull request description - -- Describes the main changes that come with the pull request -- Any relevant additional information is provided - - - -> For detailed contributing instructions see https://github.com/iluwatar/java-design-patterns/wiki/01.-How-to-contribute + diff --git a/README.md b/README.md index 28ccb332fcf5..881563902e26 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,4 @@ - - -# Design patterns implemented in Java +# Design Patterns Implemented in Java ![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) @@ -10,7 +6,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-332-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-383-orange.svg?style=flat-square)](#contributors-)
@@ -20,49 +16,38 @@ Read in different language : [**zh**](localization/zh/README.md), [**ko**](local # Introduction -Design patterns are the best, formalized practices a programmer can use to -solve common problems when designing an application or system. +Design patterns are the best, formalized practices a programmer can use to solve common problems when designing an application or system. -Design patterns can speed up the development process by providing tested, proven -development paradigms. +Design patterns can speed up the development process by providing tested, proven development paradigms. -Reusing design patterns helps prevent subtle issues that cause major -problems, and it also improves code readability for coders and architects who -are familiar with the patterns. +Reusing design patterns helps prevent subtle issues that cause major problems, and it also improves code readability for coders and architects who are familiar with the patterns. -# Getting started +# Getting Started -This site showcases Java Design Patterns. The solutions have been developed by -experienced programmers and architects from the open-source community. The -patterns can be browsed by their high-level descriptions or by looking at their -source code. The source code examples are well commented and can be thought of as -programming tutorials on how to implement a specific pattern. We use the most -popular battle-proven open-source Java technologies. +This site showcases Java Design Patterns. The solutions have been developed by experienced programmers and architects from the open-source community. The patterns can be browsed by their high-level descriptions or by looking at their +source code. The source code examples are well commented and can be thought of as programming tutorials on how to implement a specific pattern. We use the most popular battle-proven open-source Java technologies. -Before you dive into the material, you should be familiar with various -[Software Design Principles](https://java-design-patterns.com/principles/). +Before you dive into the material, you should be familiar with various [Software Design Principles](https://java-design-patterns.com/principles/). -All designs should be as simple as possible. You should start with KISS, YAGNI, -and Do The Simplest Thing That Could Possibly Work principles. Complexity and -patterns should only be introduced when they are needed for practical -extensibility. +All designs should be as simple as possible. You should start with KISS, YAGNI, and Do The Simplest Thing That Could Possibly Work principles. Complexity and patterns should only be introduced when they are needed for practical extensibility. -Once you are familiar with these concepts you can start drilling down into the -[available design patterns](https://java-design-patterns.com/patterns/) by any -of the following approaches +Once you are familiar with these concepts you can start drilling down into the [available design patterns](https://java-design-patterns.com/patterns/) by any of the following approaches: - Search for a specific pattern by name. Can't find one? Please report a new pattern [here](https://github.com/iluwatar/java-design-patterns/issues). - Using tags such as `Performance`, `Gang of Four` or `Data access`. - Using pattern categories, `Creational`, `Behavioral`, and others. -Hopefully, you find the object-oriented solutions presented on this site useful -in your architectures and have as much fun learning them as we had while developing them. +Hopefully, you find the object-oriented solutions presented on this site useful in your architectures and have as much fun learning them as we had while developing them. + +# How to Contribute + +If you are willing to contribute to the project you will find the relevant information in our [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). We will help you and answer your questions in the [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns). -# How to contribute +# The Book -If you are willing to contribute to the project you will find the relevant information in -our [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). We will help -you and answer your questions in the [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns). +The design patterns are now available as an e-book. Find out more about "Open Source Java Design Patterns" here: https://payhip.com/b/kcaF9 + +The project contributors can get the book for free. Contact the maintainer via [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns) or email (iluwatar (at) gmail (dot) com ). Send a message that contains your email address, Github username, and a link to an accepted pull request. # License @@ -76,432 +61,515 @@ This project is licensed under the terms of the MIT license. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Ilkka Seppälä
Ilkka Seppälä

📆 🚧 🖋
Subhrodip Mohanta
Subhrodip Mohanta

💻 👀 🚧
amit1307
amit1307

💻
Narendra Pathai
Narendra Pathai

💻 🤔 👀
Jeroen Meulemeester
Jeroen Meulemeester

💻
Joseph McCarthy
Joseph McCarthy

💻
Thomas
Thomas

💻
Anurag Agarwal
Anurag Agarwal

💻
Markus Moser
Markus Moser

🎨 💻 🤔
Sabiq Ihab
Sabiq Ihab

💻
Amit Dixit
Amit Dixit

💻
Piyush Kailash Chaudhari
Piyush Kailash Chaudhari

💻
joshzambales
joshzambales

💻
Kamil Pietruszka
Kamil Pietruszka

💻
Zafar Khaydarov
Zafar Khaydarov

💻 📖
Paul Campbell
Paul Campbell

💻
Argyro Sioziou
Argyro Sioziou

💻
TylerMcConville
TylerMcConville

💻
saksham93
saksham93

💻
nikhilbarar
nikhilbarar

💻
Colin But
Colin But

💻
Ruslan
Ruslan

💻
Juho Kang
Juho Kang

💻
Dheeraj Mummareddy
Dheeraj Mummareddy

💻
Bernardo Sulzbach
Bernardo Sulzbach

💻
Aleksandar Dudukovic
Aleksandar Dudukovic

💻
Yusuf Aytaş
Yusuf Aytaş

💻
Mihály Kuprivecz
Mihály Kuprivecz

💻
Stanislav Kapinus
Stanislav Kapinus

💻
GVSharma
GVSharma

💻
Srđan Paunović
Srđan Paunović

💻
Petros G. Sideris
Petros G. Sideris

💻
Pramod Gupta
Pramod Gupta

👀
Amarnath Chandana
Amarnath Chandana

💻
Anurag870
Anurag870

💻 📖
Wes Gilleland
Wes Gilleland

💻
Harshraj Thakor
Harshraj Thakor

💻
Martin Vandenbussche
Martin Vandenbussche

💻
Alexandru Somai
Alexandru Somai

💻
Artur Mogozov
Artur Mogozov

💻
anthony
anthony

💻
Christian Cygnus
Christian Cygnus

💻
Dima Gubin
Dima Gubin

💻
Joshua Jimenez
Joshua Jimenez

💻
Kai Winter
Kai Winter

💻
lbroman
lbroman

💻
Przemek
Przemek

💻
Prafful Agarwal
Prafful Agarwal

🖋
Sanket Panhale
Sanket Panhale

🖋
staillebois
staillebois

💻
Krisztián Nagy
Krisztián Nagy

💻
Alexander Ivanov
Alexander Ivanov

💻
Yosfik Alqadri
Yosfik Alqadri

💻
Agustí Becerra Milà
Agustí Becerra Milà

💻
Juan Manuel Suárez
Juan Manuel Suárez

💻
Luigi Cortese
Luigi Cortese

💻
Katarzyna Rzepecka
Katarzyna Rzepecka

💻
adamski.pro
adamski.pro

💻
Shengli Bai
Shengli Bai

💻
Boris
Boris

💻
Dmitry Avershin
Dmitry Avershin

💻
靳阳
靳阳

💻
hoangnam2261
hoangnam2261

💻
Arpit Jain
Arpit Jain

💻
Jón Ingi Sveinbjörnsson
Jón Ingi Sveinbjörnsson

💻
Kirill Vlasov
Kirill Vlasov

💻
Mitchell Irvin
Mitchell Irvin

💻
Ranjeet
Ranjeet

💻
PhoenixYip
PhoenixYip

💻
M Saif Asif
M Saif Asif

💻
kanwarpreet25
kanwarpreet25

💻
Leon Mak
Leon Mak

💻
Per Wramdemark
Per Wramdemark

💻
Evan Sia Wai Suan
Evan Sia Wai Suan

💻
AnaghaSasikumar
AnaghaSasikumar

💻
Christoffer Hamberg
Christoffer Hamberg

💻
Dominik Gruntz
Dominik Gruntz

💻
Hannes
Hannes

💻
Leo Gutiérrez Ramírez
Leo Gutiérrez Ramírez

💻
Zhang WH
Zhang WH

💻
Christopher O'Connell
Christopher O'Connell

💻
George Mavroeidis
George Mavroeidis

💻
Hemant Bothra
Hemant Bothra

💻 🎨
Kevin Peters
Kevin Peters

💻
George Aristy
George Aristy

💻
Mahendran Mookkiah
Mahendran Mookkiah

💻
Azureyjt
Azureyjt

💻
gans
gans

💻
Matt
Matt

🖋
Gopinath Langote
Gopinath Langote

💻
Hoswey
Hoswey

💻
Amit Pandey
Amit Pandey

💻
gwildor28
gwildor28

🖋
田浩
田浩

🖋
Stamatis Pitsios
Stamatis Pitsios

💻
qza
qza

💻
Rodolfo Forte
Rodolfo Forte

🖋
Ankur Kaushal
Ankur Kaushal

💻
Ovidijus Okinskas
Ovidijus Okinskas

💻
Robert Kasperczyk
Robert Kasperczyk

💻
Tapio Rautonen
Tapio Rautonen

💻
Yuri Orlov
Yuri Orlov

💻
Varun Upadhyay
Varun Upadhyay

💻
Aditya Pal
Aditya Pal

💻
grzesiekkedzior
grzesiekkedzior

💻 👀
Sivasubramani M
Sivasubramani M

💻
Sami Airaksinen
Sami Airaksinen

💻
Janne Sinivirta
Janne Sinivirta

💻
Boris-Chengbiao Zhou
Boris-Chengbiao Zhou

🖋
Jacob Hein
Jacob Hein

🖋
Richard Jones
Richard Jones

🖋
Rachel M. Carmena
Rachel M. Carmena

🖋
Zaerald Denze Lungos
Zaerald Denze Lungos

🖋
Lars Kappert
Lars Kappert

🖋
Mike Liu
Mike Liu

🌍
Matt Dolan
Matt Dolan

💻 👀
Manan
Manan

👀
Nishant Arora
Nishant Arora

💻
Peeyush
Peeyush

💻
Rakesh
Rakesh

💻 👀
Wei Seng
Wei Seng

💻
Ashish Trivedi
Ashish Trivedi

💻
洪月阳
洪月阳

💻
xdvrx1
xdvrx1

👀 🤔
Bethan Palmer
Bethan Palmer

💻
Toxic Dreamz
Toxic Dreamz

💻
Edy Cu Tjong
Edy Cu Tjong

📖
Michał Krzywański
Michał Krzywański

💻
Stefan Birkner
Stefan Birkner

💻
Fedor Skvorcov
Fedor Skvorcov

💻
samilAyoub
samilAyoub

💻
Vladislav Golubinov
Vladislav Golubinov

💻
Swaraj
Swaraj

💻
Christoph Flick
Christoph Flick

📖
Ascênio
Ascênio

👀
Domenico Sibilio
Domenico Sibilio

📖
Akash Chandwani
Akash Chandwani

👀
Pavlo Manannikov
Pavlo Manannikov

💻
Eiman
Eiman

💻
Rocky
Rocky

📖
Ibrahim ali abdelghany
Ibrahim ali abdelghany

👀
Girish Kulkarni
Girish Kulkarni

📖
Omar Karazoun
Omar Karazoun

💻
Jeff Evans
Jeff Evans

💻
Vivek Singh
Vivek Singh

💻
siavash
siavash

💻
ruchpeanuts
ruchpeanuts

📖
warp125
warp125

🌍
KHADIR Tayeb
KHADIR Tayeb

🌍
ignite1771
ignite1771

💻
Halil Demir
Halil Demir

🌍
Rohit Singh
Rohit Singh

💻
byoungju94
byoungju94

💻
Moustafa Farhat
Moustafa Farhat

🌍
Martel Richard
Martel Richard

💻
va1m
va1m

💻
Noam Greenshtain
Noam Greenshtain

💻
yonghong Xu
yonghong Xu

📖
jinishavora
jinishavora

👀 💻
Elvys Soares
Elvys Soares

💻
zWeBrain
zWeBrain

💻
余林颖
余林颖

🌍
Alain
Alain

🌍
VR
VR

📖
JackieNim
JackieNim

💻
EdisonE3
EdisonE3

💻
Tao
Tao

💻
Juan Manuel Abate
Juan Manuel Abate

🌍
Xenilo137
Xenilo137

💻
Samuel Souza
Samuel Souza

💻
Marlo Henrique
Marlo Henrique

🌍
AndriyPyzh
AndriyPyzh

💻
karthikbhat13
karthikbhat13

💻
Morteza Adigozalpour
Morteza Adigozalpour

💻
Nagaraj Tantri
Nagaraj Tantri

💻
Francesco Scuccimarri
Francesco Scuccimarri

💻
Conny Hansson
Conny Hansson

📖
Muklas Rahmanto
Muklas Rahmanto

🌍
Vadim
Vadim

🌍
Simran Keshri
Simran Keshri

💻
JCarlos
JCarlos

🌍
Ali Ghasemi
Ali Ghasemi

💻
Carl Dea
Carl Dea

💻
Mozartus
Mozartus

🌍
Manvi Goel
Manvi Goel

📖
Anum Amin
Anum Amin

📖
Reo Uehara
Reo Uehara

🌍
Fiordy
Fiordy

📖
Harshal
Harshal

💻
Abhinav Vashisth
Abhinav Vashisth

📖
Kevin
Kevin

👀 💻
Shrirang
Shrirang

👀 💻
interactwithankush
interactwithankush

💻
CharlieYu
CharlieYu

💻
Leisterbecker
Leisterbecker

💻
DragonDreamer
DragonDreamer

💻
ShivanshCharak
ShivanshCharak

💻
HattoriHenzo
HattoriHenzo

💻
Arnab Sen
Arnab Sen

💻
MohanaRao SV
MohanaRao SV

💻
Yonatan Karp-Rudin
Yonatan Karp-Rudin

💻 👀
Oliani
Oliani

💻
Renjie LIU
Renjie LIU

💻
perfect guy
perfect guy

📖
xyllq999
xyllq999

💻
Mohamed Bilal
Mohamed Bilal

📖
Karshil sheth
Karshil sheth

💻
kongleong86
kongleong86

💻
Aitor Fidalgo Sánchez
Aitor Fidalgo Sánchez

🌍 📖 👀
Victor He
Victor He

💻
Minh Nguyen
Minh Nguyen

🌍 📖
Victor He
Victor He

📖
yiichan
yiichan

📖
Pan Sem
Pan Sem

📖
zhoumengyks
zhoumengyks

💻
you
you

🌍
Thanks
Thanks

🌍
LazyProgrammer
LazyProgrammer

📖
Mohammed Faizan Ahmed
Mohammed Faizan Ahmed

📖
Bruno Fernandes
Bruno Fernandes

💻
SammanPali
SammanPali

📖
Qixiang Chen
Qixiang Chen

📖
Shourya Manekar
Shourya Manekar

🌍
Alan
Alan

🌍
JanFidor
JanFidor

💻 📖
Anton Yakutovich
Anton Yakutovich

💻
steph88ss
steph88ss

📖
Yujan Ranjitkar
Yujan Ranjitkar

🌍
yusha-g
yusha-g

🌍
Robert Volkmann
Robert Volkmann

💻 👀
Bipin Kumar Chaurasia
Bipin Kumar Chaurasia

📖
KyleSong30
KyleSong30

📖
u7281975
u7281975

📖
harshalkhachane
harshalkhachane

💻
Tejas Singh
Tejas Singh

📖 🌍
Sudarsan Balaji
Sudarsan Balaji

💻
Vaibhav Agrawal
Vaibhav Agrawal

📖
u7275858
u7275858

💻
prasad-333
prasad-333

📖
JurenXu
JurenXu

💻
murphShaw
murphShaw

📖
XianWu99
XianWu99

📖
JoshuaSinglaANU
JoshuaSinglaANU

💻
Ricardo Ramos
Ricardo Ramos

🌍
Farid Zouheir
Farid Zouheir

🌍
Vinícius A. B.
Vinícius A. B.

🌍
Stefanel Stan
Stefanel Stan

💻
Prince bhati
Prince bhati

🌍
WuLang
WuLang

📖
Hugo Kat
Hugo Kat

💻
Shivanagouda Agasimani
Shivanagouda Agasimani

💻
Aparna
Aparna

💻
Girolamo Giordano
Girolamo Giordano

🌍
Chak-C
Chak-C

💻
Nakul Nambiar
Nakul Nambiar

💻
KarmaTashiCat
KarmaTashiCat

🌍
marikattt
marikattt

💻
Hashvardhan Parmar
Hashvardhan Parmar

🌍
YongHwan
YongHwan

📖 🌍
Shogo Hida
Shogo Hida

🌍
Eugene
Eugene

💻
Piyush
Piyush

📖
Rahul Raj
Rahul Raj

💻
Bharath Kalyan S
Bharath Kalyan S

💻
Saiteja Reddy
Saiteja Reddy

🌍
Enrique Clerici
Enrique Clerici

🌍
Ramil Sayetov
Ramil Sayetov

🌍
东方未白
东方未白

💻
Fredrik Sejr
Fredrik Sejr

🌍
akshatarora0013
akshatarora0013

💻
Mughees Qasim
Mughees Qasim

💻
behappyleee
behappyleee

🌍
Ayush Thakur
Ayush Thakur

🌍
Anthony Bosch
Anthony Bosch

💻
trananso
trananso

📖
Giammaria Biffi
Giammaria Biffi

🌍
Saiful Haque
Saiful Haque

💻
JabezBrew
JabezBrew

💻
konstantin-goldman
konstantin-goldman

📖
Tien Nguyen Minh
Tien Nguyen Minh

💻 🌍
Vladimir
Vladimir

🌍
Surjendu
Surjendu

🌍
bakazhou
bakazhou

💻
Owen Leung
Owen Leung

💻
Stavros Barousis
Stavros Barousis

📖
Syyed Ibrahim Abdullah
Syyed Ibrahim Abdullah

🌍
JiaDi Zhang
JiaDi Zhang

🌍
Sanchit Bansal
Sanchit Bansal

📖
Md Saiful Islam
Md Saiful Islam

📖
Antonio Addeo
Antonio Addeo

📖 💻
Allagadda Sai Upendranath
Allagadda Sai Upendranath

📖
Matheus Braga
Matheus Braga

🌍 📖
Appari Satya Barghav
Appari Satya Barghav

📖
Marcel Ribeiro-Dantas
Marcel Ribeiro-Dantas

📖
Muhammad Hanif Amrullah
Muhammad Hanif Amrullah

🌍
JackH408
JackH408

📖
Shubham
Shubham

🌍
Nishant Jain
Nishant Jain

📖
Rhitam Chaudhury
Rhitam Chaudhury

📖
JerryZhao275
JerryZhao275

📖
Leonardo Lisanti
Leonardo Lisanti

🌍
Yennifer Herrera
Yennifer Herrera

🌍 👀
jnniu-n
jnniu-n

🌍 📖
Miguel Angel Perez Garcia
Miguel Angel Perez Garcia

👀 🌍
Suwan Sankaja
Suwan Sankaja

🌍
alok
alok

📖
Daniel Lisboa
Daniel Lisboa

🌍
Sam Powell
Sam Powell

📖
João Fernandes
João Fernandes

🌍
Hong Geon-ui
Hong Geon-ui

🌍
Doksanbir
Doksanbir

💻 📖 👀
Chant3ll3
Chant3ll3

📖 🌍
YongHwan Kwon
YongHwan Kwon

💻
Jakub Klimek
Jakub Klimek

💻
believe
believe

🌍
egg0102030405
egg0102030405

🌍 📖
Ved Asole
Ved Asole

💻
NewMorning
NewMorning

🌍
资深老萌新
资深老萌新

🌍
Seunghwan Jeon
Seunghwan Jeon

🌍
sugavanesh
sugavanesh

💻
FinnS-F
FinnS-F

💻
jerryyummy
jerryyummy

🌍
Manoj Chowdary
Manoj Chowdary

💻
Aditya
Aditya

📖
nooynayr
nooynayr

📖
CYBERCRUX2
CYBERCRUX2

📖
Luis Mateo Hincapié Martinez
Luis Mateo Hincapié Martinez

🌍
guqing
guqing

💻
Sashir Estela
Sashir Estela

💻
omahs
omahs

📖
leif e.
leif e.

💻
Ilkka Seppälä
Ilkka Seppälä

📆 🚧 🖋
Subhrodip Mohanta
Subhrodip Mohanta

💻 👀 🚧
amit1307
amit1307

💻
Narendra Pathai
Narendra Pathai

💻 🤔 👀
Jeroen Meulemeester
Jeroen Meulemeester

💻
Joseph McCarthy
Joseph McCarthy

💻
Thomas
Thomas

💻
Anurag Agarwal
Anurag Agarwal

💻
Markus Moser
Markus Moser

🎨 💻 🤔
Sabiq Ihab
Sabiq Ihab

💻
Amit Dixit
Amit Dixit

💻
Piyush Kailash Chaudhari
Piyush Kailash Chaudhari

💻
joshzambales
joshzambales

💻
Kamil Pietruszka
Kamil Pietruszka

💻
Zafar Khaydarov
Zafar Khaydarov

💻 📖
Paul Campbell
Paul Campbell

💻
Argyro Sioziou
Argyro Sioziou

💻
TylerMcConville
TylerMcConville

💻
saksham93
saksham93

💻
nikhilbarar
nikhilbarar

💻
Colin But
Colin But

💻
Ruslan
Ruslan

💻
Juho Kang
Juho Kang

💻
Dheeraj Mummareddy
Dheeraj Mummareddy

💻
Bernardo Sulzbach
Bernardo Sulzbach

💻
Aleksandar Dudukovic
Aleksandar Dudukovic

💻
Yusuf Aytaş
Yusuf Aytaş

💻
Mihály Kuprivecz
Mihály Kuprivecz

💻
Stanislav Kapinus
Stanislav Kapinus

💻
GVSharma
GVSharma

💻
Srđan Paunović
Srđan Paunović

💻
Petros G. Sideris
Petros G. Sideris

💻
Pramod Gupta
Pramod Gupta

👀
Amarnath Chandana
Amarnath Chandana

💻
Anurag870
Anurag870

💻 📖
Wes Gilleland
Wes Gilleland

💻
Harshraj Thakor
Harshraj Thakor

💻
Martin Vandenbussche
Martin Vandenbussche

💻
Alexandru Somai
Alexandru Somai

💻
Artur Mogozov
Artur Mogozov

💻
anthony
anthony

💻
Christian Cygnus
Christian Cygnus

💻
Dima Gubin
Dima Gubin

💻
Joshua Jimenez
Joshua Jimenez

💻
Kai Winter
Kai Winter

💻
lbroman
lbroman

💻
Przemek
Przemek

💻
Prafful Agarwal
Prafful Agarwal

🖋
Sanket Panhale
Sanket Panhale

🖋
staillebois
staillebois

💻
Krisztián Nagy
Krisztián Nagy

💻
Alexander Ivanov
Alexander Ivanov

💻
Yosfik Alqadri
Yosfik Alqadri

💻
Agustí Becerra Milà
Agustí Becerra Milà

💻
Juan Manuel Suárez
Juan Manuel Suárez

💻
Luigi Cortese
Luigi Cortese

💻
Katarzyna Rzepecka
Katarzyna Rzepecka

💻
adamski.pro
adamski.pro

💻
Shengli Bai
Shengli Bai

💻
Boris
Boris

💻
Dmitry Avershin
Dmitry Avershin

💻
靳阳
靳阳

💻
hoangnam2261
hoangnam2261

💻
Arpit Jain
Arpit Jain

💻
Jón Ingi Sveinbjörnsson
Jón Ingi Sveinbjörnsson

💻
Kirill Vlasov
Kirill Vlasov

💻
Mitchell Irvin
Mitchell Irvin

💻
Ranjeet
Ranjeet

💻
PhoenixYip
PhoenixYip

💻
M Saif Asif
M Saif Asif

💻
kanwarpreet25
kanwarpreet25

💻
Leon Mak
Leon Mak

💻
Per Wramdemark
Per Wramdemark

💻
Evan Sia Wai Suan
Evan Sia Wai Suan

💻
AnaghaSasikumar
AnaghaSasikumar

💻
Christoffer Hamberg
Christoffer Hamberg

💻
Dominik Gruntz
Dominik Gruntz

💻
Hannes
Hannes

💻
Leo Gutiérrez Ramírez
Leo Gutiérrez Ramírez

💻
Zhang WH
Zhang WH

💻
Christopher O'Connell
Christopher O'Connell

💻
George Mavroeidis
George Mavroeidis

💻
Hemant Bothra
Hemant Bothra

💻 🎨
Kevin Peters
Kevin Peters

💻
George Aristy
George Aristy

💻
Mahendran Mookkiah
Mahendran Mookkiah

💻
Azureyjt
Azureyjt

💻
gans
gans

💻
Matt
Matt

🖋
Gopinath Langote
Gopinath Langote

💻
Hoswey
Hoswey

💻
Amit Pandey
Amit Pandey

💻
gwildor28
gwildor28

🖋
田浩
田浩

🖋
Stamatis Pitsios
Stamatis Pitsios

💻
qza
qza

💻
Rodolfo Forte
Rodolfo Forte

🖋
Ankur Kaushal
Ankur Kaushal

💻
Ovidijus Okinskas
Ovidijus Okinskas

💻
Robert Kasperczyk
Robert Kasperczyk

💻
Tapio Rautonen
Tapio Rautonen

💻
Yuri Orlov
Yuri Orlov

💻
Varun Upadhyay
Varun Upadhyay

💻
Aditya Pal
Aditya Pal

💻
grzesiekkedzior
grzesiekkedzior

💻 👀
Sivasubramani M
Sivasubramani M

💻
Sami Airaksinen
Sami Airaksinen

💻
Janne Sinivirta
Janne Sinivirta

💻
Boris-Chengbiao Zhou
Boris-Chengbiao Zhou

🖋
Jacob Hein
Jacob Hein

🖋
Richard Jones
Richard Jones

🖋
Rachel M. Carmena
Rachel M. Carmena

🖋
Zaerald Denze Lungos
Zaerald Denze Lungos

🖋
Lars Kappert
Lars Kappert

🖋
Mike Liu
Mike Liu

🌍
Matt Dolan
Matt Dolan

💻 👀
Manan
Manan

👀
Nishant Arora
Nishant Arora

💻
Peeyush
Peeyush

💻
Rakesh
Rakesh

💻 👀
Wei Seng
Wei Seng

💻
Ashish Trivedi
Ashish Trivedi

💻
洪月阳
洪月阳

💻
xdvrx1
xdvrx1

👀 🤔
Bethan Palmer
Bethan Palmer

💻
Toxic Dreamz
Toxic Dreamz

💻
Edy Cu Tjong
Edy Cu Tjong

📖
Michał Krzywański
Michał Krzywański

💻
Stefan Birkner
Stefan Birkner

💻
Fedor Skvorcov
Fedor Skvorcov

💻
samilAyoub
samilAyoub

💻
Vladislav Golubinov
Vladislav Golubinov

💻
Swaraj
Swaraj

💻
Christoph Flick
Christoph Flick

📖
Ascênio
Ascênio

👀
Domenico Sibilio
Domenico Sibilio

📖
Akash Chandwani
Akash Chandwani

👀
Pavlo Manannikov
Pavlo Manannikov

💻
Eiman
Eiman

💻
Rocky
Rocky

📖
Ibrahim ali abdelghany
Ibrahim ali abdelghany

👀
Girish Kulkarni
Girish Kulkarni

📖
Omar Karazoun
Omar Karazoun

💻
Jeff Evans
Jeff Evans

💻
Vivek Singh
Vivek Singh

💻
siavash
siavash

💻
ruchpeanuts
ruchpeanuts

📖
warp125
warp125

🌍
KHADIR Tayeb
KHADIR Tayeb

🌍
ignite1771
ignite1771

💻
Halil Demir
Halil Demir

🌍
Rohit Singh
Rohit Singh

💻
byoungju94
byoungju94

💻
Moustafa Farhat
Moustafa Farhat

🌍
Martel Richard
Martel Richard

💻
va1m
va1m

💻
Noam Greenshtain
Noam Greenshtain

💻
yonghong Xu
yonghong Xu

📖
jinishavora
jinishavora

👀 💻
Elvys Soares
Elvys Soares

💻
zWeBrain
zWeBrain

💻
余林颖
余林颖

🌍
Alain
Alain

🌍
VR
VR

📖
JackieNim
JackieNim

💻
EdisonE3
EdisonE3

💻
Tao
Tao

💻
Juan Manuel Abate
Juan Manuel Abate

🌍
Xenilo137
Xenilo137

💻
Samuel Souza
Samuel Souza

💻 📖
Marlo Henrique
Marlo Henrique

🌍
AndriyPyzh
AndriyPyzh

💻
karthikbhat13
karthikbhat13

💻
Morteza Adigozalpour
Morteza Adigozalpour

💻
Nagaraj Tantri
Nagaraj Tantri

💻
Francesco Scuccimarri
Francesco Scuccimarri

💻
Conny Hansson
Conny Hansson

📖
Muklas Rahmanto
Muklas Rahmanto

🌍
Vadim
Vadim

🌍
Simran Keshri
Simran Keshri

💻
JCarlos
JCarlos

🌍
Ali Ghasemi
Ali Ghasemi

💻
Carl Dea
Carl Dea

💻
Mozartus
Mozartus

🌍
Manvi Goel
Manvi Goel

📖
Anum Amin
Anum Amin

📖
Reo Uehara
Reo Uehara

🌍
Fiordy
Fiordy

📖
Harshal
Harshal

💻
Abhinav Vashisth
Abhinav Vashisth

📖
Kevin
Kevin

👀 💻
Shrirang
Shrirang

👀 💻
interactwithankush
interactwithankush

💻
CharlieYu
CharlieYu

💻
Leisterbecker
Leisterbecker

💻
DragonDreamer
DragonDreamer

💻
ShivanshCharak
ShivanshCharak

💻
HattoriHenzo
HattoriHenzo

💻
Arnab Sen
Arnab Sen

💻
MohanaRao SV
MohanaRao SV

💻
Yonatan Karp-Rudin
Yonatan Karp-Rudin

💻 👀
Oliani
Oliani

💻
Renjie LIU
Renjie LIU

💻
perfect guy
perfect guy

📖
xyllq999
xyllq999

💻
Mohamed Bilal
Mohamed Bilal

📖
Karshil sheth
Karshil sheth

💻
kongleong86
kongleong86

💻
Aitor Fidalgo Sánchez
Aitor Fidalgo Sánchez

🌍 📖 👀
Victor He
Victor He

💻
Minh Nguyen
Minh Nguyen

🌍 📖
Victor He
Victor He

📖
yiichan
yiichan

📖
Pan Sem
Pan Sem

📖
zhoumengyks
zhoumengyks

💻
you
you

🌍
Thanks
Thanks

🌍
LazyProgrammer
LazyProgrammer

📖
Mohammed Faizan Ahmed
Mohammed Faizan Ahmed

📖
Bruno Fernandes
Bruno Fernandes

💻
SammanPali
SammanPali

📖
Qixiang Chen
Qixiang Chen

📖
Shourya Manekar
Shourya Manekar

🌍
Alan
Alan

🌍
JanFidor
JanFidor

💻 📖
Anton Yakutovich
Anton Yakutovich

💻
steph88ss
steph88ss

📖
Yujan Ranjitkar
Yujan Ranjitkar

🌍
yusha-g
yusha-g

🌍
Robert Volkmann
Robert Volkmann

💻 👀
Bipin Kumar Chaurasia
Bipin Kumar Chaurasia

📖
KyleSong30
KyleSong30

📖
u7281975
u7281975

📖
harshalkhachane
harshalkhachane

💻
Tejas Singh
Tejas Singh

📖 🌍
Sudarsan Balaji
Sudarsan Balaji

💻
Vaibhav Agrawal
Vaibhav Agrawal

📖
u7275858
u7275858

💻
prasad-333
prasad-333

📖
JurenXu
JurenXu

💻
murphShaw
murphShaw

📖
XianWu99
XianWu99

📖
JoshuaSinglaANU
JoshuaSinglaANU

💻
Ricardo Ramos
Ricardo Ramos

🌍
Farid Zouheir
Farid Zouheir

🌍
Vinícius A. B.
Vinícius A. B.

🌍
Stefanel Stan
Stefanel Stan

💻
Prince bhati
Prince bhati

🌍
WuLang
WuLang

📖
Hugo Kat
Hugo Kat

💻
Shivanagouda Agasimani
Shivanagouda Agasimani

💻
Aparna
Aparna

💻
Girolamo Giordano
Girolamo Giordano

🌍
Chak-C
Chak-C

💻
Nakul Nambiar
Nakul Nambiar

💻
KarmaTashiCat
KarmaTashiCat

🌍
marikattt
marikattt

💻
Hashvardhan Parmar
Hashvardhan Parmar

🌍
YongHwan
YongHwan

📖 🌍
Shogo Hida
Shogo Hida

🌍
Eugene
Eugene

💻
Piyush
Piyush

📖
Rahul Raj
Rahul Raj

💻
Bharath Kalyan S
Bharath Kalyan S

💻
Saiteja Reddy
Saiteja Reddy

🌍
Enrique Clerici
Enrique Clerici

🌍
Ramil Sayetov
Ramil Sayetov

🌍
东方未白
东方未白

💻
Fredrik Sejr
Fredrik Sejr

🌍
akshatarora0013
akshatarora0013

💻
Mughees Qasim
Mughees Qasim

💻
behappyleee
behappyleee

🌍
Ayush Thakur
Ayush Thakur

🌍
Anthony Bosch
Anthony Bosch

💻
trananso
trananso

📖
Giammaria Biffi
Giammaria Biffi

🌍
Saiful Haque
Saiful Haque

💻
JabezBrew
JabezBrew

💻
konstantin-goldman
konstantin-goldman

📖
Tien Nguyen Minh
Tien Nguyen Minh

💻 🌍
Vladimir
Vladimir

🌍
Surjendu
Surjendu

🌍 💻
bakazhou
bakazhou

💻
Owen Leung
Owen Leung

💻
Stavros Barousis
Stavros Barousis

📖
Syyed Ibrahim Abdullah
Syyed Ibrahim Abdullah

🌍
JiaDi Zhang
JiaDi Zhang

🌍
Sanchit Bansal
Sanchit Bansal

📖
Md Saiful Islam
Md Saiful Islam

📖
Antonio Addeo
Antonio Addeo

📖 💻
Allagadda Sai Upendranath
Allagadda Sai Upendranath

📖
Matheus Braga
Matheus Braga

🌍 📖
Appari Satya Barghav
Appari Satya Barghav

📖
Marcel Ribeiro-Dantas
Marcel Ribeiro-Dantas

📖
Muhammad Hanif Amrullah
Muhammad Hanif Amrullah

🌍
JackH408
JackH408

📖
Shubham
Shubham

🌍
Nishant Jain
Nishant Jain

📖
Rhitam Chaudhury
Rhitam Chaudhury

📖
JerryZhao275
JerryZhao275

📖
Leonardo Lisanti
Leonardo Lisanti

🌍
Yennifer Herrera
Yennifer Herrera

🌍 👀
jnniu-n
jnniu-n

🌍 📖
Miguel Angel Perez Garcia
Miguel Angel Perez Garcia

👀 🌍
Suwan Sankaja
Suwan Sankaja

🌍
alok
alok

📖
Daniel Lisboa
Daniel Lisboa

🌍
Sam Powell
Sam Powell

📖
João Fernandes
João Fernandes

🌍
Hong Geon-ui
Hong Geon-ui

🌍
Doksanbir
Doksanbir

💻 📖 👀
Chant3ll3
Chant3ll3

📖 🌍
YongHwan Kwon
YongHwan Kwon

💻
Jakub Klimek
Jakub Klimek

💻
believe
believe

🌍
egg0102030405
egg0102030405

🌍 📖
Ved Asole
Ved Asole

💻
NewMorning
NewMorning

🌍
资深老萌新
资深老萌新

🌍
Seunghwan Jeon
Seunghwan Jeon

🌍
sugavanesh
sugavanesh

💻
FinnS-F
FinnS-F

💻
jerryyummy
jerryyummy

🌍
Manoj Chowdary
Manoj Chowdary

💻
Aditya
Aditya

📖 💻
nooynayr
nooynayr

📖
CYBERCRUX2
CYBERCRUX2

📖
Luis Mateo Hincapié Martinez
Luis Mateo Hincapié Martinez

🌍 👀
guqing
guqing

💻
Sashir Estela
Sashir Estela

💻
omahs
omahs

📖
leif e.
leif e.

💻
Jun Kang
Jun Kang

💻
Kishalay Pandey
Kishalay Pandey

💻
drishtii7
drishtii7

💻
David Medina Orozco
David Medina Orozco

🌍 👀
Roman Leontev
Roman Leontev

💻
Riley
Riley

💻
k1w1dev
k1w1dev

💻
dev-yugantar
dev-yugantar

💻
Adelya
Adelya

💻
gatlanagaprasanna
gatlanagaprasanna

📖
Avinash Shukla
Avinash Shukla

💻
Mayank Choudhary
Mayank Choudhary

💻
romannimets
romannimets

💻
Joel
Joel

💻
Walyson Moises
Walyson Moises

💻
Xcyq
Xcyq

💻
Ritabrata
Ritabrata

👀
Trivikram Kamat
Trivikram Kamat

💻
Vincent Vanghelle
Vincent Vanghelle

🌍
Antoine Héritier
Antoine Héritier

🌍
QinShower
QinShower

🌍
LakshyaPunyani-01
LakshyaPunyani-01

💻
jasonjyu
jasonjyu

💻
jeffmorrison
jeffmorrison

💻
David M.
David M.

💻
Patrick Kleindienst
Patrick Kleindienst

💻
Juyeon
Juyeon

🌍
Mammad Yahyayev
Mammad Yahyayev

📖
Salma
Salma

💻
Arpit Sarang
Arpit Sarang

💻
Maya
Maya

🌍
HabibaMekay
HabibaMekay

💻
Ahmed-Taha-981
Ahmed-Taha-981

💻
Malak Elbanna
Malak Elbanna

💻
BiKangNing
BiKangNing

📖
Tarun Vishwakarma
Tarun Vishwakarma

💻
Shahd Hossam
Shahd Hossam

💻
Mehdi Rahimi
Mehdi Rahimi

💻
Clint Airé
Clint Airé

💻
darkhyper24
darkhyper24

💻
Mohaned Atef
Mohaned Atef

💻
Maxim Evtush
Maxim Evtush

💻
Harshita Vidapanakal
Harshita Vidapanakal

💻
smile-ab
smile-ab

🌍 💻
Francisco-G-P
Francisco-G-P

🌍
Gabriel Duarte
Gabriel Duarte

📖
Deniz Altunkapan
Deniz Altunkapan

🌍
John Klint
John Klint

💻
Sanura Hettiarachchi
Sanura Hettiarachchi

💻
Kim Gi Uk
Kim Gi Uk

💻
Suchismita Deb
Suchismita Deb

💻
diff --git a/abstract-document/README.md b/abstract-document/README.md index 7c30819c7f6b..56f8e775c676 100644 --- a/abstract-document/README.md +++ b/abstract-document/README.md @@ -1,24 +1,29 @@ --- -title: Abstract Document +title: "Abstract Document Pattern in Java: Simplifying Data Handling with Flexibility" +shortTitle: Abstract Document +description: "Explore the Abstract Document design pattern in Java. Learn its intent, explanation, applicability, benefits, and see real-world examples to implement flexible and dynamic data structures." category: Structural language: en -tag: - - Abstraction - - Extensibility - - Decoupling +tag: + - Abstraction + - Decoupling + - Dynamic typing + - Encapsulation + - Extensibility + - Polymorphism --- -## Intent +## Intent of Abstract Document Design Pattern -The Abstract Document design pattern is a structural design pattern that aims to provide a consistent way to handle hierarchical and tree-like data structures by defining a common interface for various document types. It separates the core document structure from specific data formats, enabling dynamic updates and simplified maintenance. +The Abstract Document design pattern in Java is a crucial structural design pattern that provides a consistent way to handle hierarchical and tree-like data structures by defining a common interface for various document types. It separates the core document structure from specific data formats, enabling dynamic updates and simplified maintenance. -## Explanation +## Detailed Explanation of Abstract Document Pattern with Real-World Examples -The Abstract Document pattern enables handling additional, non-static properties. This pattern uses concept of traits to enable type safety and separate properties of different classes into set of interfaces. +The Abstract Document design pattern in Java allows dynamic handling of non-static properties. This pattern uses concept of traits to enable type safety and separate properties of different classes into set of interfaces. -Real world example +Real-world example -> Consider a car that consists of multiple parts. However, we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible. +> Consider a library system implementing the Abstract Document design pattern in Java, where books can have diverse formats and attributes: physical books, eBooks, and audiobooks. Each format has unique properties, such as page count for physical books, file size for eBooks, and duration for audiobooks. The Abstract Document design pattern allows the library system to manage these diverse formats flexibly. By using this pattern, the system can store and retrieve properties dynamically, without needing a rigid structure for each book type, making it easier to add new formats or attributes in the future without significant changes to the codebase. In plain words @@ -28,86 +33,103 @@ Wikipedia says > An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the support of type-safety. The pattern makes use of traits to separate different properties of a class into different interfaces. -**Programmatic Example** +Class diagram + +![Abstract Document class diagram](./etc/abstract-document.png "Abstract Document class diagram") + + +## Programmatic Example of Abstract Document Pattern in Java + +Consider a car that consists of multiple parts. However, we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible. Let's first define the base classes `Document` and `AbstractDocument`. They basically make the object hold a property 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); - Stream children(String key, Function, T> constructor); + Stream children(String key, Function, T> constructor); } public abstract class AbstractDocument implements Document { - private final Map properties; - - protected AbstractDocument(Map 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 Object get(String key) { - return properties.get(key); - } - - @Override - public Stream children(String key, Function, T> constructor) { - return Stream.ofNullable(get(key)) - .filter(Objects::nonNull) - .map(el -> (List>) el) - .findAny() - .stream() - .flatMap(Collection::stream) - .map(constructor); - } - ... + private final Map properties; + + protected AbstractDocument(Map 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 Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + + // Other properties and methods... } ``` -Next we define an enum `Property` and a set of interfaces for type, price, model and parts. This allows us to create static looking interface to our `Car` class. + +Next we define an enum `Property` and a set of interfaces for type, price, model and parts. This allows us to create 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 getType() { - return Optional.ofNullable((String) get(Property.TYPE.toString())); - } + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } } public interface HasPrice extends Document { - default Optional getPrice() { - return Optional.ofNullable((Number) get(Property.PRICE.toString())); - } + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } } + public interface HasModel extends Document { - default Optional getModel() { - return Optional.ofNullable((String) get(Property.MODEL.toString())); - } + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } } public interface HasParts extends Document { - default Stream getParts() { - return children(Property.PARTS.toString(), Part::new); - } + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} + +public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { + + public Part(Map properties) { + super(properties); + } } ``` @@ -116,31 +138,32 @@ Now we are ready to introduce the `Car`. ```java public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { - public Car(Map properties) { - super(properties); - } + public Car(Map properties) { + super(properties); + } } ``` And finally here's how we construct and use the `Car` in a full example. ```java + public static void main(String[] args) { LOGGER.info("Constructing parts and car"); var wheelProperties = Map.of( - Property.TYPE.toString(), "wheel", - Property.MODEL.toString(), "15C", - Property.PRICE.toString(), 100L); + 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); + 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)); + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); var car = new Car(carProperties); @@ -149,27 +172,28 @@ And finally here's how we construct and use the `Car` in a full example. 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)) + p.getType().orElse(null), + p.getModel().orElse(null), + p.getPrice().orElse(null)) ); - - // Constructing parts and car - // Here is our car: - // model: 300SL - // price: 10000 - // parts: - // wheel/15C/100 - // door/Lambo/300 +} ``` -## Class diagram +The program output: -![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain") +``` +07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Constructing parts and car +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Here is our car: +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- -> model: 300SL +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> price: 10000 +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> parts: +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- wheel/15C/100 +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- door/Lambo/300 +``` -## Applicability +## When to Use the Abstract Document Pattern in Java -This pattern is particularly useful in scenarios where you have different types of documents that share some common attributes or behaviors, but also have unique attributes or behaviors specific to their individual types. Here are some scenarios where the Abstract Document design pattern can be applicable: +The Abstract Document design pattern is especially beneficial in scenarios requiring management of different document types in Java that share some common attributes or behaviors, but also have unique attributes or behaviors specific to their individual types. Here are some scenarios where the Abstract Document design pattern can be applicable: * Content Management Systems (CMS): In a CMS, you might have various types of content such as articles, images, videos, etc. Each type of content could have shared attributes like creation date, author, and tags, while also having specific attributes like image dimensions for images or video duration for videos. @@ -195,9 +219,9 @@ This pattern is particularly useful in scenarios where you have different types The key idea behind the Abstract Document design pattern is to provide a flexible and extensible way to manage different types of documents or entities with shared and distinct attributes. By defining a common interface and implementing it across various document types, you can achieve a more organized and consistent approach to handling complex data structures. -## Consequences +## Benefits and Trade-offs of Abstract Document Pattern -Benefits +Benefits: * Flexibility: Accommodates varied document structures and properties. @@ -207,14 +231,17 @@ Benefits * Reusability: Typed views enable code reuse for accessing specific attribute types. -Trade-offs +Trade-offs: * Complexity: Requires defining interfaces and views, adding implementation overhead. * Performance: Might introduce slight performance overhead compared to direct data access. -## Credits +## References and Credits -* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) -* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) * [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://amzn.to/49zRP4R) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Abstract Document Pattern (Wikipedia)](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) +* [Dealing with Properties (Martin Fowler)](http://martinfowler.com/apsupp/properties.pdf) diff --git a/abstract-document/pom.xml b/abstract-document/pom.xml index 2b7d58519edd..ef190e088d5e 100644 --- a/abstract-document/pom.xml +++ b/abstract-document/pom.xml @@ -34,6 +34,14 @@ abstract-document + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java index 665729008810..ab5ccada1414 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java @@ -31,48 +31,71 @@ import java.util.function.Function; import java.util.stream.Stream; -/** - * Abstract implementation of Document interface. - */ +/** Abstract implementation of Document interface. */ public abstract class AbstractDocument implements Document { - private final Map properties; + private final Map documentProperties; protected AbstractDocument(Map properties) { Objects.requireNonNull(properties, "properties map is required"); - this.properties = properties; + this.documentProperties = properties; } @Override public Void put(String key, Object value) { - properties.put(key, value); + documentProperties.put(key, value); return null; } @Override public Object get(String key) { - return properties.get(key); + return documentProperties.get(key); } @Override - public Stream children(String key, Function, T> constructor) { + public Stream children(String key, Function, T> childConstructor) { return Stream.ofNullable(get(key)) .filter(Objects::nonNull) .map(el -> (List>) el) .findAny() .stream() .flatMap(Collection::stream) - .map(constructor); + .map(childConstructor); } @Override public String toString() { + return buildStringRepresentation(); + } + + private String buildStringRepresentation() { var builder = new StringBuilder(); builder.append(getClass().getName()).append("["); - properties.forEach((key, value) -> builder.append("[").append(key).append(" : ").append(value) - .append("]")); + + // Explaining variable for document properties map + Map documentProperties = this.documentProperties; + + // Explaining variable for the size of document properties map + int numProperties = documentProperties.size(); + + // Explaining variable for tracking the current property index + int currentPropertyIndex = 0; + + // Iterate over document properties map + for (Map.Entry entry : documentProperties.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + // Append key-value pair + builder.append("[").append(key).append(" : ").append(value).append("]"); + + // Add comma if not last property + if (++currentPropertyIndex < numProperties) { + builder.append(", "); + } + } + builder.append("]"); return builder.toString(); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java index 6775523efff7..607b4a7f7913 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java @@ -49,20 +49,26 @@ public class App { public static void main(String[] args) { 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); @@ -70,10 +76,13 @@ public static void main(String[] args) { 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)) - ); + car.getParts() + .forEach( + p -> + LOGGER.info( + "\t{}/{}/{}", + p.getType().orElse(null), + p.getModel().orElse(null), + p.getPrice().orElse(null))); } } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java index 198a543b5c04..79a51b610337 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java @@ -28,15 +28,13 @@ import java.util.function.Function; import java.util.stream.Stream; -/** - * Document interface. - */ +/** Document interface. */ public interface Document { /** * Puts the value related to the key. * - * @param key element key + * @param key element key * @param value element value * @return Void */ @@ -53,7 +51,7 @@ public interface Document { /** * Gets the stream of child documents. * - * @param key element key + * @param key element key * @param constructor constructor of child class * @return child documents */ diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java index 6b7bbcf5246e..93fbbb9c1eae 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java @@ -27,13 +27,10 @@ import com.iluwatar.abstractdocument.AbstractDocument; import java.util.Map; -/** - * Car entity. - */ +/** Car entity. */ public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { public Car(Map properties) { super(properties); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java index 008f5a257bb6..6f517588e5a0 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java @@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.domain.enums.Property; import java.util.Optional; -/** - * HasModel trait for static access to 'model' property. - */ +/** HasModel trait for static access to 'model' property. */ public interface HasModel extends Document { default Optional getModel() { return Optional.ofNullable((String) get(Property.MODEL.toString())); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java index 5dee245d1560..8bffa753e6ef 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java @@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.domain.enums.Property; import java.util.stream.Stream; -/** - * HasParts trait for static access to 'parts' property. - */ +/** HasParts trait for static access to 'parts' property. */ public interface HasParts extends Document { default Stream getParts() { return children(Property.PARTS.toString(), Part::new); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java index db985dfba333..ce876e5faf54 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java @@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.domain.enums.Property; import java.util.Optional; -/** - * HasPrice trait for static access to 'price' property. - */ +/** HasPrice trait for static access to 'price' property. */ public interface HasPrice extends Document { default Optional getPrice() { return Optional.ofNullable((Number) get(Property.PRICE.toString())); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java index bd83adecb058..5e0f49df7b7b 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java @@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.domain.enums.Property; import java.util.Optional; -/** - * HasType trait for static access to 'type' property. - */ +/** HasType trait for static access to 'type' property. */ public interface HasType extends Document { default Optional getType() { return Optional.ofNullable((String) get(Property.TYPE.toString())); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java index 9aa46be15e41..6eec08b0d2a4 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java @@ -27,13 +27,10 @@ import com.iluwatar.abstractdocument.AbstractDocument; import java.util.Map; -/** - * Part entity. - */ +/** Part entity. */ public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { public Part(Map properties) { super(properties); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java index 3aa97ba84f65..3e0d6d10ab1f 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java @@ -24,10 +24,10 @@ */ package com.iluwatar.abstractdocument.domain.enums; -/** - * Enum To Describe Property type. - */ +/** Enum To Describe Property type. */ public enum Property { - - PARTS, TYPE, PRICE, MODEL + PARTS, + TYPE, + PRICE, + MODEL } diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java index 249f01f64a1e..a098517c3a69 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java @@ -24,16 +24,14 @@ */ package com.iluwatar.abstractdocument; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * AbstractDocument test class - */ +/** AbstractDocument test class */ class AbstractDocumentTest { private static final String KEY = "key"; @@ -80,4 +78,53 @@ void shouldIncludePropsInToString() { assertTrue(document.toString().contains(VALUE)); } + @Test + void shouldHandleExceptionDuringConstruction() { + Map invalidProperties = + null; // Invalid properties, causing NullPointerException + + // Throw null pointer exception + assertThrows( + NullPointerException.class, + () -> { + // Attempt to construct a document with invalid properties + new DocumentImplementation(invalidProperties); + }); + } + + @Test + void shouldPutAndGetNestedDocument() { + // Creating a nested document + DocumentImplementation nestedDocument = new DocumentImplementation(new HashMap<>()); + nestedDocument.put("nestedKey", "nestedValue"); + + document.put("nested", nestedDocument); + + // Retrieving the nested document + DocumentImplementation retrievedNestedDocument = + (DocumentImplementation) document.get("nested"); + + assertNotNull(retrievedNestedDocument); + assertEquals("nestedValue", retrievedNestedDocument.get("nestedKey")); + } + + @Test + void shouldUpdateExistingValue() { + // Arrange + final String key = "key"; + final String originalValue = "originalValue"; + final String updatedValue = "updatedValue"; + + // Initializing the value + document.put(key, originalValue); + + // Verifying that the initial value is retrieved correctly + assertEquals(originalValue, document.get(key)); + + // Updating the value + document.put(key, updatedValue); + + // Verifying that the updated value is retrieved correctly + assertEquals(updatedValue, document.get(key)); + } } diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java index 09af6d7b5260..16dcba0db37f 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java @@ -24,25 +24,21 @@ */ package com.iluwatar.abstractdocument; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Simple App test - */ +import org.junit.jupiter.api.Test; + +/** Simple App test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteAppWithoutException() { assertDoesNotThrow(() -> App.main(null)); } - } diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java index 4b52fa7a6ac4..fc29dea45c43 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java @@ -24,18 +24,16 @@ */ package com.iluwatar.abstractdocument; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.abstractdocument.domain.Car; import com.iluwatar.abstractdocument.domain.Part; import com.iluwatar.abstractdocument.domain.enums.Property; -import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Test for Part and Car - */ +/** Test for Part and Car */ class DomainTest { private static final String TEST_PART_TYPE = "test-part-type"; @@ -47,11 +45,11 @@ class DomainTest { @Test void shouldConstructPart() { - var partProperties = Map.of( - Property.TYPE.toString(), TEST_PART_TYPE, - Property.MODEL.toString(), TEST_PART_MODEL, - Property.PRICE.toString(), (Object) TEST_PART_PRICE - ); + var partProperties = + Map.of( + Property.TYPE.toString(), TEST_PART_TYPE, + Property.MODEL.toString(), TEST_PART_MODEL, + Property.PRICE.toString(), (Object) TEST_PART_PRICE); var part = new Part(partProperties); assertEquals(TEST_PART_TYPE, part.getType().orElseThrow()); assertEquals(TEST_PART_MODEL, part.getModel().orElseThrow()); @@ -60,15 +58,14 @@ void shouldConstructPart() { @Test void shouldConstructCar() { - var carProperties = Map.of( - Property.MODEL.toString(), TEST_CAR_MODEL, - Property.PRICE.toString(), TEST_CAR_PRICE, - Property.PARTS.toString(), List.of(Map.of(), Map.of()) - ); + var carProperties = + Map.of( + Property.MODEL.toString(), TEST_CAR_MODEL, + Property.PRICE.toString(), TEST_CAR_PRICE, + Property.PARTS.toString(), List.of(Map.of(), Map.of())); var car = new Car(carProperties); assertEquals(TEST_CAR_MODEL, car.getModel().orElseThrow()); assertEquals(TEST_CAR_PRICE, car.getPrice().orElseThrow()); assertEquals(2, car.getParts().count()); } - } diff --git a/abstract-factory/README.md b/abstract-factory/README.md index 4d5ff98a2165..ee9823a39a5f 100644 --- a/abstract-factory/README.md +++ b/abstract-factory/README.md @@ -1,26 +1,32 @@ --- -title: Abstract Factory +title: "Abstract Factory Pattern in Java: Mastering Object Creation Elegantly" +shortTitle: Abstract Factory +description: "Learn the Abstract Factory pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge." category: Creational language: en tag: - - Abstraction - - Decoupling - - Gang of Four + - Abstraction + - Decoupling + - Gang of Four + - Instantiation + - Polymorphism --- ## Also known as -Kit +* Kit -## Intent +## Intent of Abstract Factory Design Pattern -The Abstract Factory design pattern provides a way to create families of related objects without specifying their concrete classes. This allows for code that is independent of the specific classes of objects it uses, promoting flexibility and maintainability. +The Abstract Factory pattern in Java provides an interface for creating families of related or dependent objects without specifying their concrete classes, enhancing modularity and flexibility in software design. -## Explanation +## Detailed Explanation of Abstract Factory Pattern with Real-World Examples Real-world example -> To create a kingdom we need objects with a common theme. The elven kingdom needs an elven king, elven castle, and elven army whereas the orcish kingdom needs an orcish king, orcish castle, and orcish army. There is a dependency between the objects in the kingdom. +> Imagine a furniture company that uses the Abstract Factory pattern in Java to produce various styles of furniture: modern, Victorian, and rustic. Each style includes products like chairs, tables, and sofas. To ensure consistency within each style, the company uses an Abstract Factory pattern. +> +> In this scenario, the Abstract Factory is an interface for creating families of related furniture objects (chairs, tables, sofas). Each concrete factory (ModernFurnitureFactory, VictorianFurnitureFactory, RusticFurnitureFactory) implements the Abstract Factory interface and creates a set of products that match the specific style. This way, clients can create a whole set of modern or Victorian furniture without worrying about the details of their instantiation. This maintains a consistent style and allows easy swapping of one style of furniture for another. In plain words @@ -30,115 +36,90 @@ Wikipedia says > The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes -**Programmatic Example** +Class diagram + +![Abstract Factory class diagram](./etc/abstract-factory.urm.png "Abstract Factory class diagram") + +## Programmatic Example of Abstract Factory in Java + +To create a kingdom using the Abstract Factory pattern in Java, we need objects with a common theme. The elven kingdom needs an elven king, elven castle, and elven army whereas the orcish kingdom needs an orcish king, orcish castle, and orcish army. There is a dependency between the objects in the kingdom. Translating the kingdom example above. First of all, we have some interfaces and implementation for the objects in the 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!"; - @Override - public String getDescription() { - return DESCRIPTION; - } + static final String DESCRIPTION = "This is the elven castle!"; + + @Override + public String getDescription() { + return DESCRIPTION; + } } + public class ElfKing implements King { - static final String DESCRIPTION = "This is the elven king!"; - @Override - public String getDescription() { - return DESCRIPTION; - } + static final String DESCRIPTION = "This is the elven king!"; + + @Override + public String getDescription() { + return DESCRIPTION; + } } + public class ElfArmy implements Army { - static final String DESCRIPTION = "This is the elven Army!"; - @Override - public String getDescription() { - return DESCRIPTION; - } + static final String DESCRIPTION = "This is the elven Army!"; + + @Override + public String getDescription() { + return DESCRIPTION; + } } // Orcish implementations similarly -> ... - ``` Then we have the abstraction and implementations for the kingdom factory. ```java public interface KingdomFactory { - Castle createCastle(); - King createKing(); - Army createArmy(); -} + Castle createCastle(); -public class ElfKingdomFactory implements KingdomFactory { + King createKing(); - @Override - public Castle createCastle() { - return new ElfCastle(); - } - - @Override - public King createKing() { - return new ElfKing(); - } - - @Override - public Army createArmy() { - return new ElfArmy(); - } + Army createArmy(); } -public class OrcKingdomFactory implements KingdomFactory { - - @Override - public Castle createCastle() { - return new OrcCastle(); - } - - @Override - public King createKing() { - return new OrcKing(); - } - - @Override - public Army createArmy() { - return new OrcArmy(); - } -} -``` +public class ElfKingdomFactory implements KingdomFactory { -Now we have the abstract factory that lets us make a family of related objects i.e. elven kingdom factory creates elven castle, king and army, etc. + @Override + public Castle createCastle() { + return new ElfCastle(); + } -```java -var factory = new ElfKingdomFactory(); -var castle = factory.createCastle(); -var king = factory.createKing(); -var army = factory.createArmy(); - -castle.getDescription(); -king.getDescription(); -army.getDescription(); -``` + @Override + public King createKing() { + return new ElfKing(); + } -Program output: + @Override + public Army createArmy() { + return new ElfArmy(); + } +} -```java -This is the elven castle! -This is the elven king! -This is the elven Army! +// Orcish implementations similarly -> ... ``` Now, we can design a factory for our different kingdom factories. In this example, we created `FactoryMaker`, responsible for returning an instance of either `ElfKingdomFactory` or `OrcKingdomFactory`. The client can use `FactoryMaker` to create the desired concrete factory which, in turn, will produce different concrete objects (derived from `Army`, `King`, `Castle`). In this example, we also used an enum to parameterize which type of kingdom factory the client will ask for. @@ -157,51 +138,58 @@ public static class FactoryMaker { }; } } +``` - 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()); +Here is the main function of our example application: - LOGGER.info("Orc Kingdom"); - app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC)); - --similar use of the orc factory - } +```java +LOGGER.info("elf kingdom"); +createKingdom(Kingdom.FactoryMaker.KingdomType.ELF); +LOGGER.info(kingdom.getArmy().getDescription()); +LOGGER.info(kingdom.getCastle().getDescription()); +LOGGER.info(kingdom.getKing().getDescription()); + +LOGGER.info("orc kingdom"); +createKingdom(Kingdom.FactoryMaker.KingdomType.ORC); +LOGGER.info(kingdom.getArmy().getDescription()); +LOGGER.info(kingdom.getCastle().getDescription()); +LOGGER.info(kingdom.getKing().getDescription()); ``` -## Class diagram - -![alt text](./etc/abstract-factory.urm.png "Abstract Factory class diagram") +The program output: +``` +07:35:46.340 [main] INFO com.iluwatar.abstractfactory.App -- elf kingdom +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven army! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven castle! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven king! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- orc kingdom +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc army! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc castle! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc king! +``` -## Applicability +## When to Use the Abstract Factory Pattern in Java -Use the Abstract Factory pattern when +Use the Abstract Factory pattern in Java when: -* The system should be independent of how its products are created, composed, and represented -* The system should be configured with one of the multiple families of products -* The family of related product objects is designed to be used together, and you need to enforce this constraint -* You want to provide a class library of products, and you want to reveal just their interfaces, not their implementations -* The lifetime of the dependency is conceptually shorter than the lifetime of the consumer. -* You need a run-time value to construct a particular dependency -* You want to decide which product to call from a family at runtime. -* You need to supply one or more parameters only known at run-time before you can resolve a dependency. -* When you need consistency among products -* You don’t want to change existing code when adding new products or families of products to the program. +* The system should be independent of how its products are created, composed, and represented. +* You need to configure the system with one of multiple families of products. +* A family of related product objects must be used together, enforcing consistency. +* You want to provide a class library of products, exposing only their interfaces, not their implementations. +* The lifetime of dependencies is shorter than the consumer's lifetime. +* Dependencies need to be constructed using runtime values or parameters. +* You need to choose which product to use from a family at runtime. +* Adding new products or families should not require changes to existing code. -Example use cases +## Abstract Factory Pattern Java Tutorials -* Selecting to call to the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime. -* Unit test case writing becomes much easier -* UI tools for different OS +* [Abstract Factory Design Pattern in Java (DigitalOcean)](https://www.digitalocean.com/community/tutorials/abstract-factory-design-pattern-in-java) +* [Abstract Factory(Refactoring Guru)](https://refactoring.guru/design-patterns/abstract-factory) -## Consequences +## Benefits and Trade-offs of Abstract Factory Pattern -Benefits +Benefits: * Flexibility: Easily switch between product families without code modifications. @@ -211,31 +199,29 @@ Benefits * Maintainability: Changes to individual product families are localized, simplifying updates. -Trade-offs +Trade-offs: * Complexity: Defining abstract interfaces and concrete factories adds initial overhead. * Indirectness: Client code interacts with products indirectly through factories, potentially reducing transparency. -## Tutorials - -* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java) -* [Refactoring Guru - Abstract Factory](https://refactoring.guru/design-patterns/abstract-factory) - -## Known uses +## Real-World Applications of Abstract Factory Pattern in Java +* Java Swing's `LookAndFeel` classes for providing different look-and-feel options. +* Various implementations in the Java Abstract Window Toolkit (AWT) for creating different GUI components. * [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) * [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) * [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) -## Related patterns +## Related Java Design Patterns -* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) -* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Abstract Factory uses Factory Methods to create products. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Abstract Factory classes are often implemented as Singletons. +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): Similar to Abstract Factory but focuses on configuring and managing a set of related objects in a flexible way. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Design Patterns in Java](https://amzn.to/3Syw0vC) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) * [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U) -* [Design Patterns in Java](https://amzn.to/3Syw0vC) \ No newline at end of file diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index 99a92423beae..60fbf72eff54 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -34,6 +34,14 @@ abstract-factory + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java index e360822ca147..798cbe4fd118 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java @@ -74,6 +74,7 @@ public void run() { /** * Creates kingdom. + * * @param kingdomType type of Kingdom */ public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) { @@ -82,4 +83,4 @@ public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) { kingdom.setCastle(kingdomFactory.createCastle()); kingdom.setArmy(kingdomFactory.createArmy()); } -} \ No newline at end of file +} diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java index 3efec4c87eb1..78c75323f1c0 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * Army interface. - */ +/** Army interface. */ public interface Army { String getDescription(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java index 8fca068199cb..ee1e16f3cd3f 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * Castle interface. - */ +/** Castle interface. */ public interface Castle { String getDescription(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java index 055d2cc75b0c..d7e46c1456f0 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * ElfArmy. - */ +/** ElfArmy. */ public class ElfArmy implements Army { static final String DESCRIPTION = "This is the elven army!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java index 5b0c26c4290d..136afb11fd23 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * ElfCastle. - */ +/** ElfCastle. */ public class ElfCastle implements Castle { static final String DESCRIPTION = "This is the elven castle!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java index 0696e1d096f9..9b0d3a6f1a77 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * ElfKing. - */ +/** ElfKing. */ public class ElfKing implements King { static final String DESCRIPTION = "This is the elven king!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java index f45d2ee036c1..b09a2f47c252 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * ElfKingdomFactory concrete factory. - */ +/** ElfKingdomFactory concrete factory. */ public class ElfKingdomFactory implements KingdomFactory { @Override @@ -43,5 +41,4 @@ public King createKing() { public Army createArmy() { return new ElfArmy(); } - } diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java index 01af71a6a293..9f65ed434577 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * King interface. - */ +/** King interface. */ public interface King { String getDescription(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java index db1c65ca46de..d1f85a6a4812 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Helper class to manufacture {@link KingdomFactory} beans. - */ +/** Helper class to manufacture {@link KingdomFactory} beans. */ @Getter @Setter public class Kingdom { @@ -38,21 +36,16 @@ public class Kingdom { private Castle castle; private Army army; - /** - * The factory of kingdom factories. - */ + /** The factory of kingdom factories. */ public static class FactoryMaker { - /** - * Enumeration for the different types of Kingdoms. - */ + /** Enumeration for the different types of Kingdoms. */ public enum KingdomType { - ELF, ORC + ELF, + ORC } - /** - * The factory method to create KingdomFactory concrete objects. - */ + /** The factory method to create KingdomFactory concrete objects. */ public static KingdomFactory makeFactory(KingdomType type) { return switch (type) { case ELF -> new ElfKingdomFactory(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java index fdfe19dc08d2..199c6697dcaa 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * KingdomFactory factory interface. - */ +/** KingdomFactory factory interface. */ public interface KingdomFactory { Castle createCastle(); @@ -34,5 +32,4 @@ public interface KingdomFactory { King createKing(); Army createArmy(); - } diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java index d687c32e8c27..31ed6896d921 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcArmy. - */ +/** OrcArmy. */ public class OrcArmy implements Army { static final String DESCRIPTION = "This is the orc army!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java index f842bb3c62d0..bdae5709a97c 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcCastle. - */ +/** OrcCastle. */ public class OrcCastle implements Castle { static final String DESCRIPTION = "This is the orc castle!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java index e25d93bfba40..7f106d45a01d 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcKing. - */ +/** OrcKing. */ public class OrcKing implements King { static final String DESCRIPTION = "This is the orc king!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java index c80728a87fdc..82d258570460 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcKingdomFactory concrete factory. - */ +/** OrcKingdomFactory concrete factory. */ public class OrcKingdomFactory implements KingdomFactory { @Override diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java index 0f7708e07a12..b5dde940c464 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java @@ -24,14 +24,12 @@ */ package com.iluwatar.abstractfactory; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Tests for abstract factory. - */ +import org.junit.jupiter.api.Test; + +/** Tests for abstract factory. */ class AbstractFactoryTest { private final App app = new App(); diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java index 736a7f8b740f..9f53691a594d 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java @@ -24,18 +24,16 @@ */ package com.iluwatar.abstractfactory; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Check whether the execution of the main method in {@link App} throws an exception. - */ +import org.junit.jupiter.api.Test; + +/** Check whether the execution of the main method in {@link App} throws an exception. */ class AppTest { - + @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/active-object/README.md b/active-object/README.md index 26e3e816af7f..a804ce9e632b 100644 --- a/active-object/README.md +++ b/active-object/README.md @@ -1,163 +1,222 @@ --- -title: Active Object +title: "Active Object Pattern in Java: Achieving Efficient Asynchronous Processing" +shortTitle: Active Object +description: "Learn about the Active Object design pattern in Java. This guide covers asynchronous behavior, concurrency, and practical examples to enhance your Java applications' performance." category: Concurrency language: en tag: - - Performance + - Asynchronous + - Decoupling + - Messaging + - Synchronization + - Thread management --- +## Intent of Active Object Design Pattern -## Intent +The Active Object pattern provides a reliable method for asynchronous processing in Java, ensuring responsive applications and efficient thread management. It achieves this by encapsulating tasks within objects that have their own thread and message queue. This separation keeps the main thread responsive and avoids issues like direct thread manipulation or shared state access. -The Active Object design pattern provides a safe and reliable way to implement asynchronous behavior in concurrent systems. It achieves this by encapsulating tasks within objects that have their own thread and message queue. This separation keeps the main thread responsive and avoids issues like direct thread manipulation or shared state access. +## Detailed Explanation of Active Object Pattern with Real-World Examples -## Explanation +Real-world example -The class that implements the active object pattern will contain a self-synchronization mechanism without using 'synchronized' methods. +> Imagine a busy restaurant where customers place orders with waiters. Instead of the waiters going to the kitchen to prepare the food themselves, they write the orders on slips and hand them to a dispatcher. The dispatcher manages a pool of chefs who prepare the meals asynchronously. Once a chef is free, they pick up the next order from the queue, prepare the dish, and notify the waiter when it's ready for serving. +> +> In this analogy, the waiters represent the client threads, the dispatcher represents the scheduler, and the chefs represent the method execution in separate threads. This setup allows the waiters to continue taking orders without being blocked by the food preparation process, much like the Active Object pattern decouples method invocation from execution to enhance concurrency. -Real-world example +In plain words + +> The Active Object pattern decouples method execution from method invocation to improve concurrency and responsiveness in multithreaded applications. + +Wikipedia says + +> The active object design pattern decouples method execution from method invocation for objects that each reside in their own thread of control.[1] The goal is to introduce concurrency, by using asynchronous method invocation and a scheduler for handling requests. +> +> The pattern consists of six elements: +> +> * A proxy, which provides an interface towards clients with publicly accessible methods. +> * An interface which defines the method request on an active object. +> * A list of pending requests from clients. +> * A scheduler, which decides which request to execute next. +> * The implementation of the active object method. +> * A callback or variable for the client to receive the result. ->The Orcs are known for their wildness and untameable soul. It seems like they have their own thread of control based on previous behavior. +Sequence diagram -To implement a creature that has its own thread of control mechanism and expose its API only and not the execution itself, we can use the Active Object pattern. +![Active Object sequence diagram](./etc/active-object-sequence-diagram.png) -**Programmatic Example** +## Programmatic Example of Active Object in Java + +This section explains how the Active Object design pattern works in Java, highlighting its use in asynchronous task management and concurrency control. + +The Orcs are known for their wildness and untameable soul. It seems like they have their own thread of control based on previous behavior. To implement a creature that has its own thread of control mechanism and expose its API only and not the execution itself, we can use the Active Object pattern. ```java -public abstract class ActiveCreature{ - private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); - - private BlockingQueue requests; - - private String name; - - private Thread thread; - - public ActiveCreature(String name) { - this.name = name; - this.requests = new LinkedBlockingQueue(); - thread = new Thread(new Runnable() { - @Override - public void run() { - while (true) { - try { - requests.take().run(); - } catch (InterruptedException e) { - logger.error(e.getMessage()); +public abstract class ActiveCreature { + private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); + + private BlockingQueue requests; + + private String name; + + private Thread thread; + + public ActiveCreature(String name) { + this.name = name; + this.requests = new LinkedBlockingQueue(); + thread = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + requests.take().run(); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } } - } - } - } - ); - 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 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; - } + ); + 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 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; + } } ``` -We can see that any class that will extend the ActiveCreature class will have its own thread of control to invoke and execute methods. +We can see that any class that will extend the `ActiveCreature` class will have its own thread of control to invoke and execute methods. -For example, the Orc class: +For example, the `Orc` class: ```java public class Orc extends ActiveCreature { - public Orc(String name) { - super(name); - } - + public Orc(String name) { + super(name); + } } ``` -Now, we can create multiple creatures such as Orcs, tell them to eat and roam, and they will execute it on their own thread of control: +Now, we can create multiple creatures such as orcs, tell them to eat and roam, and they will execute it on their own thread of control: ```java - public static void main(String[] args) { - 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()); +public class App implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(App.class.getName()); + + private static final int NUM_CREATURES = 3; + + public static void main(String[] args) { + var app = new App(); + app.run(); + } + + @Override + public void run() { + List creatures = new ArrayList<>(); + try { + for (int i = 0; i < NUM_CREATURES; i++) { + creatures.add(new Orc(Orc.class.getSimpleName() + i)); + creatures.get(i).eat(); + creatures.get(i).roam(); + } + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + Thread.currentThread().interrupt(); + } finally { + for (int i = 0; i < NUM_CREATURES; i++) { + creatures.get(i).kill(0); + } + } } - Runtime.getRuntime().exit(1); - } +} ``` -## Class diagram +Program output: + +``` +09:00:02.501 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 is eating! +09:00:02.501 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 is eating! +09:00:02.501 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 is eating! +09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has finished eating! +09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has finished eating! +09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has started to roam in the wastelands. +09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has finished eating! +09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has started to roam in the wastelands. +09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has started to roam in the wastelands. +``` -![alt text](./etc/active-object.urm.png "Active Object class diagram") +## When to Use the Active Object Pattern in Java -## Applicability +Use the Active Object pattern in Java when: -* When you need to perform long-running operations without blocking the main thread. +* when you need to handle asynchronous tasks without blocking the main thread, ensuring better performance and responsiveness. * When you need to interact with external resources asynchronously. * When you want to improve the responsiveness of your application. * When you need to manage concurrent tasks in a modular and maintainable way. -## Tutorials +## Active Object Pattern Java Tutorials + +* [Android and Java Concurrency: The Active Object Pattern(Douglas Schmidt)](https://www.youtube.com/watch?v=Cd8t2u5Qmvc) + +## Real-World Applications of Active Object Pattern in Java + +* Real-time trading systems where transaction requests are handled asynchronously. +* GUIs where long-running tasks are executed in the background without freezing the user interface. +* Game programming to handle concurrent updates to game state or AI computations. -* [Android and Java Concurrency: The Active Object Pattern](https://www.youtube.com/watch?v=Cd8t2u5Qmvc) +## Benefits and Trade-offs of Active Object Pattern -## Consequences +Discover the benefits and trade-offs of using the Active Object pattern in Java, including improved thread safety and potential overhead concerns. -Benefits +Benefits: * Improves responsiveness of the main thread. * Encapsulates concurrency concerns within objects. * Promotes better code organization and maintainability. * Provides thread safety and avoids shared state access problems. -Trade-offs +Trade-offs: * Introduces additional overhead due to message passing and thread management. * May not be suitable for all types of concurrency problems. -## Related patterns +## Related Java Design Patterns -* Observer -* Reactor -* Producer-consumer -* Thread pool +* [Command](https://java-design-patterns.com/patterns/command/): Encapsulates a request as an object, similarly to how the Active Object pattern encapsulates method calls. +* [Promise](https://java-design-patterns.com/patterns/promise/): Provides a means to retrieve the result of an asynchronous method call, often used in conjunction with Active Object. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): The Active Object pattern can use a proxy to handle method invocations asynchronously. -## Credits +## References and Credits * [Design Patterns: Elements of Reusable Object Software](https://amzn.to/3HYqrBE) * [Concurrent Programming in Java: Design Principles and Patterns](https://amzn.to/498SRVq) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) * [Learning Concurrent Programming in Scala](https://amzn.to/3UE07nV) * [Pattern Languages of Program Design 3](https://amzn.to/3OI1j61) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) diff --git a/active-object/etc/active-object-sequence-diagram.png b/active-object/etc/active-object-sequence-diagram.png new file mode 100644 index 000000000000..b725d9b07b6d Binary files /dev/null and b/active-object/etc/active-object-sequence-diagram.png differ diff --git a/active-object/pom.xml b/active-object/pom.xml index 08a09e6642e8..aa26dbbe2e48 100644 --- a/active-object/pom.xml +++ b/active-object/pom.xml @@ -34,6 +34,14 @@ active-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java index d1c5fa69264d..5a440020c0ac 100644 --- a/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java +++ b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java @@ -29,87 +29,87 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ActiveCreature class is the base of the active object example. - * @author Noam Greenshtain - * - */ +/** ActiveCreature class is the base of the active object example. */ public abstract class ActiveCreature { - + private static final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); private BlockingQueue requests; - + private String name; - + private Thread thread; // Thread of execution. - + private int status; // status of the thread of execution. - /** - * Constructor and initialization. - */ + /** Constructor and initialization. */ protected ActiveCreature(String name) { this.name = name; this.status = 0; this.requests = new LinkedBlockingQueue<>(); - thread = new Thread(() -> { - boolean infinite = true; - while (infinite) { - try { - requests.take().run(); - } catch (InterruptedException e) { - if (this.status != 0) { - logger.error("Thread was interrupted. --> {}", e.getMessage()); - } - infinite = false; - Thread.currentThread().interrupt(); - } - } - }); + thread = + new Thread( + () -> { + boolean infinite = true; + while (infinite) { + try { + requests.take().run(); + } catch (InterruptedException e) { + if (this.status != 0) { + logger.error("Thread was interrupted. --> {}", e.getMessage()); + } + infinite = false; + Thread.currentThread().interrupt(); + } + } + }); thread.start(); } /** * Eats the porridge. + * * @throws InterruptedException due to firing a new Runnable. */ public void eat() throws InterruptedException { - requests.put(() -> { - logger.info("{} is eating!", name()); - logger.info("{} has finished eating!", name()); - }); + requests.put( + () -> { + logger.info("{} is eating!", name()); + logger.info("{} has finished eating!", name()); + }); } /** * Roam the wastelands. + * * @throws InterruptedException due to firing a new Runnable. */ public void roam() throws InterruptedException { - requests.put(() -> - logger.info("{} has started to roam in the wastelands.", name()) - ); + requests.put(() -> logger.info("{} has started to roam in the wastelands.", name())); } - + /** * Returns the name of the creature. + * * @return the name of the creature. */ public String name() { return this.name; } - + /** * Kills the thread of execution. + * * @param status of the thread of execution. 0 == OK, the rest is logging an error. */ public void kill(int status) { this.status = status; this.thread.interrupt(); } - + /** * Returns the status of the thread of execution. + * * @return the status of the thread of execution. */ public int getStatus() { diff --git a/active-object/src/main/java/com/iluwatar/activeobject/App.java b/active-object/src/main/java/com/iluwatar/activeobject/App.java index b88b4c5590a0..ca3a5526ebb8 100644 --- a/active-object/src/main/java/com/iluwatar/activeobject/App.java +++ b/active-object/src/main/java/com/iluwatar/activeobject/App.java @@ -30,17 +30,17 @@ import org.slf4j.LoggerFactory; /** - * The Active Object pattern helps to solve synchronization difficulties without using - * 'synchronized' methods. The active object will contain a thread-safe data structure - * (such as BlockingQueue) and use to synchronize method calls by moving the logic of the method - * into an invocator(usually a Runnable) and store it in the DSA. - * + * The Active Object pattern helps to solve synchronization difficulties without using + * 'synchronized' methods. The active object will contain a thread-safe data structure (such as + * BlockingQueue) and use to synchronize method calls by moving the logic of the method into an + * invocator(usually a Runnable) and store it in the DSA. + * *

In this example, we fire 20 threads to modify a value in the target class. */ public class App implements Runnable { - + private static final Logger logger = LoggerFactory.getLogger(App.class.getName()); - + private static final int NUM_CREATURES = 3; /** @@ -48,11 +48,11 @@ public class App implements Runnable { * * @param args command line arguments. */ - public static void main(String[] args) { + public static void main(String[] args) { var app = new App(); app.run(); } - + @Override public void run() { List creatures = new ArrayList<>(); diff --git a/active-object/src/main/java/com/iluwatar/activeobject/Orc.java b/active-object/src/main/java/com/iluwatar/activeobject/Orc.java index 07f30aad0788..30adde034de5 100644 --- a/active-object/src/main/java/com/iluwatar/activeobject/Orc.java +++ b/active-object/src/main/java/com/iluwatar/activeobject/Orc.java @@ -24,15 +24,10 @@ */ package com.iluwatar.activeobject; -/** - * An implementation of the ActiveCreature class. - * @author Noam Greenshtain - * - */ +/** An implementation of the ActiveCreature class. */ public class Orc extends ActiveCreature { public Orc(String name) { super(name); } - } diff --git a/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java b/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java index 5441ed6b0d9b..be79e2fb5527 100644 --- a/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java +++ b/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java @@ -27,17 +27,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; + class ActiveCreatureTest { - - @Test - void executionTest() throws InterruptedException { - ActiveCreature orc = new Orc("orc1"); - assertEquals("orc1",orc.name()); - assertEquals(0,orc.getStatus()); - orc.eat(); - orc.roam(); - orc.kill(0); - } - + @Test + void executionTest() throws InterruptedException { + ActiveCreature orc = new Orc("orc1"); + assertEquals("orc1", orc.name()); + assertEquals(0, orc.getStatus()); + orc.eat(); + orc.roam(); + orc.kill(0); + } } diff --git a/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java b/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java index 8ef2f142cf46..559e2a1f58f1 100644 --- a/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java +++ b/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java @@ -28,11 +28,10 @@ import org.junit.jupiter.api.Test; - class AppTest { - @Test - void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/acyclic-visitor/README.md b/acyclic-visitor/README.md index 57adf4673b18..fb57d5681fd4 100644 --- a/acyclic-visitor/README.md +++ b/acyclic-visitor/README.md @@ -1,22 +1,25 @@ --- -title: Acyclic Visitor +title: "Acyclic Visitor Pattern in Java: Streamlining Object Interactions" +shortTitle: Acyclic Visitor +description: "Learn about the Acyclic Visitor pattern in Java. This guide explains how it decouples operations from object hierarchies, providing examples and real-world applications." category: Behavioral language: en tag: - Decoupling - Extensibility + - Interface + - Object composition --- -## Intent +## Intent of Acyclic Visitor Design Pattern -The Acyclic Visitor pattern decouples operations from an object hierarchy, allowing you to add new operations without modifying the object structure directly. +The Acyclic Visitor pattern in Java decouples operations from an object hierarchy, providing a flexible design for various applications. -## Explanation +## Detailed Explanation of Acyclic Visitor Pattern with Real-World Examples -Real world example +Real-world example -> We have a hierarchy of modem classes. The modems in this hierarchy need to be visited by an external algorithm based -> on filtering criteria (is it Unix or DOS compatible modem). +> An analogous real-world example of the Acyclic Visitor pattern in Java is a museum guide system, demonstrating the practical application of this design pattern. Imagine a museum with various exhibits like paintings, sculptures, and historical artifacts. The museum has different types of guides (audio guide, human guide, virtual reality guide) that provide information about each exhibit. Instead of modifying the exhibits every time a new guide type is introduced, each guide implements an interface to visit different exhibit types. This way, the museum can add new types of guides without altering the existing exhibits, ensuring that the system remains extensible and maintainable without forming any dependency cycles. In plain words @@ -24,108 +27,123 @@ In plain words [WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) says -> The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those -> hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern. +> The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern. -**Programmatic Example** +Sequence diagram + +![Acyclic Visitor sequence diagram](./etc/acyclic-visitor-sequence-diagram.png "Acyclic Visitor sequence diagram") + + +## Programmatic Example of Acyclic Visitor in Java + +In this Java example, we have a hierarchy of modem classes illustrating the Acyclic Visitor pattern. The modems in this hierarchy need to be visited by an external algorithm based on filtering criteria (is it Unix or DOS compatible modem). 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"); + + // Other properties and methods... + + @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"); + + // Other properties and methods... + + @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"); + } } - } } ``` -Next we introduce the `ModemVisitor` hierarchy. +Next, we introduce the `ModemVisitor` hierarchy. ```java 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 { } public class ConfigureForDosVisitor implements AllModemVisitor { - ... - @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."); - } + + // Other properties and methods... + + @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."); + } } public class ConfigureForUnixVisitor implements ZoomVisitor { - ... - @Override - public void visit(Zoom zoom) { - LOGGER.info(zoom + " used with Unix configurator."); - } + + // Other properties and methods... + + @Override + public void visit(Zoom zoom) { + LOGGER.info(zoom + " used with Unix configurator."); + } } ``` Finally, here are the visitors in action. ```java +public static void main(String[] args) { 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); + + hayes.accept(conDos); // Hayes modem with Dos configurator + zoom.accept(conDos); // Zoom modem with Dos configurator + hayes.accept(conUnix); // Hayes modem with Unix configurator + zoom.accept(conUnix); // Zoom modem with Unix configurator +} ``` Program output: ``` - // Hayes modem used with Dos configurator. - // Zoom modem used with Dos configurator. - // Only HayesVisitor is allowed to visit Hayes modem - // Zoom modem used with Unix configurator. +09:15:11.125 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForDosVisitor -- Hayes modem used with Dos configurator. +09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForDosVisitor -- Zoom modem used with Dos configurator. +09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.Hayes -- Only HayesVisitor is allowed to visit Hayes modem +09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor -- Zoom modem used with Unix configurator. ``` -## Class diagram - -![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") - -## Applicability +## When to Use the Acyclic Visitor Pattern in Java This pattern can be used: @@ -135,30 +153,34 @@ This pattern can be used: * When the visited class hierarchy will be frequently extended with new derivatives of the Element class. * When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive. -## Tutorials +## Acyclic Visitor Pattern Java Tutorials -* [Acyclic Visitor Pattern Example](https://codecrafter.blogspot.com/2012/12/the-acyclic-visitor-pattern.html) +* [The Acyclic Visitor Pattern (Code Crafter)](https://codecrafter.blogspot.com/2012/12/the-acyclic-visitor-pattern.html) -## Consequences +## Benefits and Trade-offs of Acyclic Visitor Pattern Benefits: -* No dependency cycles between class hierarchies. -* No need to recompile all the visitors if a new one is added. -* Does not cause compilation failure in existing visitors if class hierarchy has a new member. +* Extensible: New operations can be added easily without changing the object structure. +* Decoupled: Reduces coupling between the objects and the operations on them. +* No dependency cycles: Ensures acyclic dependencies, improving maintainability and reducing complexity. Trade-offs: -* Violates [Liskov's Substitution Principle](https://java-design-patterns.com/principles/#liskov-substitution-principle) by showing that it can accept all visitors but actually only being interested in particular visitors. -* Parallel hierarchy of visitors has to be created for all members in visitable class hierarchy. +* Increased complexity: Can introduce additional complexity with the need for multiple visitor interfaces. +* Maintenance overhead: Modifying the object hierarchy requires updating all visitors. -## Related patterns +## Related Java Design Patterns -* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/) +* [Composite](https://java-design-patterns.com/patterns/composite/): Often used in conjunction with Acyclic Visitor to allow treating individual objects and compositions uniformly. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used alongside to add responsibilities to objects dynamically. +* [Visitor](https://java-design-patterns.com/patterns/visitor/): The Acyclic Visitor pattern is a variation of the Visitor pattern that avoids cyclic dependencies. -## Credits +## References and Credits -* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) -* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML](https://amzn.to/4bOtzwF) +* [Acyclic Visitor (Robert C. Martin)](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) +* [Acyclic Visitor (WikiWikiWeb)](https://wiki.c2.com/?AcyclicVisitor) diff --git a/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png b/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png new file mode 100644 index 000000000000..a3c2ba56b89f Binary files /dev/null and b/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png differ diff --git a/acyclic-visitor/pom.xml b/acyclic-visitor/pom.xml index 52604048e127..b4f5646b71c0 100644 --- a/acyclic-visitor/pom.xml +++ b/acyclic-visitor/pom.xml @@ -34,6 +34,14 @@ acyclic-visitor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java index 38da4923a467..a3b1679a2d9f 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java @@ -28,6 +28,4 @@ * All ModemVisitor interface extends all visitor interfaces. This interface provides ease of use * when a visitor needs to visit all modem types. */ -public interface AllModemVisitor extends ZoomVisitor, HayesVisitor { - -} +public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {} diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java index 64d4d039aba3..3b7c6cd61e4b 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java @@ -37,9 +37,7 @@ */ public class App { - /** - * Program's entry point. - */ + /** Program's entry point. */ public static void main(String[] args) { var conUnix = new ConfigureForUnixVisitor(); var conDos = new ConfigureForDosVisitor(); @@ -50,6 +48,6 @@ public static void main(String[] args) { hayes.accept(conDos); // Hayes modem with Dos configurator zoom.accept(conDos); // Zoom modem with Dos configurator hayes.accept(conUnix); // Hayes modem with Unix configurator - zoom.accept(conUnix); // Zoom modem with Unix configurator + zoom.accept(conUnix); // Zoom modem with Unix configurator } } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java index 9f9f29187839..267a8d66ac45 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java @@ -27,8 +27,7 @@ import lombok.extern.slf4j.Slf4j; /** - * ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos - * manufacturer. + * ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos manufacturer. */ @Slf4j public class ConfigureForDosVisitor implements AllModemVisitor { diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java index 097f19c0dbbd..d9fd14f69435 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java @@ -37,4 +37,4 @@ public class ConfigureForUnixVisitor implements ZoomVisitor { public void visit(Zoom zoom) { LOGGER.info(zoom + " used with Unix configurator."); } -} \ No newline at end of file +} diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java index 384df8a4d9c9..e0b2fcc2b530 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Hayes class implements its accept method. - */ +/** Hayes class implements its accept method. */ @Slf4j public class Hayes implements Modem { - /** - * Accepts all visitors but honors only HayesVisitor. - */ + /** Accepts all visitors but honors only HayesVisitor. */ @Override public void accept(ModemVisitor modemVisitor) { if (modemVisitor instanceof HayesVisitor) { @@ -42,12 +38,9 @@ public void accept(ModemVisitor modemVisitor) { } else { LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem"); } - } - /** - * Hayes' modem's toString method. - */ + /** Hayes' modem's toString method. */ @Override public String toString() { return "Hayes modem"; diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java index a33c87cfa645..aad9b970994f 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java @@ -24,9 +24,7 @@ */ package com.iluwatar.acyclicvisitor; -/** - * HayesVisitor interface. - */ +/** HayesVisitor interface. */ public interface HayesVisitor extends ModemVisitor { void visit(Hayes hayes); } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java index fd15ee422468..8552574453e5 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java @@ -24,10 +24,7 @@ */ package com.iluwatar.acyclicvisitor; -/** - * //Modem abstract class. - * converted to an interface - */ +/** //Modem abstract class. converted to an interface */ public interface Modem { void accept(ModemVisitor modemVisitor); } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java index e9f02e1ad6fd..59b50a54a12f 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Zoom class implements its accept method. - */ +/** Zoom class implements its accept method. */ @Slf4j public class Zoom implements Modem { - /** - * Accepts all visitors but honors only ZoomVisitor. - */ + /** Accepts all visitors but honors only ZoomVisitor. */ @Override public void accept(ModemVisitor modemVisitor) { if (modemVisitor instanceof ZoomVisitor) { @@ -44,9 +40,7 @@ public void accept(ModemVisitor modemVisitor) { } } - /** - * Zoom modem's toString method. - */ + /** Zoom modem's toString method. */ @Override public String toString() { return "Zoom modem"; diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java index 639af1c65777..5388ded6f735 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java @@ -24,9 +24,7 @@ */ package com.iluwatar.acyclicvisitor; -/** - * ZoomVisitor interface. - */ +/** ZoomVisitor interface. */ public interface ZoomVisitor extends ModemVisitor { void visit(Zoom zoom); } diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java index 9cc242d8f7fc..7a21498a63ea 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.acyclicvisitor; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that the Acyclic Visitor example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that the Acyclic Visitor example runs without errors. */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java index 66640e3ca1ac..a989d9287921 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java @@ -24,14 +24,12 @@ */ package com.iluwatar.acyclicvisitor; -import org.junit.jupiter.api.Test; - import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; -/** - * Hayes test class - */ +import org.junit.jupiter.api.Test; + +/** Hayes test class */ class HayesTest { @Test diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java index df7b7e8408a5..d5fe79965d47 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java @@ -24,16 +24,13 @@ */ package com.iluwatar.acyclicvisitor; - -import org.junit.jupiter.api.Test; - import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -/** - * Zoom test class - */ +import org.junit.jupiter.api.Test; + +/** Zoom test class */ class ZoomTest { @Test diff --git a/adapter/README.md b/adapter/README.md index 8de9f4138089..489742494709 100644 --- a/adapter/README.md +++ b/adapter/README.md @@ -1,28 +1,31 @@ --- -title: Adapter +title: "Adapter Pattern in Java: Seamless Integration of Incompatible Systems" +shortTitle: Adapter +description: "Learn how the Adapter Design Pattern works in Java with detailed examples and use cases. Understand how it enables compatibility between incompatible interfaces." category: Structural language: en tag: - Compatibility + - Decoupling - Gang of Four - - Integration + - Interface + - Object composition + - Wrapping --- ## Also known as -Wrapper +* Wrapper -## Intent +## Intent of Adapter Design Pattern -The Adapter pattern converts the interface of a class into another interface that clients expect, enabling compatibility. +The Adapter Design Pattern in Java converts the interface of a class into another interface that clients expect, enabling compatibility. -## Explanation +## Detailed Explanation of Adapter Pattern with Real-World Examples 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 +> 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 In plain words @@ -32,114 +35,120 @@ Wikipedia says > In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code. -**Programmatic Example** +Sequence diagram -Consider a captain that can only use rowing boats and cannot sail at all. +![Adapter sequence diagram](./etc/adapter-sequence-diagram.png "Adapter sequence diagram") + +## Programmatic Example of Adapter Pattern in Java + +The Adapter Pattern example in Java shows how a class with an incompatible interface can be adapted to work with another class. + +Consider a wannabe captain that can only use rowing boats but can't sail at all. 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"); + } } ``` -And captain expects an implementation of `RowingBoat` interface to be able to move +The captain expects an implementation of `RowingBoat` interface to be able to move. ```java public class Captain { - private final RowingBoat rowingBoat; - // default constructor and setter for rowingBoat - public Captain(RowingBoat rowingBoat) { - this.rowingBoat = rowingBoat; - } + private final RowingBoat rowingBoat; - public void row() { - rowingBoat.row(); - } + // default constructor and setter for rowingBoat + public Captain(RowingBoat rowingBoat) { + this.rowingBoat = rowingBoat; + } + + public void row() { + rowingBoat.row(); + } } ``` -Now let's say the pirates are coming and our captain needs to escape but there is only a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills. +Now, let's say the pirates are coming and our captain needs to escape but there is only a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills. ```java @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(); + } } ``` -And now the `Captain` can use the `FishingBoat` to escape the pirates. +Now the `Captain` can use the `FishingBoat` to escape the pirates. ```java -var captain = new Captain(new FishingBoatAdapter()); -captain.row(); + public static void main(final String[] args) { + // The captain can only operate rowing boats but with adapter he is able to + // use fishing boats as well + var captain = new Captain(new FishingBoatAdapter()); + captain.row(); +} ``` -## Class diagram +The program outputs: -![alt text](./etc/adapter.urm.png "Adapter class diagram") +``` +10:25:08.074 [main] INFO com.iluwatar.adapter.FishingBoat -- The fishing boat is sailing +``` -## Applicability +## When to Use the Adapter Pattern in Java -Use the Adapter pattern when +Use the Adapter pattern in Java when * You want to use an existing class, and its interface does not match the one you need * You want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces * You need to use several existing subclasses, but it's impractical to adapt their interface by subclassing everyone. An object adapter can adapt the interface of its parent class. * Most of the applications using third-party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code. -## Tutorials - -* [Dzone](https://dzone.com/articles/adapter-design-pattern-in-java) -* [Refactoring Guru](https://refactoring.guru/design-patterns/adapter/java/example) -* [Baeldung](https://www.baeldung.com/java-adapter-pattern) -* [GeeksforGeeks](https://www.geeksforgeeks.org/adapter-pattern/) - +## Adapter Pattern Java Tutorials -## Consequences +* [Using the Adapter Design Pattern in Java (Dzone)](https://dzone.com/articles/adapter-design-pattern-in-java) +* [Adapter in Java (Refactoring Guru)](https://refactoring.guru/design-patterns/adapter/java/example) +* [The Adapter Pattern in Java (Baeldung)](https://www.baeldung.com/java-adapter-pattern) +* [Adapter Design Pattern (GeeksForGeeks)](https://www.geeksforgeeks.org/adapter-pattern/) -Class and object adapters have different trade-offs. A class adapter +## Benefits and Trade-offs of Adapter Pattern -* Adapts Adaptee to Target by committing to a concrete Adaptee class. As a consequence, a class adapter won’t work when we want to adapt a class and all its subclasses. -* Lets Adapter override some of Adaptee’s behavior since Adapter is a subclass of Adaptee. -* Introduces only one object, and no additional pointer indirection is needed to get to the adaptee. +Class and object adapters offer different benefits and drawbacks. A class adapter adapts the Adaptee to the Target by binding to a specific Adaptee class, which means it cannot adapt a class and all its subclasses. This type of adapter allows the Adapter to override some of the Adaptee’s behavior because the Adapter is a subclass of the Adaptee. Additionally, it introduces only one object without needing extra pointer indirection to reach the Adaptee. -An object adapter +On the other hand, an object adapter allows a single Adapter to work with multiple Adaptees, including the Adaptee and all its subclasses. This type of adapter can add functionality to all Adaptees simultaneously. However, it makes overriding the Adaptee’s behavior more difficult, as it requires subclassing the Adaptee and having the Adapter refer to this subclass instead of the Adaptee itself. -* Lets a single Adapter work with many Adaptees, that is, the Adaptee itself and all of its subclasses (if any). The Adapter can also add functionality to all Adaptees at once. -* Makes it harder to override Adaptee behavior. It will require subclassing Adaptee and making the Adapter refer to the subclass rather than the Adaptee itself. - - -## Real-world examples +## Real-World Applications of Adapter Pattern in Java +* `java.io.InputStreamReader` and `java.io.OutputStreamWriter` in the Java IO library. +* GUI component libraries that allow for plug-ins or adapters to convert between different GUI component interfaces. * [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) * [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) * [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-) * [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-) +## References and Credits -## Credits - -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/adapter/etc/adapter-sequence-diagram.png b/adapter/etc/adapter-sequence-diagram.png new file mode 100644 index 000000000000..a9bb557ea61e Binary files /dev/null and b/adapter/etc/adapter-sequence-diagram.png differ diff --git a/adapter/pom.xml b/adapter/pom.xml index 9307c255d09f..6e7f45a515b5 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -4,7 +4,7 @@ This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). The MIT License - Copyright © 2014-2023 Ilkka Seppälä + Copyright © 2014-2022 Ilkka Seppälä Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,6 +34,14 @@ adapter + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/adapter/src/main/java/com/iluwatar/adapter/App.java b/adapter/src/main/java/com/iluwatar/adapter/App.java index 1f572813cef1..a4fa74274f74 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/App.java +++ b/adapter/src/main/java/com/iluwatar/adapter/App.java @@ -37,16 +37,15 @@ *

The Adapter ({@link FishingBoatAdapter}) converts the interface of the adaptee class ({@link * FishingBoat}) into a suitable one expected by the client ({@link RowingBoat}). * - *

The story of this implementation is this.
Pirates are coming! we need a {@link - * RowingBoat} to flee! We have a {@link FishingBoat} and our captain. We have no time to make up a - * new ship! we need to reuse this {@link FishingBoat}. The captain needs a rowing boat which he can - * operate. The spec is in {@link RowingBoat}. We will use the Adapter pattern to reuse {@link - * FishingBoat}. + *

The story of this implementation is this.
+ * Pirates are coming! we need a {@link RowingBoat} to flee! We have a {@link FishingBoat} and our + * captain. We have no time to make up a new ship! we need to reuse this {@link FishingBoat}. The + * captain needs a rowing boat which he can operate. The spec is in {@link RowingBoat}. We will use + * the Adapter pattern to reuse {@link FishingBoat}. */ public final class App { - private App() { - } + private App() {} /** * Program entry point. diff --git a/adapter/src/main/java/com/iluwatar/adapter/Captain.java b/adapter/src/main/java/com/iluwatar/adapter/Captain.java index 3d6d7746d00a..3b771e9d833e 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/Captain.java +++ b/adapter/src/main/java/com/iluwatar/adapter/Captain.java @@ -29,7 +29,8 @@ import lombok.Setter; /** - * The Captain uses {@link RowingBoat} to sail.
This is the client in the pattern. + * The Captain uses {@link RowingBoat} to sail.
+ * This is the client in the pattern. */ @Setter @NoArgsConstructor @@ -41,5 +42,4 @@ public final class Captain { void row() { rowingBoat.row(); } - } diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java index e692d859873c..dd39f88f1ce0 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java +++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java @@ -36,5 +36,4 @@ final class FishingBoat { void sail() { LOGGER.info("The fishing boat is sailing"); } - } diff --git a/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java index c8714ef91040..55eeeaf4bd42 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java +++ b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java @@ -25,10 +25,10 @@ package com.iluwatar.adapter; /** - * The interface expected by the client.
A rowing boat is rowed to move. + * The interface expected by the client.
+ * A rowing boat is rowed to move. */ public interface RowingBoat { void row(); - } diff --git a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java index 10024ff0dc46..bc4984da3d6f 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.adapter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.util.HashMap; -import java.util.Map; - import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -/** - * Tests for the adapter pattern. - */ +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Tests for the adapter pattern. */ class AdapterPatternTest { private Map beans; @@ -43,9 +41,7 @@ class AdapterPatternTest { private static final String ROWING_BEAN = "captain"; - /** - * This method runs before the test execution and sets the bean objects in the beans Map. - */ + /** This method runs before the test execution and sets the bean objects in the beans Map. */ @BeforeEach void setup() { beans = new HashMap<>(); diff --git a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java index be51d2687548..a2cc4c22bcaa 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java @@ -24,23 +24,17 @@ */ package com.iluwatar.adapter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Adapter example runs without errors. - */ -class AppTest { +import org.junit.jupiter.api.Test; - /** - * Check whether the execution of the main method in {@link App} - * throws an exception. - */ +/** Tests that Adapter example runs without errors. */ +class AppTest { + /** Check whether the execution of the main method in {@link App} throws an exception. */ @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/aggregator-microservices/README.md b/aggregator-microservices/README.md deleted file mode 100644 index 732129e17916..000000000000 --- a/aggregator-microservices/README.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: Aggregator Microservices -category: Architectural -language: en -tag: -- API design -- Cloud distributed -- Decoupling -- Microservices ---- - -## Intent - -Streamline client's interactions with system's microservices by providing a single aggregation point that consolidates data and responses from multiple services. This simplifies the client's communication with the system, improving efficiency and reducing complexity. - -## Explanation - -Real world example - -> Our web marketplace needs information about products and their current inventory. It makes a call to an aggregator service, which, in turn, calls the product information and product inventory microservices, returning the combined information. - -In plain words - -> Aggregator Microservice collects pieces of data from various microservices and returns an aggregate for processing. - -Stack Overflow says - -> Aggregator Microservice invokes multiple services to achieve the functionality required by the application. - -**Programmatic Example** - -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 -> - ... -} -``` - -Next we can introduce our `Aggregator` microservice. It contains clients `ProductInformationClient` and -`ProductInventoryClient` for calling respective microservices. - -```java -@RestController -public class Aggregator { - - @Resource - private ProductInformationClient informationClient; - - @Resource - private ProductInventoryClient inventoryClient; - - @RequestMapping(path = "/product", method = RequestMethod.GET) - public Product getProduct() { - - 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 default error inventory - product.setProductInventories(requireNonNullElse(productInventory, -1)); - - return product; - } -} -``` - -Here's the essence of information microservice implementation. Inventory microservice is similar, it just returns -inventory counts. - -```java -@RestController -public class InformationController { - @RequestMapping(value = "/information", method = RequestMethod.GET) - public String getProductTitle() { - return "The Product Title."; - } -} -``` - -Now calling our `Aggregator` REST API returns the product information. - -```bash -curl http://localhost:50004/product -{"title":"The Product Title.","productInventories":5} -``` - -## Class diagram - -![Class diagram of the Aggregator Microservices Pattern](./aggregator-service/etc/aggregator-service.png "Aggregator Microservice") - -## Applicability - -The Aggregator Microservices Design Pattern is particularly useful in scenarios where a client requires a composite response that is assembled from data provided by multiple microservices. Common use cases include e-commerce applications where product details, inventory, and reviews might be provided by separate services, or in dashboard applications where aggregated data from various services is displayed in a unified view. - -## Consequences - -Benefits: - -* Simplified Client: Clients interact with just one service rather than managing calls to multiple microservices, which simplifies client-side logic. -* Reduced Latency: By aggregating responses, the number of network calls is reduced, which can improve the application's overall latency. -* Decoupling: Clients are decoupled from the individual microservices, allowing for more flexibility in changing the microservices landscape without impacting clients. -* Centralized Logic: Aggregation allows for centralized transformation and logic application on the data collected from various services, which can be more efficient than handling it in the client or spreading it across multiple services. - -Trade-offs: - -* Single Point of Failure: The aggregator service can become a bottleneck or a single point of failure if not designed with high availability and scalability in mind. -* Complexity: Implementing an aggregator can introduce complexity, especially in terms of data aggregation logic and error handling when dealing with multiple services. - -## Related Patterns - -* [API Gateway](https://java-design-patterns.com/patterns/api-gateway/): The Aggregator Microservices pattern is often used in conjunction with an API Gateway, which provides a single entry point for clients to access multiple microservices. -* [Composite](https://java-design-patterns.com/patterns/composite/): The Aggregator Microservices pattern can be seen as a form of the Composite pattern, where the composite is the aggregated response from multiple microservices. -* [Facade](https://java-design-patterns.com/patterns/facade/): The Aggregator Microservices pattern can be seen as a form of the Facade pattern, where the facade is the aggregator service that provides a simplified interface to the client. - -## Credits - -* [Microservice Design Patterns](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/) -* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60) -* [Architectural Patterns: Uncover essential patterns in the most indispensable realm of enterprise architecture](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4) -* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/43aGpSR) -* [Microservices Patterns: With examples in Java](https://amzn.to/4a5LHkP) -* [Microservice Architecture: Aligning Principles, Practices, and Culture](https://amzn.to/3T9jZNi) -* [Production-Ready Microservices: Building Standardized Systems Across an Engineering Organization](https://amzn.to/4a0Vk4c) -* [Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services](https://amzn.to/3T9g9Uj) diff --git a/ambassador/README.md b/ambassador/README.md index b2902d7ddd42..fa08223feb75 100644 --- a/ambassador/README.md +++ b/ambassador/README.md @@ -1,45 +1,49 @@ --- -title: Ambassador -category: Structural +title: "Ambassador Pattern in Java: Simplifying Remote Resource Management" +shortTitle: Ambassador +description: "Explore the Ambassador Pattern in Java, its benefits, use cases, and practical examples. Learn how to decouple and offload common functionalities to improve system performance and maintainability." +category: Integration language: en tag: + - API design - Decoupling - - Cloud distributed + - Fault tolerance + - Proxy + - Resilience + - Scalability --- -## Intent +## Intent of Ambassador Design Pattern -Provide a helper service instance on a client and offload common functionality away from a shared resource. +The Ambassador Pattern in Java helps offload common functionalities such as monitoring, logging, and routing from a shared resource to a helper service instance, enhancing performance and maintainability in distributed systems. -## Also known as +## Detailed Explanation of Ambassador Pattern with Real-World Examples -* Sidecar +Real-world example -## Explanation - -Real world example - -> A remote service has many clients accessing a function it provides. The service is a legacy application and is -> impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request -> frequency should be implemented along with latency checks and client-side logging. +> Imagine a busy hotel where guests frequently request restaurant reservations, event tickets, or transportation arrangements. Instead of each guest individually contacting these services, the hotel provides a concierge. The concierge handles these tasks on behalf of the guests, ensuring that reservations are made smoothly, tickets are booked on time, and transportation is scheduled efficiently. +> +> In this analogy, the guests are the client services, the external providers (restaurants, ticket vendors, transportation) are the remote services, and the concierge represents the ambassador service. This setup allows the guests to focus on enjoying their stay while the concierge manages the complexities of external interactions, providing a seamless and enhanced experience. In plain words -> With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and -> logging. +> With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging. Microsoft documentation states -> An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern -> can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, -> security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, -> or other applications that are difficult to modify, in order to extend their networking capabilities. It can also -> enable a specialized team to implement those features. +> An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features. + +Sequence diagram + +![Ambassador sequence diagram](./etc/ambassador-sequence-diagram.png "Ambassador sequence diagram") -**Programmatic Example** +## Programmatic Example of Ambassador Pattern in Java -With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented -by the remote service as well as the ambassador service: +In this example of the Ambassador Pattern in Java, we demonstrate how to implement latency checks, logging, and retry mechanisms to improve system reliability. + +A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging. + +With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented by the remote service as well as the ambassador service. ```java interface RemoteServiceInterface { @@ -50,6 +54,7 @@ interface RemoteServiceInterface { A remote services represented as a singleton. ```java + @Slf4j public class RemoteService implements RemoteServiceInterface { private static RemoteService service = null; @@ -61,7 +66,8 @@ public class RemoteService implements RemoteServiceInterface { return service; } - private RemoteService() {} + private RemoteService() { + } @Override public long doRemoteFunction(int value) { @@ -78,69 +84,71 @@ public class RemoteService implements RemoteServiceInterface { } ``` -A service ambassador adding additional features such as logging, latency checks +A service ambassador adds additional features such as logging and latency checks. ```java + @Slf4j public class ServiceAmbassador implements RemoteServiceInterface { - 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); + 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; + } } - } else { - break; - } + return result; } - return result; - } } ``` -A client has a local service ambassador used to interact with the remote service: +A client has a local service ambassador used to interact with the remote service. ```java + @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; + } } ``` @@ -148,36 +156,32 @@ 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); + } } ``` 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 +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 ``` -## Class diagram - -![alt text](./etc/ambassador.urm.png "Ambassador class diagram") +## When to Use the Ambassador Pattern in Java -## Applicability - -* Cloud Native and Microservices Architectures: Especially useful in distributed systems where it's crucial to monitor, log, and secure inter-service communication. +* The Ambassador Pattern is particularly beneficial for Cloud Native and Microservices Architectures in Java. It helps in monitoring, logging, and securing inter-service communication, making it ideal for distributed systems. * Legacy System Integration: Facilitates communication with newer services by handling necessary but non-core functionalities. * Performance Enhancement: Can be used to cache results or compress data to improve communication efficiency. @@ -189,7 +193,7 @@ Typical use cases include: * Offload remote service tasks * Facilitate network connection -## Consequences +## Benefits and Trade-offs of Ambassador Pattern Benefits: @@ -204,7 +208,7 @@ Trade-offs: * Potential Performance Overhead: The additional network hop can introduce latency and overhead, particularly if not optimized. * Deployment Overhead: Requires additional resources and management for deploying and scaling ambassador services. -## Known uses +## Real-World Applications of Ambassador Pattern in Java * Service Mesh Implementations: In a service mesh architecture, like Istio or Linkerd, the Ambassador pattern is often employed as a sidecar proxy that handles inter-service communications. This includes tasks such as service discovery, routing, load balancing, telemetry (metrics and tracing), and security (authentication and authorization). * API Gateways: API gateways can use the Ambassador pattern to encapsulate common functionalities like rate limiting, caching, request shaping, and authentication. This allows backend services to focus on their core business logic without being burdened by these cross-cutting concerns. @@ -216,17 +220,17 @@ Trade-offs: * Network Optimization: For services deployed across different geographical locations or cloud regions, Ambassadors can optimize communication by compressing data, batching requests, or even implementing smart routing to reduce latency and costs. * [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador) -## Related patterns +## Related Java Design Patterns +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Often used in conjunction to manage fault tolerance by stopping calls to an unresponsive service. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): The decorator pattern is used to add functionality to an object dynamically, while the ambassador pattern is used to offload functionality to a separate object. * [Proxy](https://java-design-patterns.com/patterns/proxy/): Shares similarities with the proxy pattern, but the ambassador pattern specifically focuses on offloading ancillary functionalities. * Sidecar: A similar pattern used in the context of containerized applications, where a sidecar container provides additional functionality to the main application container. -* [Decorator](https://java-design-patterns.com/patterns/decorator/): The decorator pattern is used to add functionality to an object dynamically, while the ambassador pattern is used to offload functionality to a separate object. -## Credits +## References and Credits -* [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) -* [Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services](https://www.amazon.com/s?k=designing+distributed+systems&sprefix=designing+distri%2Caps%2C156&linkCode=ll2&tag=javadesignpat-20&linkId=a12581e625462f9038557b01794e5341&language=en_US&ref_=as_li_ss_tl) +* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/43aGpSR) * [Cloud Native Patterns: Designing Change-tolerant Software](https://amzn.to/3wUAl4O) * [Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services](https://amzn.to/3T9g9Uj) -* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60) -* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/43aGpSR) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) +* [Ambassador pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) diff --git a/ambassador/etc/ambassador-sequence-diagram.png b/ambassador/etc/ambassador-sequence-diagram.png new file mode 100644 index 000000000000..71e6a947c5dd Binary files /dev/null and b/ambassador/etc/ambassador-sequence-diagram.png differ diff --git a/ambassador/pom.xml b/ambassador/pom.xml index a6d702426080..15e4a07f0dc3 100644 --- a/ambassador/pom.xml +++ b/ambassador/pom.xml @@ -34,6 +34,14 @@ 4.0.0 ambassador + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/App.java b/ambassador/src/main/java/com/iluwatar/ambassador/App.java index ff025d9b2bf6..8de149fe0813 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/App.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/App.java @@ -28,8 +28,8 @@ * The ambassador pattern creates a helper service that sends network requests on behalf of a * client. It is often used in cloud-based applications to offload features of a remote service. * - *

An ambassador service can be thought of as an out-of-process proxy that is co-located with - * the client. Similar to the proxy design pattern, the ambassador service provides an interface for + *

An ambassador service can be thought of as an out-of-process proxy that is co-located with the + * client. Similar to the proxy design pattern, the ambassador service provides an interface for * another remote service. In addition to the interface, the ambassador provides extra functionality * and features, specifically offloaded common connectivity tasks. This usually consists of * monitoring, logging, routing, security etc. This is extremely useful in legacy applications where @@ -37,14 +37,11 @@ * capabilities. * *

In this example, we will the ({@link ServiceAmbassador}) class represents the ambassador while - * the - * ({@link RemoteService}) class represents a remote application. + * the ({@link RemoteService}) class represents a remote application. */ public class App { - /** - * Entry point. - */ + /** Entry point. */ public static void main(String[] args) { var host1 = new Client(); var host2 = new Client(); diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/Client.java b/ambassador/src/main/java/com/iluwatar/ambassador/Client.java index d0f81c1dd121..0baabf4ffc06 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/Client.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/Client.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * A simple Client. - */ +/** A simple Client. */ @Slf4j public class Client { diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java index eba634494fc7..d99348040cfe 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java @@ -29,9 +29,7 @@ import com.iluwatar.ambassador.util.RandomProvider; import lombok.extern.slf4j.Slf4j; -/** - * A remote legacy application represented by a Singleton implementation. - */ +/** A remote legacy application represented by a Singleton implementation. */ @Slf4j public class RemoteService implements RemoteServiceInterface { private static final int THRESHOLD = 200; @@ -49,9 +47,7 @@ private RemoteService() { this(Math::random); } - /** - * This constructor is used for testing purposes only. - */ + /** This constructor is used for testing purposes only. */ RemoteService(RandomProvider randomProvider) { this.randomProvider = randomProvider; } @@ -75,7 +71,8 @@ public long doRemoteFunction(int value) { LOGGER.error("Thread sleep state interrupted", e); Thread.currentThread().interrupt(); } - return waitTime <= THRESHOLD ? value * 10 + return waitTime <= THRESHOLD + ? value * 10 : RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(); } } diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java index 104d81ec2eff..aa6012bae33f 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java @@ -24,9 +24,7 @@ */ package com.iluwatar.ambassador; -/** - * Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}). - */ +/** Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}). */ interface RemoteServiceInterface { long doRemoteFunction(int value); diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java index 8f1a0a1a4907..8549ed7247f3 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java @@ -29,17 +29,14 @@ /** * Holds information regarding the status of the Remote Service. * - *

This Enum replaces the integer value previously - * stored in {@link RemoteServiceInterface} as SonarCloud was identifying - * it as an issue. All test cases have been checked after changes, - * without failures.

+ *

This Enum replaces the integer value previously stored in {@link RemoteServiceInterface} as + * SonarCloud was identifying it as an issue. All test cases have been checked after changes, + * without failures. */ - public enum RemoteServiceStatus { FAILURE(-1); - @Getter - private final long remoteServiceStatusValue; + @Getter private final long remoteServiceStatusValue; RemoteServiceStatus(long remoteServiceStatusValue) { this.remoteServiceStatusValue = remoteServiceStatusValue; diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java index f3f30a09dc9b..4d310169770b 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java @@ -40,8 +40,7 @@ public class ServiceAmbassador implements RemoteServiceInterface { private static final int RETRIES = 3; private static final int DELAY_MS = 3000; - ServiceAmbassador() { - } + ServiceAmbassador() {} @Override public long doRemoteFunction(int value) { diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java index e8243cdcc7cf..4eba2fada7fa 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java @@ -24,9 +24,7 @@ */ package com.iluwatar.ambassador.util; -/** - * An interface for randomness. Useful for testing purposes. - */ +/** An interface for randomness. Useful for testing purposes. */ public interface RandomProvider { double random(); } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java index cea0eeac7bb9..ddb2d6eff411 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.ambassador; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java index ff7f027f64f1..24603efff9d4 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.ambassador; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Test for {@link Client} - */ +import org.junit.jupiter.api.Test; + +/** Test for {@link Client} */ class ClientTest { @Test @@ -38,6 +36,7 @@ void test() { Client client = new Client(); var result = client.useService(10); - assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); + assertTrue( + result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); } } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java index 5fed19a169b1..81e4f744128f 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java @@ -29,9 +29,7 @@ import com.iluwatar.ambassador.util.RandomProvider; import org.junit.jupiter.api.Test; -/** - * Test for {@link RemoteService} - */ +/** Test for {@link RemoteService} */ class RemoteServiceTest { @Test diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java index 50c354c1485a..0543b2e7e370 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java @@ -24,18 +24,17 @@ */ package com.iluwatar.ambassador; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * Test for {@link ServiceAmbassador} - */ +import org.junit.jupiter.api.Test; + +/** Test for {@link ServiceAmbassador} */ class ServiceAmbassadorTest { @Test void test() { long result = new ServiceAmbassador().doRemoteFunction(10); - assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); + assertTrue( + result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); } } diff --git a/anti-corruption-layer/README.md b/anti-corruption-layer/README.md index bcffc3595237..9b6042850eba 100644 --- a/anti-corruption-layer/README.md +++ b/anti-corruption-layer/README.md @@ -1,11 +1,19 @@ --- -title: Anti-corruption layer +title: "Anti-Corruption Layer Pattern in Java: Ensuring System Integrity Amidst Legacy Systems" +shortTitle: Anti-Corruption Layer +description: "Learn how the Anti-Corruption Layer design pattern helps in decoupling subsystems, preventing data corruption, and facilitating seamless integration in Java applications." category: Integration language: en tag: - Architecture - Decoupling + - Integration - Isolation + - Layered architecture + - Migration + - Modernization + - Refactoring + - Wrapping --- ## Also known as @@ -14,37 +22,43 @@ tag: * Interface layer * Translation layer -## Intent +## Intent of Anti-Corruption Layer Design Pattern -Implement a façade or adapter layer between different subsystems that don't share the same semantics. It translates between different data formats and systems, ensuring that the integration between systems does not lead to corruption of business logic or data integrity. +The Anti-Corruption Layer (ACL) is a crucial design pattern in Java development, particularly for system integration and maintaining data integrity. Implement a façade or adapter layer between different subsystems that don't share the same semantics. It translates between different data formats and systems, ensuring that the integration between systems does not lead to corruption of business logic or data integrity. -## Explanation +## Detailed Explanation of Anti-Corruption Layer Pattern with Real-World Examples -### Context and problem +Real-world example -Most applications rely on other systems for some data or functionality. For example, when a legacy application is migrated to a modern system, it may still need existing legacy resources. New features must be able to call the legacy system. This is especially true of gradual migrations, where different features of a larger application are moved to a modern system over time. +> This example demonstrates how the Anti-Corruption Layer ensures seamless integration between legacy systems and modern platforms, crucial for maintaining business logic integrity during system migration. +> +> Imagine a large retail company transitioning its inventory management system from an old legacy software to a new modern platform. The legacy system has been in use for decades and contains complex business rules and data formats that are incompatible with the new system. Instead of directly connecting the new system to the legacy one, the company implements an Anti-Corruption Layer (ACL). +> +> The ACL acts as a mediator, translating and adapting data between the two systems. When the new system requests inventory data, the ACL translates the request into a format the legacy system understands, retrieves the data, and then translates it back into a format suitable for the new system. This approach ensures that the new system remains unaffected by the intricacies of the legacy system, preventing corruption of data and business logic while facilitating a smooth transition. -Often these legacy systems suffer from quality issues such as convoluted data schemas or obsolete APIs. The features and technologies used in legacy systems can vary widely from more modern systems. To interoperate with the legacy system, the new application may need to support outdated infrastructure, protocols, data models, APIs, or other features that you wouldn't otherwise put into a modern application. +In plain words -Maintaining access between new and legacy systems can force the new system to adhere to at least some of the legacy system's APIs or other semantics. When these legacy features have quality issues, supporting them "corrupts" what might otherwise be a cleanly designed modern application. Similar issues can arise with any external system that your development team doesn't control, not just legacy systems. +> The Anti-Corruption Layer design pattern protects a system from the complexities and changes of external systems by providing an intermediary translation layer. -### Solution +[Microsoft's documentation](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) says -Isolate the different subsystems by placing an anti-corruption layer between them. This layer translates communications between the two systems, allowing one system to remain unchanged while the other can avoid compromising its design and technological approach. +> Implement a façade or adapter layer between different subsystems that don't share the same semantics. This layer translates requests that one subsystem makes to the other subsystem. Use this pattern to ensure that an application's design is not limited by dependencies on outside subsystems. This pattern was first described by Eric Evans in Domain-Driven Design. -### Programmatic example +Sequence diagram -#### Introduction +![Anti-Corruption Layer sequence diagram](./etc/anti-corruption-layer-sequence-diagram.png "Anti-Corruption Layer sequence diagram") -The example shows why the anti-corruption layer is needed. +## Programmatic Example of Anti-Corruption Layer Pattern in Java + +The ACL design pattern in Java provides an intermediary layer that translates data formats, ensuring that integration between different systems does not lead to data corruption. Here are 2 shop-ordering systems: `Legacy` and `Modern`. -The aforementioned systems have different domain models and have to operate simultaneously. Since they work independently the orders can come either from the `Legacy` or `Modern` system. Therefore, the system that receives the legacyOrder needs to check if the legacyOrder is valid and not present in the other system. Then it can place the legacyOrder in its own system. +The aforementioned systems have different domain models and have to operate simultaneously. Since they work independently the orders can come either from the `Legacy` or `Modern` system. Therefore, the system that receives the legacyOrder needs to check if the legacyOrder is valid and not present in the other system. Then it can place the legacyOrder in its own system. But for that, the system needs to know the domain model of the other system and to avoid that, the anti-corruption layer(ACL) is introduced. The ACL is a layer that translates the domain model of the `Legacy` system to the domain model of the `Modern` system and vice versa. Also, it hides all other operations with the other system, uncoupling the systems. -#### Domain model of the `Legacy` system +Domain model of the `Legacy` system: ```java public class LegacyOrder { @@ -56,7 +70,7 @@ public class LegacyOrder { } ``` -#### Domain model of the `Modern` system +Domain model of the `Modern` system: ```java public class ModernOrder { @@ -67,9 +81,11 @@ public class ModernOrder { private String extra; } + public class Customer { private String address; } + public class Shipment { private String item; private String qty; @@ -77,7 +93,7 @@ public class Shipment { } ``` -#### Anti-corruption layer +Anti-corruption layer: ```java public class AntiCorruptionLayer { @@ -99,9 +115,7 @@ public class AntiCorruptionLayer { } ``` -#### The connection - -Wherever the `Legacy` or `Modern` system needs to communicate with the counterpart the ACL needs to be used to avoid corrupting the current domain model. The example below shows how the `Legacy` system places an order with a validation from the `Modern` system. +The connection between the systems. Wherever the `Legacy` or `Modern` system needs to communicate with the counterpart the ACL needs to be used to avoid corrupting the current domain model. The example below shows how the `Legacy` system places an order with a validation from the `Modern` system. ```java public class LegacyShop { @@ -112,7 +126,7 @@ public class LegacyShop { String id = legacyOrder.getId(); - Optional orderInModernSystem = acl.findOrderInModernSystem(id); + Optional orderInModernSystem = acl.findOrderInModernSystem(id); if (orderInModernSystem.isPresent()) { // order is already in the modern system @@ -123,7 +137,7 @@ public class LegacyShop { } ``` -## Applicability +## When to Use the Anti-Corruption Layer Pattern in Java Use this pattern when: @@ -133,18 +147,18 @@ Use this pattern when: * In scenarios where different subsystems within a larger system use different data formats or structures * When there is a need to ensure loose coupling between different subsystems or external services to facilitate easier maintenance and scalability -## Tutorials +## Anti-Corruption Layer Pattern Java Tutorials -* [Microsoft - Anti-Corruption Layer](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) -* [Amazon - Anti-Corruption Layer](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html) +* [Anti-Corruption Layer (Microsoft)](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) +* [Anti-Corruption Layer Pattern (Amazon)](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html) -## Known Uses +## Real-World Applications of Anti-Corruption Layer Pattern in Java * Microservices architectures where individual services must communicate without being tightly coupled to each other’s data schemas * Enterprise systems integration, especially when integrating modern systems with legacy systems * In bounded contexts within Domain-Driven Design (DDD) to maintain the integrity of a domain model when interacting with external systems or subsystems -## Consequences +## Benefits and Trade-offs of Anti-Corruption Layer Pattern Benefits: @@ -158,13 +172,14 @@ Trade-offs: * Requires extra effort in design and implementation to ensure the layer is effective without becoming a bottleneck * Can lead to duplication of models if not carefully managed -## Related Patterns +## Related Java Design Patterns -* [Facade](https://java-design-patterns.com/patterns/facade/): The Anti-Corruption Layer can be seen as a specialized form of the Facade pattern that is used to isolate different subsystems * [Adapter](https://java-design-patterns.com/patterns/adapter/): The Anti-Corruption Layer can be implemented using the Adapter pattern to translate between different data formats or structures +* [Facade](https://java-design-patterns.com/patterns/facade/): The Anti-Corruption Layer can be seen as a specialized form of the Facade pattern that is used to isolate different subsystems * [Gateway](https://java-design-patterns.com/patterns/gateway/): The Anti-Corruption Layer can be used as a Gateway to external systems to provide a unified interface -## Credits +## References and Credits * [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3vptcJz) * [Implementing Domain-Driven Design](https://amzn.to/3ISOSRA) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png b/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png new file mode 100644 index 000000000000..835ff4e84a0f Binary files /dev/null and b/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png differ diff --git a/anti-corruption-layer/etc/anti-corruption-layer.urm.png b/anti-corruption-layer/etc/anti-corruption-layer.urm.png new file mode 100644 index 000000000000..96e432121e7e Binary files /dev/null and b/anti-corruption-layer/etc/anti-corruption-layer.urm.png differ diff --git a/anti-corruption-layer/pom.xml b/anti-corruption-layer/pom.xml index 711e006b7076..2fddfd3ecc46 100644 --- a/anti-corruption-layer/pom.xml +++ b/anti-corruption-layer/pom.xml @@ -45,8 +45,8 @@ test - junit - junit + org.junit.jupiter + junit-jupiter-engine test diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java index 3f53c54c0f60..f7cf8f075f83 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,16 +22,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - package com.iluwatar.corruption; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * This layer translates communications between the two systems, - * allowing one system to remain unchanged while the other can avoid compromising - * its design and technological approach. + * This layer translates communications between the two systems, allowing one system to remain + * unchanged while the other can avoid compromising its design and technological approach. */ @SpringBootApplication public class App { diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java index 87b7f7d00a0c..880d98c7d5b0 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java @@ -1,28 +1,48 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ /** - * Context and problem - * Most applications rely on other systems for some data or functionality. - * For example, when a legacy application is migrated to a modern system, - * it may still need existing legacy resources. New features must be able to call the legacy system. - * This is especially true of gradual migrations, - * where different features of a larger application are moved to a modern system over time. + * Context and problem Most applications rely on other systems for some data or functionality. For + * example, when a legacy application is migrated to a modern system, it may still need existing + * legacy resources. New features must be able to call the legacy system. This is especially true of + * gradual migrations, where different features of a larger application are moved to a modern system + * over time. * - *

Often these legacy systems suffer from quality issues such as convoluted data schemas - * or obsolete APIs. - * The features and technologies used in legacy systems can vary widely from more modern systems. - * To interoperate with the legacy system, - * the new application may need to support outdated infrastructure, protocols, data models, APIs, - * or other features that you wouldn't otherwise put into a modern application. + *

Often these legacy systems suffer from quality issues such as convoluted data schemas or + * obsolete APIs. The features and technologies used in legacy systems can vary widely from more + * modern systems. To interoperate with the legacy system, the new application may need to support + * outdated infrastructure, protocols, data models, APIs, or other features that you wouldn't + * otherwise put into a modern application. * - *

Maintaining access between new and legacy systems can force the new system to adhere to - * at least some of the legacy system's APIs or other semantics. - * When these legacy features have quality issues, supporting them "corrupts" what might - * otherwise be a cleanly designed modern application. - * Similar issues can arise with any external system that your development team doesn't control, - * not just legacy systems. + *

Maintaining access between new and legacy systems can force the new system to adhere to at + * least some of the legacy system's APIs or other semantics. When these legacy features have + * quality issues, supporting them "corrupts" what might otherwise be a cleanly designed modern + * application. Similar issues can arise with any external system that your development team doesn't + * control, not just legacy systems. * *

Solution Isolate the different subsystems by placing an anti-corruption layer between them. - * This layer translates communications between the two systems, - * allowing one system to remain unchanged while the other can avoid compromising - * its design and technological approach. + * This layer translates communications between the two systems, allowing one system to remain + * unchanged while the other can avoid compromising its design and technological approach. */ package com.iluwatar.corruption; diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java index 9f693cef8873..4e8a17fa5d2a 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system; import com.iluwatar.corruption.system.legacy.LegacyShop; @@ -9,36 +33,34 @@ import org.springframework.stereotype.Service; /** - * The class represents an anti-corruption layer. - * The main purpose of the class is to provide a layer between the modern and legacy systems. - * The class is responsible for converting the data from one system to another - * decoupling the systems to each other + * The class represents an anti-corruption layer. The main purpose of the class is to provide a + * layer between the modern and legacy systems. The class is responsible for converting the data + * from one system to another decoupling the systems to each other * - *

It allows using one system a domain model of the other system - * without changing the domain model of the system. + *

It allows using one system a domain model of the other system without changing the domain + * model of the system. */ @Service public class AntiCorruptionLayer { - @Autowired - private LegacyShop legacyShop; - + @Autowired private LegacyShop legacyShop; /** * The method converts the order from the legacy system to the modern system. + * * @param id the id of the order * @return the order in the modern system */ public Optional findOrderInLegacySystem(String id) { - return legacyShop.findOrder(id).map(o -> - new ModernOrder( - o.getId(), - new Customer(o.getCustomer()), - new Shipment(o.getItem(), o.getQty(), o.getPrice()), - "" - ) - ); + return legacyShop + .findOrder(id) + .map( + o -> + new ModernOrder( + o.getId(), + new Customer(o.getCustomer()), + new Shipment(o.getItem(), o.getQty(), o.getPrice()), + "")); } - } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java index f7424980d9d9..e84578528be7 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system; import java.util.HashMap; @@ -5,6 +29,7 @@ /** * The class represents a data store for the modern system. + * * @param the type of the value stored in the data store */ public abstract class DataStore { @@ -20,6 +45,5 @@ public Optional get(String key) { public Optional put(String key, V value) { return Optional.ofNullable(inner.put(key, value)); - } } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java index 843385c02685..c0acd288ed0c 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java @@ -1,8 +1,30 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system; -/** - * The class represents a general exception for the shop. - */ +/** The class represents a general exception for the shop. */ public class ShopException extends Exception { public ShopException(String message) { super(message); @@ -17,9 +39,12 @@ public ShopException(String message) { * @throws ShopException the exception */ public static ShopException throwIncorrectData(String lhs, String rhs) throws ShopException { - throw new ShopException("The order is already placed but has an incorrect data:\n" - + "Incoming order: " + lhs + "\n" - + "Existing order: " + rhs); + throw new ShopException( + "The order is already placed but has an incorrect data:\n" + + "Incoming order: " + + lhs + + "\n" + + "Existing order: " + + rhs); } - } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java index a114302eb294..45faa06cb26c 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java @@ -1,11 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.legacy; import lombok.AllArgsConstructor; import lombok.Data; /** - * The class represents an order in the legacy system. - * The class is used by the legacy system to store the data. + * The class represents an order in the legacy system. The class is used by the legacy system to + * store the data. */ @Data @AllArgsConstructor diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyShop.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyShop.java index fe5a88be8f9a..b74eb1c29718 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyShop.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyShop.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.legacy; import java.util.Optional; diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java index 85d0f64d6fb8..ec1d613a7235 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java @@ -1,13 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.legacy; import com.iluwatar.corruption.system.DataStore; import org.springframework.stereotype.Service; /** - * The class represents a data store for the legacy system. - * The class is used by the legacy system to store the data. + * The class represents a data store for the legacy system. The class is used by the legacy system + * to store the data. */ @Service -public class LegacyStore extends DataStore { -} - +public class LegacyStore extends DataStore {} diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java index d76792c48fd9..130f36d39674 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java @@ -1,11 +1,33 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.modern; import lombok.AllArgsConstructor; import lombok.Data; -/** - * The class represents a customer in the modern system. - */ +/** The class represents a customer in the modern system. */ @Data @AllArgsConstructor public class Customer { diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java index 6a561f361a3e..7b62985015d6 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java @@ -1,11 +1,33 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.modern; import lombok.AllArgsConstructor; import lombok.Data; -/** - * The class represents an order in the modern system. - */ +/** The class represents an order in the modern system. */ @Data @AllArgsConstructor public class ModernOrder { @@ -15,6 +37,4 @@ public class ModernOrder { private Shipment shipment; private String extra; - - } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java index 9cb5898684d6..24080abe1533 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.modern; import com.iluwatar.corruption.system.AntiCorruptionLayer; @@ -7,20 +31,18 @@ import org.springframework.stereotype.Service; /** - * The class represents a modern shop system. - * The main purpose of the class is to place orders and find orders. + * The class represents a modern shop system. The main purpose of the class is to place orders and + * find orders. */ @Service public class ModernShop { - @Autowired - private ModernStore store; + @Autowired private ModernStore store; - @Autowired - private AntiCorruptionLayer acl; + @Autowired private AntiCorruptionLayer acl; /** - * Places the order in the modern system. - * If the order is already present in the legacy system, then no need to place it again. + * Places the order in the modern system. If the order is already present in the legacy system, + * then no need to place it again. */ public void placeOrder(ModernOrder order) throws ShopException { @@ -38,9 +60,7 @@ public void placeOrder(ModernOrder order) throws ShopException { } } - /** - * Finds the order in the modern system. - */ + /** Finds the order in the modern system. */ public Optional findOrder(String orderId) { return store.get(orderId); } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java index 8ccc00a958d6..4fb3952fae5e 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java @@ -1,12 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.modern; import com.iluwatar.corruption.system.DataStore; import org.springframework.stereotype.Service; -/** - * The class represents a data store for the modern system. - */ +/** The class represents a data store for the modern system. */ @Service -public class ModernStore extends DataStore { -} - +public class ModernStore extends DataStore {} diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java index d4570f47a981..085a3921ceeb 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java @@ -1,11 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system.modern; import lombok.AllArgsConstructor; import lombok.Data; /** - * The class represents a shipment in the modern system. - * The class is used by the modern system to store the data. + * The class represents a shipment in the modern system. The class is used by the modern system to + * store the data. */ @Data @AllArgsConstructor diff --git a/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java b/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java index 0883c8511f6b..ee46d124eee6 100644 --- a/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java +++ b/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java @@ -1,87 +1,96 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.corruption.system; +import static org.junit.jupiter.api.Assertions.*; + import com.iluwatar.corruption.system.legacy.LegacyOrder; import com.iluwatar.corruption.system.legacy.LegacyShop; import com.iluwatar.corruption.system.modern.Customer; import com.iluwatar.corruption.system.modern.ModernOrder; import com.iluwatar.corruption.system.modern.ModernShop; import com.iluwatar.corruption.system.modern.Shipment; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.Optional; +import org.springframework.test.context.junit.jupiter.SpringExtension; -import static org.junit.jupiter.api.Assertions.*; - -@RunWith(SpringRunner.class) +@ExtendWith(SpringExtension.class) @SpringBootTest public class AntiCorruptionLayerTest { - @Autowired - private LegacyShop legacyShop; - - @Autowired - private ModernShop modernShop; - - - /** - * Test the anti-corruption layer. - * Main intention is to demonstrate how the anti-corruption layer works. - *

- * The 2 shops (modern and legacy) should operate independently and in the same time synchronize the data. - * To avoid corrupting the domain models of the 2 shops, we use an anti-corruption layer - * that transforms one model to another under the hood. - * - */ - @Test - public void antiCorruptionLayerTest() throws ShopException { - - // a new order comes to the legacy shop. - LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1); - // place the order in the legacy shop. - legacyShop.placeOrder(legacyOrder); - // the order is placed as usual since there is no other orders with the id in the both systems. - Optional legacyOrderWithIdOne = legacyShop.findOrder("1"); - assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne); - - // a new order (or maybe just the same order) appears in the modern shop. - ModernOrder modernOrder = new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 1, 1), ""); - - // the system places it, but it checks if there is an order with the same id in the legacy shop. - modernShop.placeOrder(modernOrder); - - Optional modernOrderWithIdOne = modernShop.findOrder("1"); - // there is no new order placed since there is already an order with the same id in the legacy shop. - assertTrue(modernOrderWithIdOne.isEmpty()); - - } - /** - * Test the anti-corruption layer. - * Main intention is to demonstrate how the anti-corruption layer works. - *

- * This test tests the anti-corruption layer from the rule the orders should be the same in the both systems. - * - */ - @Test(expected = ShopException.class) - public void antiCorruptionLayerWithExTest() throws ShopException { - - // a new order comes to the legacy shop. - LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1); - // place the order in the legacy shop. - legacyShop.placeOrder(legacyOrder); - // the order is placed as usual since there is no other orders with the id in the both systems. - Optional legacyOrderWithIdOne = legacyShop.findOrder("1"); - assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne); - - // a new order but with the same id and different data appears in the modern shop - ModernOrder modernOrder = new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 10, 1), ""); - - // the system rejects the order since there are 2 orders with contradiction there. - modernShop.placeOrder(modernOrder); - - - } -} \ No newline at end of file + @Autowired private LegacyShop legacyShop; + + @Autowired private ModernShop modernShop; + + /** + * Test the anti-corruption layer. Main intention is to demonstrate how the anti-corruption layer + * works. The 2 shops (modern and legacy) should operate independently and in the same time + * synchronize the data. + */ + @Test + public void antiCorruptionLayerTest() throws ShopException { + // a new order comes to the legacy shop. + LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1); + // place the order in the legacy shop. + legacyShop.placeOrder(legacyOrder); + // the order is placed as usual since there is no other orders with the id in the both systems. + Optional legacyOrderWithIdOne = legacyShop.findOrder("1"); + assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne); + + // a new order (or maybe just the same order) appears in the modern shop + ModernOrder modernOrder = + new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 1, 1), ""); + // the system places it, but it checks if there is an order with the same id in the legacy shop. + modernShop.placeOrder(modernOrder); + + Optional modernOrderWithIdOne = modernShop.findOrder("1"); + // there is no new order placed since there is already an order with the same id in the legacy + // shop. + assertTrue(modernOrderWithIdOne.isEmpty()); + } + + /** + * Test the anti-corruption layer when a conflict occurs between systems. This test ensures that + * an exception is thrown when conflicting orders are placed. + */ + @Test + public void antiCorruptionLayerWithExTest() throws ShopException { + // a new order comes to the legacy shop. + LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1); + // place the order in the legacy shop. + legacyShop.placeOrder(legacyOrder); + // the order is placed as usual since there is no other orders with the id in the both systems. + Optional legacyOrderWithIdOne = legacyShop.findOrder("1"); + assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne); + // a new order but with the same id and different data appears in the modern shop + ModernOrder modernOrder = + new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 10, 1), ""); + // the system rejects the order since there are 2 orders with contradiction there. + assertThrows(ShopException.class, () -> modernShop.placeOrder(modernOrder)); + } +} diff --git a/api-gateway/README.md b/api-gateway/README.md deleted file mode 100644 index 40d5f4dc445e..000000000000 --- a/api-gateway/README.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -title: API Gateway -category: Architectural -language: en -tag: - - API design - - Cloud distributed - - Decoupling - - Microservices ---- - -## Intent - -The API Gateway design pattern aims to provide a unified interface to a set of microservices. It acts as a single entry point for clients, routing requests to the appropriate microservices and aggregating results, thereby simplifying the client-side code. - -## Also known as - -* Backend for Frontends (BFF) - -## Explanation - -With the Microservices pattern, a client may need data from multiple different microservices. If the -client called each microservice directly, that could contribute to longer load times, since the -client would have to make a network request for each microservice called. Moreover, having the -client call each microservice directly ties the client to that microservice - if the internal -implementations of the microservices change (for example, if two microservices are combined sometime -in the future) or if the location (host and port) of a microservice changes, then every client that -makes use of those microservices must be updated. - -The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway -pattern, an additional entity (the API Gateway) is placed between the client and the microservices. -The job of the API Gateway is to aggregate the calls to the microservices. Rather than the client -calling each microservice individually, the client calls the API Gateway a single time. The API -Gateway then calls each of the microservices that the client needs. - -Real world example - -> We are implementing microservices and API Gateway pattern for an e-commerce site. In this system -> the API Gateway makes calls to the Image and Price microservices. - -In plain words - -> For a system implemented using microservices architecture, API Gateway is the single entry point -> that aggregates the calls to the individual microservices. - -Wikipedia says - -> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling -> and security policies, passes requests to the back-end service and then passes the response back -> to the requester. A gateway often includes a transformation engine to orchestrate and modify the -> requests and responses on the fly. A gateway can also provide functionality such as collecting -> analytics data and providing caching. The gateway can provide functionality to support -> authentication, authorization, security, audit and regulatory compliance. - -**Programmatic Example** - -This implementation shows what the API Gateway pattern could look like for an e-commerce site. The -`ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and -`PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price -information and an image of a product, so the `ApiGateway` calls both of the microservices and -aggregates the data in the `DesktopProduct` model. However, mobile users only see price information; -they do not see a product image. For mobile users, the `ApiGateway` only retrieves price -information, which it uses to populate the `MobileProduct`. - -Here's the Image microservice implementation. - -```java -public interface ImageClient { - 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(); - - try { - var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); - return httpResponse.body(); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - - return null; - } -} -``` - -Here's the Price microservice implementation. - -```java -public interface PriceClient { - 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(); - - try { - var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); - return httpResponse.body(); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - - return null; - } -} -``` - -Here we can see how API Gateway maps the requests to the microservices. - -```java -public class ApiGateway { - - @Resource - private ImageClient imageClient; - - @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 = "/mobile", method = RequestMethod.GET) - public MobileProduct getProductMobile() { - var mobileProduct = new MobileProduct(); - mobileProduct.setPrice(priceClient.getPrice()); - return mobileProduct; - } -} -``` - -## Class diagram - -![alt text](./etc/api-gateway.png "API Gateway") - -## Applicability - -* When building a microservices architecture, and there's a need to abstract the complexity of microservices from the client. -* When multiple microservices need to be consumed in a single request. -* For authentication, authorization, and security enforcement at a single point. -* To optimize communication between clients and services, especially in a cloud environment. - -## Consequences - -Benefits: - -* Decouples client from microservices, allowing services to evolve independently. -* Simplifies client by aggregating requests to multiple services. -* Centralized location for cross-cutting concerns like security, logging, and rate limiting. -* Potential for performance optimizations like caching and request compression. - -Trade-offs: - -* Introduces a single point of failure, although this can be mitigated with high availability setups. -* Can become a bottleneck if not properly scaled. -* Adds complexity in terms of deployment and management. - -## Known uses - -* E-commerce platforms where multiple services (product info, pricing, inventory) are aggregated for a single view. -* Mobile applications that consume various backend services but require a simplified interface for ease of use. -* Cloud-native applications that leverage multiple microservices architectures. - -## Related patterns - -* [Aggregator Microservice](../aggregator-microservices/README.md) - The API Gateway pattern is often used in conjunction with the Aggregator Microservice pattern to provide a unified interface to a set of microservices. -* [Proxy](../proxy/README.md) - The API Gateway pattern is a specialized form of the Proxy pattern, where the gateway acts as a single entry point for clients, routing requests to the appropriate microservices and aggregating results. -* [Circuit Breaker](../circuit-breaker/README.md) - API Gateways can use the Circuit Breaker pattern to prevent cascading failures when calling multiple microservices. - -## Tutorials - -* [Exploring the New Spring Cloud Gateway](https://www.baeldung.com/spring-cloud-gateway) -* [Spring Cloud - Gateway](https://www.tutorialspoint.com/spring_cloud/spring_cloud_gateway.htm) -* [Getting Started With Spring Cloud Gateway](https://dzone.com/articles/getting-started-with-spring-cloud-gateway) - -## Credits - -* [microservices.io - API Gateway](http://microservices.io/patterns/apigateway.html) -* [NGINX - Building Microservices: Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) -* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=ac7b6a57f866ac006a309d9086e8cfbd) -* [Building Microservices: Designing Fine-Grained Systems](https://www.amazon.com/gp/product/1491950358/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1491950358&linkId=4c95ca9831e05e3f0dadb08841d77bf1) -* [Designing Data-Intensive Applications](https://amzn.to/3PfRk7Y) -* [Cloud Native Patterns: Designing change-tolerant software](https://amzn.to/3uV12WN) diff --git a/arrange-act-assert/README.md b/arrange-act-assert/README.md index 9c6d614b9df3..584d8601a23e 100644 --- a/arrange-act-assert/README.md +++ b/arrange-act-assert/README.md @@ -1,146 +1,145 @@ --- -title: Arrange/Act/Assert +title: "Arrange/Act/Assert Pattern in Java: Enhance Testing Clarity and Simplicity" +shortTitle: Arrange/Act/Assert +description: "Learn how to use the Arrange/Act/Assert pattern to structure your unit tests in Java. Improve readability and maintainability of your code with clear testing phases." category: Testing language: en tag: - - Idiom + - Code simplification + - Isolation - Testing --- ## Also known as -Given/When/Then +* Given/When/Then -## Intent +## Intent of Arrange/Act/Assert Design Pattern -Arrange/Act/Assert (AAA) is a pattern for organizing unit tests. -It breaks tests down into three clear and distinct steps: +The Arrange/Act/Assert pattern is essential in unit testing in Java. This testing method structures unit tests clearly by dividing them into three distinct sections: setup (Arrange), execution (Act), and verification (Assert). -1. Arrange: Perform the setup and initialization required for the test. -2. Act: Take action(s) required for the test. -3. Assert: Verify the outcome(s) of the test. +## Detailed Explanation of Arrange/Act/Assert Pattern with Real-World Examples -## Explanation +Real-world example -This pattern has several significant benefits. It creates a clear separation between a test's -setup, operations, and results. This structure makes the code easier to read and understand. If -you place the steps in order and format your code to separate them, you can scan a test and -quickly comprehend what it does. +> Imagine you are organizing a small event. To ensure everything runs smoothly, you follow a pattern similar to Arrange/Act/Assert: +> +> 1. **Arrange**: You set up the venue, prepare the guest list, arrange seating, and organize the catering. +> 2. **Act**: You conduct the event according to the plan, welcoming guests, serving food, and following the schedule. +> 3. **Assert**: After the event, you evaluate its success by checking guest feedback, ensuring all tasks were completed, and reviewing if everything went as planned. +> +> This clear separation of preparation, execution, and evaluation helps ensure the event is well-organized and successful, mirroring the structured approach of the Arrange/Act/Assert pattern in software testing. -It also enforces a certain degree of discipline when you write your tests. You have to think -clearly about the three steps your test will perform. It makes tests more natural to write at -the same time since you already have an outline. +In plain words -Real world example +> Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy maintenance. -> We need to write comprehensive and clear unit test suite for a class. +WikiWikiWeb says -In plain words +> Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods. -> Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy -> maintenance. +Flowchart -WikiWikiWeb says +![Arrange/Act/Assert flowchart](./etc/arrange-act-assert-flowchart.png "Arrange/Act/Assert flowchart") -> Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods. +## Programmatic Example of Arrange/Act/Assert Pattern in Java -**Programmatic Example** +We need to write comprehensive and clear unit test suite for a class. Using the Arrange/Act/Assert pattern in Java testing ensures clarity. 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; - } + Cash(int amount) { + this.amount = amount; + } - void plus(int addend) { - amount += addend; - } + void plus(int addend) { + amount += addend; + } - boolean minus(int subtrahend) { - if (amount >= subtrahend) { - amount -= subtrahend; - return true; - } else { - return false; + boolean minus(int subtrahend) { + if (amount >= subtrahend) { + amount -= subtrahend; + return true; + } else { + return false; + } } - } - int count() { - return amount; - } + int count() { + return amount; + } } ``` -Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly -separated steps for each unit test. +Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly 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 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 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 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 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()); + } } ``` -## Applicability +## When to Use the Arrange/Act/Assert Pattern in Java Use Arrange/Act/Assert pattern when -* Unit testing, especially within the context of TDD and BDD +* Unit testing, especially within the context of TDD and BDD * Anywhere clarity and structure are needed in test cases -## Known uses +## Real-World Applications of Arrange/Act/Assert Pattern in Java -* Widely adopted in software projects using TDD and BDD methodologies. +* This pattern is particularly useful when practicing TDD and/or BDD methodologies in Java. * Utilized in various programming languages and testing frameworks, such as JUnit (Java), NUnit (.NET), and xUnit frameworks. -## Consequences +## Benefits and Trade-offs of Arrange/Act/Assert Pattern Benefits: @@ -153,16 +152,16 @@ Trade-offs: * May introduce redundancy in tests, as similar arrangements may be repeated across tests. * Some complex tests might not fit neatly into this structure, requiring additional context or setup outside these three phases. -## Related patterns +## Related Java Design Patterns * [Page Object](https://java-design-patterns.com/patterns/page-object/): A pattern for organizing UI tests that can be used in conjunction with Arrange/Act/Assert. -## Credits +## References and Credits -* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) -* [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/) -* [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html) -* [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f7e8dd50d720c9b63dbf) -* [Unit Testing Principles, Practices, and Patterns](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cf22a63c3e4758ae08aa0a0cc35) -* [Test Driven Development: By Example](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b84ca5087472ef0e05) * [The Art of Unit Testing: with examples in C#](https://amzn.to/49IbdwO) +* [Test Driven Development: By Example](https://amzn.to/3wEwKbF) +* [Unit Testing Principles, Practices, and Patterns: Effective testing styles, patterns, and reliable automation for unit testing, mocking, and integration testing with examples in C#](https://amzn.to/4ayjpiM) +* [xUnit Test Patterns: Refactoring Test Code](https://amzn.to/4dHGDpm) +* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) +* [Bill Wake: 3A – Arrange, Act, Assert (NCrunch)](https://xp123.com/articles/3a-arrange-act-assert/) +* [GivenWhenThen (Martin Fowler)](https://martinfowler.com/bliki/GivenWhenThen.html) diff --git a/arrange-act-assert/etc/arrange-act-assert-flowchart.png b/arrange-act-assert/etc/arrange-act-assert-flowchart.png new file mode 100644 index 000000000000..8b7615352523 Binary files /dev/null and b/arrange-act-assert/etc/arrange-act-assert-flowchart.png differ diff --git a/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java b/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java index c3f5e6fe43e1..0c31b1f89f44 100644 --- a/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java +++ b/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java @@ -35,12 +35,12 @@ public class Cash { private int amount; - //plus + // plus void plus(int addend) { amount += addend; } - //minus + // minus boolean minus(int subtrahend) { if (amount >= subtrahend) { amount -= subtrahend; @@ -50,7 +50,7 @@ boolean minus(int subtrahend) { } } - //count + // count int count() { return amount; } diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java index b771cb6e7dee..ebb261277080 100644 --- a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java +++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java @@ -35,8 +35,11 @@ * tests, so they're easier to read, maintain and enhance. * *

It breaks tests down into three clear and distinct steps: + * *

1. Arrange: Perform the setup and initialization required for the test. + * *

2. Act: Take action(s) required for the test. + * *

3. Assert: Verify the outcome(s) of the test. * *

This pattern has several significant benefits. It creates a clear separation between a test's @@ -48,53 +51,52 @@ * clearly about the three steps your test will perform. But it makes tests more natural to write at * the same time since you already have an outline. * - *

In ({@link CashAAATest}) we have four test methods. Each of them has only one reason to - * change and one reason to fail. In a large and complicated code base, tests that honor the single + *

In ({@link CashAAATest}) we have four test methods. Each of them has only one reason to change + * and one reason to fail. In a large and complicated code base, tests that honor the single * responsibility principle are much easier to troubleshoot. */ - class CashAAATest { @Test void testPlus() { - //Arrange + // Arrange var cash = new Cash(3); - //Act + // Act cash.plus(4); - //Assert + // Assert assertEquals(7, cash.count()); } @Test void testMinus() { - //Arrange + // Arrange var cash = new Cash(8); - //Act + // Act var result = cash.minus(5); - //Assert + // Assert assertTrue(result); assertEquals(3, cash.count()); } @Test void testInsufficientMinus() { - //Arrange + // Arrange var cash = new Cash(1); - //Act + // Act var result = cash.minus(6); - //Assert + // Assert assertFalse(result); assertEquals(1, cash.count()); } @Test void testUpdate() { - //Arrange + // Arrange var cash = new Cash(5); - //Act + // Act cash.plus(6); var result = cash.minus(3); - //Assert + // Assert assertTrue(result); assertEquals(8, cash.count()); } diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java index 142fd623abb8..5756822516b8 100644 --- a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java +++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java @@ -37,23 +37,22 @@ * single responsibility principle. If this test method failed after a small code change, it might * take some digging to discover why. */ - class CashAntiAAATest { @Test void testCash() { - //initialize + // initialize var cash = new Cash(3); - //test plus + // test plus cash.plus(4); assertEquals(7, cash.count()); - //test minus + // test minus cash = new Cash(8); assertTrue(cash.minus(5)); assertEquals(3, cash.count()); assertFalse(cash.minus(6)); assertEquals(3, cash.count()); - //test update + // test update cash.plus(5); assertTrue(cash.minus(5)); assertEquals(3, cash.count()); diff --git a/async-method-invocation/README.md b/async-method-invocation/README.md index 950b958610e5..519152e1268c 100644 --- a/async-method-invocation/README.md +++ b/async-method-invocation/README.md @@ -1,167 +1,171 @@ --- -title: Async Method Invocation +title: "Async Method Invocation Pattern in Java: Elevate Performance with Asynchronous Programming" +shortTitle: Async Method Invocation +description: "Learn about the Async Method Invocation pattern in Java for asynchronous method calls, enhancing concurrency, scalability, and responsiveness in your applications. Explore real-world examples and code implementations." category: Concurrency language: en tag: - - Asynchronous - - Reactive - - Scalability + - Asynchronous + - Decoupling + - Reactive + - Scalability + - Thread management --- -## Intent - -Asynchronous method invocation is a pattern where the calling thread -is not blocked while waiting results of tasks. The pattern provides parallel -processing of multiple independent tasks and retrieving the results via -callbacks or waiting until everything is done. - ## Also known as * Asynchronous Procedure Call -## Explanation +## Intent of Async Method Invocation Design Pattern + +The Async Method Invocation pattern is designed to enhance concurrency by allowing methods to be called asynchronously. This pattern helps in executing parallel tasks, reducing wait times, and improving system throughput. -Real world example +## Detailed Explanation of Async Method Invocation Pattern with Real-World Examples -> Launching space rockets is an exciting business. The mission command gives an order to launch and -> after some undetermined time, the rocket either launches successfully or fails miserably. +Real-world example + +> Asynchronous method invocation enables non-blocking operations, allowing multiple processes to run concurrently. This pattern is particularly useful in applications requiring high scalability and performance, such as web servers and microservices. +> +> In the context of space rockets, an analogous example of the Async Method Invocation pattern can be seen in the communication between the mission control center and the onboard systems of the rocket. When mission control sends a command to the rocket to adjust its trajectory or perform a system check, they do not wait idly for the rocket to complete the task and report back. Instead, mission control continues to monitor other aspects of the mission and manage different tasks. The rocket executes the command asynchronously and sends a status update or result back to mission control once the operation is complete. This allows mission control to efficiently manage multiple concurrent operations without being blocked by any single task, similar to how asynchronous method invocation works in software systems. In plain words -> Asynchronous method invocation starts task processing and returns immediately before the task is -> ready. The results of the task processing are returned to the caller later. +> Asynchronous method invocation starts task processing and returns immediately before the task is ready. The results of the task processing are returned to the caller later. Wikipedia says -> In multithreaded computer programming, asynchronous method invocation (AMI), also known as -> asynchronous method calls or the asynchronous pattern is a design pattern in which the call site -> is not blocked while waiting for the called code to finish. Instead, the calling thread is -> notified when the reply arrives. Polling for a reply is an undesired option. +> In multithreaded computer programming, asynchronous method invocation (AMI), also known as asynchronous method calls or the asynchronous pattern is a design pattern in which the call site is not blocked while waiting for the called code to finish. Instead, the calling thread is notified when the reply arrives. Polling for a reply is an undesired option. + +Sequence diagram + +![Async Method Invocation sequence diagram](./etc/async-method-invocation-sequence-diagram.png "Async Method Invocation sequence diagram") -**Programmatic Example** +## Programmatic Example of Async Method Invocation Pattern in Java + +Consider a scenario where multiple tasks need to be executed simultaneously. Using the Async Method Invocation pattern, you can initiate these tasks without waiting for each to complete, thus optimizing resource usage and reducing latency. In this example, we are launching space rockets and deploying lunar rovers. -The application demonstrates the async method invocation pattern. The key parts of the pattern are -`AsyncResult` which is an intermediate container for an asynchronously evaluated value, -`AsyncCallback` which can be provided to be executed on task completion and `AsyncExecutor` that -manages the execution of the async tasks. +The application demonstrates the async method invocation pattern. The key parts of the pattern are`AsyncResult` which is an intermediate container for an asynchronously evaluated value, `AsyncCallback` which can be provided to be executed on task completion and `AsyncExecutor` that manages the execution of the async tasks. ```java public interface AsyncResult { - boolean isCompleted(); - T getValue() throws ExecutionException; - void await() throws InterruptedException; + boolean isCompleted(); + + T getValue() throws ExecutionException; + + void await() throws InterruptedException; } ``` ```java public interface AsyncCallback { void onComplete(T value); + void onError(Exception ex); } ``` ```java public interface AsyncExecutor { - AsyncResult startProcess(Callable task); - AsyncResult startProcess(Callable task, AsyncCallback callback); - T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; + AsyncResult startProcess(Callable task); + + AsyncResult startProcess(Callable task, AsyncCallback callback); + + T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; } ``` -`ThreadAsyncExecutor` is an implementation of `AsyncExecutor`. Some of its key parts are highlighted -next. +`ThreadAsyncExecutor` is an implementation of `AsyncExecutor`. Some of its key parts are highlighted next. ```java public class ThreadAsyncExecutor implements AsyncExecutor { - @Override - public AsyncResult startProcess(Callable task) { - return startProcess(task, null); - } - - @Override - public AsyncResult startProcess(Callable task, AsyncCallback 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 endProcess(AsyncResult asyncResult) - throws ExecutionException, InterruptedException { - if (!asyncResult.isCompleted()) { - asyncResult.await(); + @Override + public AsyncResult startProcess(Callable task) { + return startProcess(task, null); + } + + @Override + public AsyncResult startProcess(Callable task, AsyncCallback 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 endProcess(AsyncResult asyncResult) + throws ExecutionException, InterruptedException { + if (!asyncResult.isCompleted()) { + asyncResult.await(); + } + return asyncResult.getValue(); } - return asyncResult.getValue(); - } } ``` 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(); - - // start few async tasks with varying processing times, two last with callback handlers - final var asyncResult1 = executor.startProcess(lazyval(10, 500)); - 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")); - - // 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(); - - // 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"); + public static void main(String[] args) throws Exception { + // 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)); + 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")); + + // 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(); + + // log the results of the tasks, callbacks log immediately when complete + log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result1)); + log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result2)); + log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result3)); } ``` Here's the program console output. -```java -21:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - Space rocket 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 launched successfully -21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover -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 launch complete -21:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launch complete +``` +21:47:08.227[executor-2]INFO com.iluwatar.async.method.invocation.App-Space rocket 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 launched successfully +21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover +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 launch complete +21:47:08.618[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launch complete ``` -# Class diagram - -![alt text](./etc/async-method-invocation.urm.png "Async Method Invocation") +## When to Use the Async Method Invocation Pattern in Java -## Applicability +This pattern is ideal for applications needing to manage multiple parallel tasks efficiently. It is commonly used in scenarios such as handling background processes, improving user interface responsiveness, and managing asynchronous data processing. Use the async method invocation pattern when @@ -170,7 +174,9 @@ Use the async method invocation pattern when * In GUI applications to prevent freezing or unresponsiveness during long-running tasks. * In web applications for non-blocking IO operations. -## Known Uses +## Real-World Applications of Async Method Invocation Pattern in Java + +Many modern applications leverage the Async Method Invocation pattern, including web servers handling concurrent requests, microservices architectures, and systems requiring intensive background processing. * Web servers handling HTTP requests asynchronously to improve throughput and reduce latency. * Desktop and mobile applications using background threads to perform time-consuming operations without blocking the user interface. @@ -180,7 +186,9 @@ Use the async method invocation pattern when * [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) * [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx) -## Consequences +## Benefits and Trade-offs of Async Method Invocation Pattern + +While this pattern offers significant performance benefits, it also introduces complexity in error handling and resource management. Proper implementation is essential to avoid potential pitfalls such as race conditions and deadlocks. Benefits: @@ -194,13 +202,16 @@ Trade-offs: * Resource Management: Requires careful management of threads or execution contexts, which can introduce overhead and potential resource exhaustion issues. * Error Handling: Asynchronous operations can make error handling more complex, as exceptions may occur in different threads or at different times. -Related Patterns: +## Related Java Design Patterns + +The Async Method Invocation pattern often works well with other design patterns like the Command Pattern for encapsulating requests, the Observer Pattern for event handling, and the Promise Pattern for managing asynchronous results. * [Command](https://java-design-patterns.com/patterns/command/): Asynchronous method invocation can be used to implement the Command pattern, where commands are executed asynchronously. * [Observer](https://java-design-patterns.com/patterns/observer/): Asynchronous method invocation can be used to notify observers asynchronously when a subject's state changes. * [Promise](https://java-design-patterns.com/patterns/promise/): The AsyncResult interface can be considered a form of Promise, representing a value that may not be available yet. -## Credits +## References and Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3Ti1N4f) +* [Effective Java](https://amzn.to/4cGk2Jz) * [Java Concurrency in Practice](https://amzn.to/4ab97VU) diff --git a/async-method-invocation/etc/async-method-invocation-sequence-diagram.png b/async-method-invocation/etc/async-method-invocation-sequence-diagram.png new file mode 100644 index 000000000000..28420d783379 Binary files /dev/null and b/async-method-invocation/etc/async-method-invocation-sequence-diagram.png differ diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml index 52a03369f47d..d9ddd918c8e9 100644 --- a/async-method-invocation/pom.xml +++ b/async-method-invocation/pom.xml @@ -34,6 +34,14 @@ async-method-invocation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index 01fd8f1c681a..ec3beed3be4d 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -30,15 +30,15 @@ /** * In this example, we are launching space rockets and deploying lunar rovers. * - *

The application demonstrates the async method invocation pattern. The key parts of the - * pattern are AsyncResult which is an intermediate container for an asynchronously - * evaluated value, AsyncCallback which can be provided to be executed on task - * completion and AsyncExecutor that manages the execution of the async tasks. + *

The application demonstrates the async method invocation pattern. The key parts of the pattern + * are AsyncResult which is an intermediate container for an asynchronously evaluated + * value, AsyncCallback which can be provided to be executed on task completion and + * AsyncExecutor that manages the execution of the async tasks. * - *

The main method shows example flow of async invocations. The main thread starts multiple - * tasks with variable durations and then continues its own work. When the main thread has done it's - * job it collects the results of the async tasks. Two of the tasks are handled with callbacks, - * meaning the callbacks are executed immediately when the tasks complete. + *

The main method shows example flow of async invocations. The main thread starts multiple tasks + * with variable durations and then continues its own work. When the main thread has done it's job + * it collects the results of the async tasks. Two of the tasks are handled with callbacks, meaning + * the callbacks are executed immediately when the tasks complete. * *

Noteworthy difference of thread usage between the async results and callbacks is that the * async results are collected in the main thread but the callbacks are executed within the worker @@ -62,10 +62,7 @@ public class App { private static final String ROCKET_LAUNCH_LOG_PATTERN = "Space rocket <%s> launched successfully"; - /** - * Program entry point. - */ - + /** Program entry point. */ public static void main(String[] args) throws Exception { // construct a new executor that will run async tasks var executor = new ThreadAsyncExecutor(); @@ -74,8 +71,8 @@ public static void main(String[] args) throws Exception { final var asyncResult1 = executor.startProcess(lazyval(10, 500)); 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 asyncResult4 = + executor.startProcess(lazyval(20, 400), callback("Deploying lunar rover")); final var asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover")); @@ -99,7 +96,7 @@ public static void main(String[] args) throws Exception { /** * Creates a callable that lazily evaluates to given value with artificial delay. * - * @param value value to evaluate + * @param value value to evaluate * @param delayMillis artificial delay in milliseconds * @return new callable for lazy evaluation */ diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java index fcea6d07190d..3bae90830098 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -27,9 +27,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -/** - * AsyncExecutor interface. - */ +/** AsyncExecutor interface. */ public interface AsyncExecutor { /** @@ -44,7 +42,7 @@ public interface AsyncExecutor { * Starts processing of an async task. Returns immediately with async result. Executes callback * when the task is completed. * - * @param task task to be executed asynchronously + * @param task task to be executed asynchronously * @param callback callback to be executed on task completion * @return async result for the task */ @@ -56,7 +54,7 @@ public interface AsyncExecutor { * * @param asyncResult async result of a task * @return evaluated value of the completed task - * @throws ExecutionException if execution has failed, containing the root cause + * @throws ExecutionException if execution has failed, containing the root cause * @throws InterruptedException if the execution is interrupted */ T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java index d71cf0defcd1..3eebdc4e773d 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -44,7 +44,7 @@ public interface AsyncResult { * Gets the value of completed async task. * * @return evaluated value or throws ExecutionException if execution has failed - * @throws ExecutionException if execution has failed, containing the root cause + * @throws ExecutionException if execution has failed, containing the root cause * @throws IllegalStateException if execution is not completed */ T getValue() throws ExecutionException; diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index f4a50e0d6c30..a1261f34184c 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -24,19 +24,14 @@ */ package com.iluwatar.async.method.invocation; -import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; -/** - * Implementation of async executor that creates a new thread for every task. - */ +/** Implementation of async executor that creates a new thread for every task. */ public class ThreadAsyncExecutor implements AsyncExecutor { - /** - * Index for thread naming. - */ + /** Index for thread naming. */ private final AtomicInteger idx = new AtomicInteger(0); @Override @@ -47,19 +42,22 @@ public AsyncResult startProcess(Callable task) { @Override public AsyncResult startProcess(Callable task, AsyncCallback callback) { var result = new CompletableResult<>(callback); - new Thread(() -> { - try { - result.setValue(task.call()); - } catch (Exception ex) { - result.setException(ex); - } - }, "executor-" + idx.incrementAndGet()).start(); + new Thread( + () -> { + try { + result.setValue(task.call()); + } catch (Exception ex) { + result.setException(ex); + } + }, + "executor-" + idx.incrementAndGet()) + .start(); return result; } @Override - public T endProcess(AsyncResult asyncResult) throws ExecutionException, - InterruptedException { + public T endProcess(AsyncResult asyncResult) + throws ExecutionException, InterruptedException { if (!asyncResult.isCompleted()) { asyncResult.await(); } diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java index f31c5549bdd1..d58a3f6b5c30 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java @@ -24,26 +24,22 @@ */ package com.iluwatar.async.method.invocation; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java index 32d591b2bb44..d6540ce77309 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java @@ -33,7 +33,6 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; -import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeEach; @@ -43,50 +42,43 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Date: 12/6/15 - 10:49 AM - * - * @author Jeroen Meulemeester - */ +/** ThreadAsyncExecutorTest */ class ThreadAsyncExecutorTest { - @Captor - private ArgumentCaptor exceptionCaptor; + @Captor private ArgumentCaptor exceptionCaptor; - @Mock - private Callable task; + @Mock private Callable task; - @Mock - private AsyncCallback callback; + @Mock private AsyncCallback callback; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } - /** - * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} - */ + /** Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} */ @Test void testSuccessfulTaskWithoutCallback() { - assertTimeout(ofMillis(3000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - - final var result = new Object(); - when(task.call()).thenReturn(result); - - final var asyncResult = executor.startProcess(task); - assertNotNull(asyncResult); - asyncResult.await(); // Prevent timing issues, and wait until the result is available - assertTrue(asyncResult.isCompleted()); - - // Our task should only execute once ... - verify(task, times(1)).call(); - - // ... and the result should be exactly the same object - assertSame(result, asyncResult.getValue()); - }); + assertTimeout( + ofMillis(3000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + + final var result = new Object(); + when(task.call()).thenReturn(result); + + final var asyncResult = executor.startProcess(task); + assertNotNull(asyncResult); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + // Our task should only execute once ... + verify(task, times(1)).call(); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + }); } /** @@ -95,28 +87,30 @@ void testSuccessfulTaskWithoutCallback() { */ @Test void testSuccessfulTaskWithCallback() { - assertTimeout(ofMillis(3000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - - final var result = new Object(); - when(task.call()).thenReturn(result); - - final var asyncResult = executor.startProcess(task, callback); - assertNotNull(asyncResult); - asyncResult.await(); // Prevent timing issues, and wait until the result is available - assertTrue(asyncResult.isCompleted()); - - // Our task should only execute once ... - verify(task, times(1)).call(); - - // ... same for the callback, we expect our object - verify(callback, times(1)).onComplete(eq(result)); - verify(callback, times(0)).onError(exceptionCaptor.capture()); - - // ... and the result should be exactly the same object - assertSame(result, asyncResult.getValue()); - }); + assertTimeout( + ofMillis(3000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + + final var result = new Object(); + when(task.call()).thenReturn(result); + + final var asyncResult = executor.startProcess(task, callback); + assertNotNull(asyncResult); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + // Our task should only execute once ... + verify(task, times(1)).call(); + + // ... same for the callback, we expect our object + verify(callback, times(1)).onComplete(eq(result)); + verify(callback, times(0)).onError(exceptionCaptor.capture()); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + }); } /** @@ -125,38 +119,43 @@ void testSuccessfulTaskWithCallback() { */ @Test void testLongRunningTaskWithoutCallback() { - assertTimeout(ofMillis(5000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - - final var result = new Object(); - when(task.call()).thenAnswer(i -> { - Thread.sleep(1500); - return result; - }); - - final var asyncResult = executor.startProcess(task); - assertNotNull(asyncResult); - assertFalse(asyncResult.isCompleted()); - - try { - asyncResult.getValue(); - fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); - } catch (IllegalStateException e) { - assertNotNull(e.getMessage()); - } - - // Our task should only execute once, but it can take a while ... - verify(task, timeout(3000).times(1)).call(); - - // Prevent timing issues, and wait until the result is available - asyncResult.await(); - assertTrue(asyncResult.isCompleted()); - verifyNoMoreInteractions(task); - - // ... and the result should be exactly the same object - assertSame(result, asyncResult.getValue()); - }); + assertTimeout( + ofMillis(5000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + + final var result = new Object(); + when(task.call()) + .thenAnswer( + i -> { + Thread.sleep(1500); + return result; + }); + + final var asyncResult = executor.startProcess(task); + assertNotNull(asyncResult); + assertFalse(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail( + "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); + } catch (IllegalStateException e) { + assertNotNull(e.getMessage()); + } + + // Our task should only execute once, but it can take a while ... + verify(task, timeout(3000).times(1)).call(); + + // Prevent timing issues, and wait until the result is available + asyncResult.await(); + assertTrue(asyncResult.isCompleted()); + verifyNoMoreInteractions(task); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + }); } /** @@ -165,42 +164,47 @@ void testLongRunningTaskWithoutCallback() { */ @Test void testLongRunningTaskWithCallback() { - assertTimeout(ofMillis(5000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - - final var result = new Object(); - when(task.call()).thenAnswer(i -> { - Thread.sleep(1500); - return result; - }); - - final var asyncResult = executor.startProcess(task, callback); - assertNotNull(asyncResult); - assertFalse(asyncResult.isCompleted()); - - verifyNoMoreInteractions(callback); - - try { - asyncResult.getValue(); - fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); - } catch (IllegalStateException e) { - assertNotNull(e.getMessage()); - } - - // Our task should only execute once, but it can take a while ... - verify(task, timeout(3000).times(1)).call(); - verify(callback, timeout(3000).times(1)).onComplete(eq(result)); - verify(callback, times(0)).onError(isA(Exception.class)); - - // Prevent timing issues, and wait until the result is available - asyncResult.await(); - assertTrue(asyncResult.isCompleted()); - verifyNoMoreInteractions(task, callback); - - // ... and the result should be exactly the same object - assertSame(result, asyncResult.getValue()); - }); + assertTimeout( + ofMillis(5000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + + final var result = new Object(); + when(task.call()) + .thenAnswer( + i -> { + Thread.sleep(1500); + return result; + }); + + final var asyncResult = executor.startProcess(task, callback); + assertNotNull(asyncResult); + assertFalse(asyncResult.isCompleted()); + + verifyNoMoreInteractions(callback); + + try { + asyncResult.getValue(); + fail( + "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); + } catch (IllegalStateException e) { + assertNotNull(e.getMessage()); + } + + // Our task should only execute once, but it can take a while ... + verify(task, timeout(3000).times(1)).call(); + verify(callback, timeout(3000).times(1)).onComplete(eq(result)); + verify(callback, times(0)).onError(isA(Exception.class)); + + // Prevent timing issues, and wait until the result is available + asyncResult.await(); + assertTrue(asyncResult.isCompleted()); + verifyNoMoreInteractions(task, callback); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + }); } /** @@ -210,35 +214,40 @@ void testLongRunningTaskWithCallback() { */ @Test void testEndProcess() { - assertTimeout(ofMillis(5000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - - final var result = new Object(); - when(task.call()).thenAnswer(i -> { - Thread.sleep(1500); - return result; - }); - - final var asyncResult = executor.startProcess(task); - assertNotNull(asyncResult); - assertFalse(asyncResult.isCompleted()); - - try { - asyncResult.getValue(); - fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); - } catch (IllegalStateException e) { - assertNotNull(e.getMessage()); - } - - assertSame(result, executor.endProcess(asyncResult)); - verify(task, times(1)).call(); - assertTrue(asyncResult.isCompleted()); - - // Calling end process a second time while already finished should give the same result - assertSame(result, executor.endProcess(asyncResult)); - verifyNoMoreInteractions(task); - }); + assertTimeout( + ofMillis(5000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + + final var result = new Object(); + when(task.call()) + .thenAnswer( + i -> { + Thread.sleep(1500); + return result; + }); + + final var asyncResult = executor.startProcess(task); + assertNotNull(asyncResult); + assertFalse(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail( + "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); + } catch (IllegalStateException e) { + assertNotNull(e.getMessage()); + } + + assertSame(result, executor.endProcess(asyncResult)); + verify(task, times(1)).call(); + assertTrue(asyncResult.isCompleted()); + + // Calling end process a second time while already finished should give the same result + assertSame(result, executor.endProcess(asyncResult)); + verifyNoMoreInteractions(task); + }); } /** @@ -247,25 +256,28 @@ void testEndProcess() { */ @Test void testNullTask() { - assertTimeout(ofMillis(3000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - final var asyncResult = executor.startProcess(null); - - assertNotNull(asyncResult, "The AsyncResult should not be 'null', even though the task was 'null'."); - asyncResult.await(); // Prevent timing issues, and wait until the result is available - assertTrue(asyncResult.isCompleted()); - - try { - asyncResult.getValue(); - fail("Expected ExecutionException with NPE as cause"); - } catch (final ExecutionException e) { - assertNotNull(e.getMessage()); - assertNotNull(e.getCause()); - assertEquals(NullPointerException.class, e.getCause().getClass()); - } - }); - + assertTimeout( + ofMillis(3000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + final var asyncResult = executor.startProcess(null); + + assertNotNull( + asyncResult, + "The AsyncResult should not be 'null', even though the task was 'null'."); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail("Expected ExecutionException with NPE as cause"); + } catch (final ExecutionException e) { + assertNotNull(e.getMessage()); + assertNotNull(e.getCause()); + assertEquals(NullPointerException.class, e.getCause().getClass()); + } + }); } /** @@ -274,32 +286,35 @@ void testNullTask() { */ @Test void testNullTaskWithCallback() { - assertTimeout(ofMillis(3000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - final var asyncResult = executor.startProcess(null, callback); - - assertNotNull(asyncResult, "The AsyncResult should not be 'null', even though the task was 'null'."); - asyncResult.await(); // Prevent timing issues, and wait until the result is available - assertTrue(asyncResult.isCompleted()); - verify(callback, times(0)).onComplete(any()); - verify(callback, times(1)).onError(exceptionCaptor.capture()); - - final var exception = exceptionCaptor.getValue(); - assertNotNull(exception); - - assertEquals(NullPointerException.class, exception.getClass()); - - try { - asyncResult.getValue(); - fail("Expected ExecutionException with NPE as cause"); - } catch (final ExecutionException e) { - assertNotNull(e.getMessage()); - assertNotNull(e.getCause()); - assertEquals(NullPointerException.class, e.getCause().getClass()); - } - }); - + assertTimeout( + ofMillis(3000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + final var asyncResult = executor.startProcess(null, callback); + + assertNotNull( + asyncResult, + "The AsyncResult should not be 'null', even though the task was 'null'."); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + verify(callback, times(0)).onComplete(any()); + verify(callback, times(1)).onError(exceptionCaptor.capture()); + + final var exception = exceptionCaptor.getValue(); + assertNotNull(exception); + + assertEquals(NullPointerException.class, exception.getClass()); + + try { + asyncResult.getValue(); + fail("Expected ExecutionException with NPE as cause"); + } catch (final ExecutionException e) { + assertNotNull(e.getMessage()); + assertNotNull(e.getCause()); + assertEquals(NullPointerException.class, e.getCause().getClass()); + } + }); } /** @@ -308,28 +323,27 @@ void testNullTaskWithCallback() { */ @Test void testNullTaskWithNullCallback() { - assertTimeout(ofMillis(3000), () -> { - // Instantiate a new executor and start a new 'null' task ... - final var executor = new ThreadAsyncExecutor(); - final var asyncResult = executor.startProcess(null, null); - - assertNotNull( - asyncResult, - "The AsyncResult should not be 'null', even though the task and callback were 'null'." - ); - asyncResult.await(); // Prevent timing issues, and wait until the result is available - assertTrue(asyncResult.isCompleted()); - - try { - asyncResult.getValue(); - fail("Expected ExecutionException with NPE as cause"); - } catch (final ExecutionException e) { - assertNotNull(e.getMessage()); - assertNotNull(e.getCause()); - assertEquals(NullPointerException.class, e.getCause().getClass()); - } - }); - + assertTimeout( + ofMillis(3000), + () -> { + // Instantiate a new executor and start a new 'null' task ... + final var executor = new ThreadAsyncExecutor(); + final var asyncResult = executor.startProcess(null, null); + + assertNotNull( + asyncResult, + "The AsyncResult should not be 'null', even though the task and callback were 'null'."); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail("Expected ExecutionException with NPE as cause"); + } catch (final ExecutionException e) { + assertNotNull(e.getMessage()); + assertNotNull(e.getCause()); + assertEquals(NullPointerException.class, e.getCause().getClass()); + } + }); } - } diff --git a/balking/README.md b/balking/README.md index 486439c6819f..f659d278b349 100644 --- a/balking/README.md +++ b/balking/README.md @@ -1,22 +1,25 @@ --- -title: Balking +title: "Balking Pattern in Java: Smart Control Over Java Execution" +shortTitle: Balking +description: "Learn the Balking design pattern in Java, a concurrency pattern that prevents code execution in inappropriate states. Discover examples, use cases, and benefits." category: Concurrency language: en tag: - - Decoupling + - Concurrency + - Decoupling + - Fault tolerance + - Synchronization --- -## Intent +## Intent of Balking Design Pattern -Balking Pattern is used to prevent an object from executing a certain code if it is in an incomplete or inappropriate state. If the state is not suitable for the action, the method call is ignored (or "balked"). +The Balking Pattern in Java is a concurrency design pattern that prevents an object from executing certain code if it is in an incomplete or inappropriate state. This pattern is crucial for managing state and concurrency in multithreaded Java applications. -## Explanation +## Detailed Explanation of Balking Pattern with Real-World Examples -Real world example +Real-world example -> There's a start-button in a washing machine to initiate the laundry washing. When the washing -> machine is inactive the button works as expected, but if it's already washing the button does -> nothing. +> A real-world analogy of the Balking design pattern can be seen in a laundry service. Imagine a washing machine at a laundromat that only starts washing clothes if the door is properly closed and locked. If a user tries to start the machine while the door is open, the machine balks and does nothing. This ensures that the washing process only begins when it is safe to do so, preventing water spillage and potential damage to the machine. Similarly, the Balking pattern in software design ensures that operations are only executed when the object is in an appropriate state, preventing erroneous actions and maintaining system stability. In plain words @@ -24,54 +27,53 @@ In plain words Wikipedia says -> The balking pattern is a software design pattern that only executes an action on an object when -> the object is in a particular state. For example, if an object reads ZIP files and a calling -> method invokes a get method on the object when the ZIP file is not open, the object would "balk" -> at the request. +> The balking pattern is a software design pattern that only executes an action on an object when the object is in a particular state. For example, if an object reads ZIP files and a calling method invokes a get method on the object when the ZIP file is not open, the object would "balk" at the request. -**Programmatic Example** +## Programmatic Example of Balking Pattern in Java -In this example implementation, `WashingMachine` is an object that has two states in which it can -be: ENABLED and WASHING. If the machine is ENABLED, the state changes to WASHING using a thread-safe -method. On the other hand, if it already has been washing and any other thread executes `wash()` -it won't do that and returns without doing anything. +This example demonstrates the Balking Pattern in a multithreaded Java application, highlighting state management and concurrency control. The Balking Pattern is exemplified by a washing machine's start button that initiates washing only if the machine is idle. This ensures state management and prevents concurrent issues. + +There's a start-button in a washing machine to initiate the laundry washing. When the washing machine is inactive the button works as expected, but if it's already washing the button does nothing. + +In this example implementation, `WashingMachine` is an object that has two states in which it can be: ENABLED and WASHING. If the machine is ENABLED, the state changes to WASHING using a thread-safe method. On the other hand, if it already has been washing and any other thread executes `wash`it won't do that and returns without doing anything. Here are the relevant parts of the `WashingMachine` class. ```java + @Slf4j public class WashingMachine { - 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; + 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; + } + 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()); } - 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()); - } } ``` @@ -79,27 +81,29 @@ 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); } ``` -Now we introduce the application using the `WashingMachine`. +Now, we introduce the application using the `WashingMachine`. ```java - public static void main(String... args) { +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.execute(washingMachine::wash); } executorService.shutdown(); try { - executorService.awaitTermination(10, TimeUnit.SECONDS); + if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } } catch (InterruptedException ie) { - LOGGER.error("ERROR: Waiting on executor service shutdown!"); - Thread.currentThread().interrupt(); + LOGGER.error("ERROR: Waiting on executor service shutdown!"); + Thread.currentThread().interrupt(); } - } +} ``` Here is the console output of the program. @@ -114,25 +118,20 @@ Here is the console output of the program. 14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed. ``` -## Class diagram - -![alt text](./etc/balking.png "Balking") - -## Applicability +## When to Use the Balking Pattern in Java Use the Balking pattern when * You want to invoke an action on an object only when it is in a particular state -* Objects are generally only in a state that is prone to balking temporarily but for an unknown - amount of time +* Objects are generally only in a state that is prone to balking temporarily but for an unknown amount of time * In multithreaded applications where certain actions should only proceed when specific conditions are met, and those conditions are expected to change over time due to external factors or concurrent operations. -## Known Uses: +## Real-World Applications of Balking Pattern in Java * Resource pooling, where resources are only allocated if they are in a valid state for allocation. * Thread management, where threads only proceed with tasks if certain conditions (like task availability or resource locks) are met. -## Consequences: +## Benefits and Trade-offs of Balking Pattern Benefits: @@ -145,12 +144,14 @@ Trade-offs: * Can introduce complexity by obscuring the conditions under which actions are taken or ignored, potentially making the system harder to debug and understand. * May lead to missed opportunities or actions if the state changes are not properly monitored or if the balking condition is too restrictive. -## Related patterns +## Related Java Design Patterns -* [Double-Checked Locking Pattern](https://java-design-patterns.com/patterns/double-checked-locking/) -* [Guarded Suspension Pattern](https://java-design-patterns.com/patterns/guarded-suspension/) -* [State](https://java-design-patterns.com/patterns/state/) +* [Double-Checked Locking](https://java-design-patterns.com/patterns/double-checked-locking/): Ensures that initialization occurs only when necessary and avoids unnecessary locking, which is related to Balking in terms of conditionally executing logic based on the object's state. +* [Guarded Suspension](https://java-design-patterns.com/patterns/guarded-suspension/): Similar in ensuring actions are only performed when an object is in a certain state, but typically involves waiting until the state is valid. +* [State](https://java-design-patterns.com/patterns/state/): The State pattern can be used in conjunction with Balking to manage the states and transitions of the object. -## Credits +## References and Credits -* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, 2nd Edition, Volume 1](https://www.amazon.com/gp/product/0471227293/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0471227293&linkId=0e39a59ffaab93fb476036fecb637b99) +* [Concurrent Programming in Java : Design Principles and Patterns](https://amzn.to/4dIBqxL) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML](https://amzn.to/4bOtzwF) diff --git a/balking/pom.xml b/balking/pom.xml index b9902e671351..818f7549c330 100644 --- a/balking/pom.xml +++ b/balking/pom.xml @@ -34,6 +34,14 @@ 4.0.0 balking + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/balking/src/main/java/com/iluwatar/balking/DelayProvider.java b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java index f142c8c7bf72..f27922219ea6 100644 --- a/balking/src/main/java/com/iluwatar/balking/DelayProvider.java +++ b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java @@ -26,9 +26,7 @@ import java.util.concurrent.TimeUnit; -/** - * An interface to simulate delay while executing some work. - */ +/** An interface to simulate delay while executing some work. */ public interface DelayProvider { void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task); } diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java index 794cfbd8ae15..52ce7c593b6b 100644 --- a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java +++ b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java @@ -28,30 +28,26 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * Washing machine class. - */ +/** Washing machine class. */ @Slf4j public class WashingMachine { private final DelayProvider delayProvider; - @Getter - private WashingMachineState washingMachineState; + @Getter private WashingMachineState washingMachineState; - /** - * Creates a new instance of WashingMachine. - */ + /** Creates a new instance of WashingMachine. */ public WashingMachine() { - this((interval, timeUnit, task) -> { - try { - Thread.sleep(timeUnit.toMillis(interval)); - } catch (InterruptedException ie) { - LOGGER.error("", ie); - Thread.currentThread().interrupt(); - } - task.run(); - }); + this( + (interval, timeUnit, task) -> { + try { + Thread.sleep(timeUnit.toMillis(interval)); + } catch (InterruptedException ie) { + LOGGER.error("", ie); + Thread.currentThread().interrupt(); + } + task.run(); + }); } /** @@ -63,9 +59,7 @@ public WashingMachine(DelayProvider delayProvider) { this.washingMachineState = WashingMachineState.ENABLED; } - /** - * Method responsible for washing if the object is in appropriate state. - */ + /** Method responsible for washing if the object is in appropriate state. */ public void wash() { synchronized (this) { var machineState = getWashingMachineState(); @@ -81,12 +75,9 @@ public void wash() { this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing); } - /** - * Method is responsible for ending the washing by changing machine state. - */ + /** Method is responsible for ending the washing by changing machine state. */ public synchronized void endOfWashing() { washingMachineState = WashingMachineState.ENABLED; LOGGER.info("{}: Washing completed.", Thread.currentThread().getId()); } - } diff --git a/balking/src/test/java/com/iluwatar/balking/AppTest.java b/balking/src/test/java/com/iluwatar/balking/AppTest.java index b744b6786613..40beabf553d0 100644 --- a/balking/src/test/java/com/iluwatar/balking/AppTest.java +++ b/balking/src/test/java/com/iluwatar/balking/AppTest.java @@ -24,27 +24,22 @@ */ package com.iluwatar.balking; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; -/** - * Application test - */ +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { assertDoesNotThrow((Executable) App::main); } - -} \ No newline at end of file +} diff --git a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java index f5c1e2de60f3..9bf7ac2548a0 100644 --- a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java +++ b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java @@ -29,9 +29,7 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -/** - * Tests for {@link WashingMachine} - */ +/** Tests for {@link WashingMachine} */ class WashingMachineTest { private final FakeDelayProvider fakeDelayProvider = new FakeDelayProvider(); @@ -69,4 +67,4 @@ public void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task) { this.task = task; } } -} \ No newline at end of file +} diff --git a/bloc/README.md b/bloc/README.md new file mode 100644 index 000000000000..228af1634052 --- /dev/null +++ b/bloc/README.md @@ -0,0 +1,228 @@ +--- +title: "Bloc Pattern in Java: State Management Simplified" +shortTitle: Bloc +description: "Learn how the Bloc pattern helps manage state changes in Java applications. This guide covers dynamic listener management, real-world examples, and clean code practices for state management." +category: Structural +language: en +tag: + - State Management + - Event-driven + - Listener Management + - Object Composition + - Dynamic Behavior +--- + +## Also known as + +* Event-driven State Management +* State Listener Pattern + +## Intent of the Bloc Pattern + +The Bloc pattern manages the state of an object and allows for dynamically notifying interested listeners about state changes. It separates state management logic from the rest of the application, improving code organization and flexibility. + +## Detailed explanation of the Bloc pattern with real-World examples + +### Real-world example + +> Consider a digital counter application where multiple parts of the UI need to be updated whenever the counter changes. For example, a label displaying the counter value and an activity log showing changes. Instead of directly modifying these UI components, the Bloc pattern manages the counter state and notifies all registered listeners about the state change. Listeners can dynamically subscribe or unsubscribe from receiving updates. + +### In plain words + +> The Bloc pattern manages a single state object and dynamically notifies registered listeners whenever the state changes. + +### Wikipedia says + +> While not a formalized "Gang of Four" design pattern, Bloc is widely used in state-driven applications. It centralizes state management and propagates state changes to registered observers, following principles of separation of concerns. + +--- + +## Programmatic Example of the Bloc Pattern in Java + +### **Core Components of the Bloc Pattern** + +#### **1. State Object** + +The `State` class holds the representation of the state of the application that will be passed to listeners whenever there is a change to do but in this example it's simplified to be a single value. + +```java +package com.iluwatar.bloc; + +import lombok.Getter; + +@Getter +public class State { + private final int value; + + public State(int value) { + this.value = value; + } + +} +``` +The `ListenerManager` interface manages the basic operations for the listeners and is implemented by bloc class +```java +import java.util.List; + +public interface ListenerManager { + void addListener(StateListener listener); + void removeListener(StateListener listener); + List> getListeners(); +} +``` +The `StateListener` interface has a method that the listener needs to react to changes in the state and is used by bloC to notify listeners whenever there is an update to state. +```java +public interface StateListener { +void onStateChange(T state); +} +``` + +The `Bloc` class holds the current state and manages logic of states and notifies the list of listeners when states changes. +The `Bloc` class contains methods for listeners and states like emitstate which updates the currentstate and notifies listeners addlistener which adds new listener to the listeners list and notifies it with the currentstate removelistener which removes listener from the listeners list and increment which increases the state value by 1 which is like an update to the current state and notifies the listeners in listeners list with the new state which holds a value incremented by 1 and decrement functions which does the opposite of increment function and notifies listeners in listeners list. +```java +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Bloc implements ListenerManager { +private State currentState; +private final List> listeners = new ArrayList<>(); + +public Bloc() { +this.currentState = new State(0); +} + +@Override +public void addListener(StateListener listener) { +listeners.add(listener); +listener.onStateChange(currentState); +} + +@Override +public void removeListener(StateListener listener) { +listeners.remove(listener); +} + +@Override +public List> getListeners() { +return Collections.unmodifiableList(listeners); +} + +private void emitState(State newState) { +currentState = newState; +for (StateListener listener : listeners) { +listener.onStateChange(currentState); +} +} + +public void increment() { +emitState(new State(currentState.getValue() + 1)); +} + +public void decrement() { +emitState(new State(currentState.getValue() - 1)); +} +} +``` +The `main` class have a simple gui to try and test the bloc pattern components separately from the ui components. +the `main` class creates an instance of bloc then adds a listener to update the ui which resembles the counter and some buttons to change the states and toggle the listener dynamically +```java +import javax.swing.*; +import java.awt.*; + +public class Main { +public static void main(String[] args) { +Bloc bloc = new Bloc(); +JFrame frame = new JFrame("Bloc Example"); +frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); +frame.setSize(400, 300); +JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); +counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); +JButton incrementButton = new JButton("Increment"); +JButton decrementButton = new JButton("Decrement"); +JButton toggleListenerButton = new JButton("Disable Listener"); + + frame.setLayout(new BorderLayout()); + frame.add(counterLabel, BorderLayout.CENTER); + frame.add(incrementButton, BorderLayout.NORTH); + frame.add(decrementButton, BorderLayout.SOUTH); + frame.add(toggleListenerButton, BorderLayout.EAST); + + StateListener stateListener = state -> counterLabel.setText("Counter: " + state.getValue()); + + bloc.addListener(stateListener); + + toggleListenerButton.addActionListener(e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); + + incrementButton.addActionListener(e -> bloc.increment()); + decrementButton.addActionListener(e -> bloc.decrement()); + + frame.setVisible(true); +} +} +``` +## Program Output + +- **On Increment** + `Counter: 1` + +- **On Decrement** + `Counter: 0` + +- **Dynamic Listener Toggle** + - Listener disabled: Counter stops updating. + - Listener enabled: Counter updates again. + +--- + +## When to Use the Bloc Pattern + +Use the Bloc pattern when: + +- You need a centralized system to manage state updates. +- You want to dynamically add/remove listeners without tight coupling. +- You are building an event-driven or state-driven system, such as UI frameworks. +--- + +## Real-World Applications of Bloc Pattern + +- **UI State Management**: Reacting to button clicks, updating labels, and toggling views. +- **Event-driven Systems**: Handling multiple subscribers efficiently for state updates. +--- + +## Benefits and Trade-offs of Bloc Pattern + +### Benefits: +- Clean separation of state management and UI logic. +- Flexibility to dynamically add/remove listeners. +- Centralized state propagation. + +### Trade-offs: +- Adds some complexity with the listener management mechanism. +- May introduce performance concerns with excessive listeners. +- the bloc class handles too many methods which violates the single responsbility principle +--- + +## Related Patterns + +- **Observer**: Bloc is a specialized implementation of the Observer pattern. +- **Mediator**: Bloc centralizes communication and state propagation. +- **cubit**: bloC is more general implementation than cubit +--- + +## References and Credits + +- [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +- [Java Swing Documentation](https://docs.oracle.com/javase/tutorial/uiswing/) +- [Event-Driven Programming in Java](https://www.oracle.com/java/) +- [bloC archetecture](https://bloclibrary.dev/architecture/) +- [flutter bloC package](https://pub.dev/documentation/flutter_bloc/latest/) + diff --git a/bloc/etc/bloc.png b/bloc/etc/bloc.png new file mode 100644 index 000000000000..60d6eb77c8fc Binary files /dev/null and b/bloc/etc/bloc.png differ diff --git a/bloc/etc/bloc.puml b/bloc/etc/bloc.puml new file mode 100644 index 000000000000..5991f533ae70 --- /dev/null +++ b/bloc/etc/bloc.puml @@ -0,0 +1,41 @@ +@startuml +package com.iluwatar.bloc { + + class State { + - value : int + + State(value : int) + + getValue() : int + } + + interface StateListener { + + onStateChange(state : T) + } + + interface ListenerManager { + + addListener(listener : StateListener) + + removeListener(listener : StateListener) + + getListeners() : List> + } + + class BloC { + - currentState : State + - listeners : List> + + BloC() + + addListener(listener : StateListener) + + removeListener(listener : StateListener) + + getListeners() : List> + - emitState(newState : State) + + increment() + + decrement() + } + + class Main { + + main(args : String[]) + } + + ListenerManager <|.. BloC + StateListener <|.. BloC + BloC o-- State + BloC *-- StateListener +} +@enduml diff --git a/bloc/etc/bloc.urm.puml b/bloc/etc/bloc.urm.puml new file mode 100644 index 000000000000..6408aa76e6a4 --- /dev/null +++ b/bloc/etc/bloc.urm.puml @@ -0,0 +1,32 @@ +@startuml +package com.iluwatar.bloc { + class Bloc { + - currentState : State + - listeners : List> + + Bloc() + + addListener(listener : StateListener) + + decrement() + - emitState(newState : State) + + getListeners() : List> + + increment() + + removeListener(listener : StateListener) + } + class BlocUi { + + BlocUi() + + createAndShowUi() + } + interface ListenerManager { + + addListener(StateListener) {abstract} + + getListeners() : List> {abstract} + + removeListener(StateListener) {abstract} + } + class Main { + + Main() + + main(args : String[]) {static} + } + interface StateListener { + + onStateChange(T) {abstract} + } +} +Bloc ..|> ListenerManager +@enduml \ No newline at end of file diff --git a/bloc/pom.xml b/bloc/pom.xml new file mode 100644 index 000000000000..cc52a3b99dc2 --- /dev/null +++ b/bloc/pom.xml @@ -0,0 +1,76 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + bloc + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.testng + testng + 7.11.0 + test + + + org.assertj + assertj-core + 3.27.3 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.bloc.Main + + + + + + + + + diff --git a/bloc/src/main/java/com/iluwatar/bloc/Bloc.java b/bloc/src/main/java/com/iluwatar/bloc/Bloc.java new file mode 100644 index 000000000000..f6ab0a61cdbf --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/Bloc.java @@ -0,0 +1,98 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * The Bloc class is responsible for managing the current state and notifying registered listeners + * whenever the state changes. It implements the ListenerManager interface, allowing listeners to be + * added, removed, and notified of state changes. + */ +public class Bloc implements ListenerManager { + + private State currentState; + private final List> listeners = new ArrayList<>(); + + /** Constructs a new Bloc instance with an initial state of value 0. */ + public Bloc() { + this.currentState = new State(0); + } + + /** + * Adds a listener to receive state change notifications. + * + * @param listener the listener to add + */ + @Override + public void addListener(StateListener listener) { + listeners.add(listener); + listener.onStateChange(currentState); + } + + /** + * Removes a listener from receiving state change notifications. + * + * @param listener the listener to remove + */ + @Override + public void removeListener(StateListener listener) { + listeners.remove(listener); + } + + /** + * Returns an unmodifiable list of all registered listeners. + * + * @return an unmodifiable list of listeners + */ + @Override + public List> getListeners() { + return Collections.unmodifiableList(listeners); + } + + /** + * Emits a new state and notifies all registered listeners of the change. + * + * @param newState the new state to emit + */ + private void emitState(State newState) { + currentState = newState; + for (StateListener listener : listeners) { + listener.onStateChange(currentState); + } + } + + /** Increments the current state value by 1 and notifies listeners of the change. */ + public void increment() { + emitState(new State(currentState.value() + 1)); + } + + /** Decrements the current state value by 1 and notifies listeners of the change. */ + public void decrement() { + emitState(new State(currentState.value() - 1)); + } +} diff --git a/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java b/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java new file mode 100644 index 000000000000..500d455d82e1 --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import java.awt.BorderLayout; +import java.awt.Font; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.SwingConstants; +import javax.swing.WindowConstants; + +/** The BlocUI class handles the creation and management of the UI components. */ +public class BlocUi { + + /** Creates and shows the UI. */ + public void createAndShowUi() { + // Create a Bloc instance to manage the state + final Bloc bloc = new Bloc(); + + // setting up a frame window with a title + JFrame frame = new JFrame("BloC example"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(400, 300); + + // label to display the counter value + JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); + counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); + + // buttons for increment, decrement, and toggling listener + JButton decrementButton = new JButton("Decrement"); + JButton toggleListenerButton = new JButton("Disable Listener"); + JButton incrementButton = new JButton("Increment"); + + frame.setLayout(new BorderLayout()); + frame.add(counterLabel, BorderLayout.CENTER); + frame.add(incrementButton, BorderLayout.NORTH); + frame.add(decrementButton, BorderLayout.SOUTH); + frame.add(toggleListenerButton, BorderLayout.EAST); + + // making a state listener to update the counter label when the state changes + StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value()); + + // adding the listener to the Bloc instance + bloc.addListener(stateListener); + + toggleListenerButton.addActionListener( + e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); + + incrementButton.addActionListener(e -> bloc.increment()); + decrementButton.addActionListener(e -> bloc.decrement()); + + frame.setVisible(true); + } +} diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java b/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java similarity index 64% rename from priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java rename to bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java index 2d372c7a8955..cd55b0fb320d 100644 --- a/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java +++ b/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java @@ -22,38 +22,35 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.priority.queue; +package com.iluwatar.bloc; + +import java.util.List; /** - * Manage priority queue. + * Interface for managing listeners for state changes. + * + * @param The type of state to be handled by the listeners. */ -public class QueueManager { - /* - Priority message - */ - private final PriorityMessageQueue messagePriorityMessageQueue; - - public QueueManager(int initialCapacity) { - messagePriorityMessageQueue = new PriorityMessageQueue<>(new Message[initialCapacity]); - } +public interface ListenerManager { /** - * Publish message to queue. + * Adds a listener that will be notified of state changes. + * + * @param listener the listener to be added */ - public void publishMessage(Message message) { - messagePriorityMessageQueue.add(message); - } - + void addListener(StateListener listener); /** - * Receive message from queue. + * Removes a listener so that it no longer receives state change notifications. + * + * @param listener the listener to be removed */ - public Message receiveMessage() { - if (messagePriorityMessageQueue.isEmpty()) { - return null; - } - return messagePriorityMessageQueue.remove(); - } - + void removeListener(StateListener listener); + /** + * Returns a list of all listeners currently registered for state changes. + * + * @return a list of registered listeners + */ + List> getListeners(); } diff --git a/bloc/src/main/java/com/iluwatar/bloc/Main.java b/bloc/src/main/java/com/iluwatar/bloc/Main.java new file mode 100644 index 000000000000..b7a929bcf2bd --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/Main.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +/** + * The BLoC (Business Logic Component) pattern is a software design pattern primarily used in + * Flutter applications. It facilitates the separation of business logic from UI code, making the + * application more modular, testable, and scalable. The BLoC pattern uses streams to manage the + * flow of data and state changes, allowing widgets to react to new states as they arrive. In the + * BLoC pattern, the application is divided into three key components: - Input streams: Represent + * user interactions or external events fed into the BLoC. - Business logic: Processes the input and + * determines the resulting state or actions. - Output streams: Emit the updated state for the UI to + * consume. The BLoC pattern is especially useful in reactive programming scenarios and aligns well + * with the declarative nature of Flutter. By using this pattern, developers can ensure a clear + * separation of concerns, enhance reusability, and maintain consistent state management throughout + * the application. + */ +public class Main { + + /** + * The entry point of the application. Initializes the GUI. + * + * @param args command-line arguments (not used in this example) + */ + public static void main(String[] args) { + BlocUi blocUi = new BlocUi(); + blocUi.createAndShowUi(); + } +} diff --git a/bloc/src/main/java/com/iluwatar/bloc/State.java b/bloc/src/main/java/com/iluwatar/bloc/State.java new file mode 100644 index 000000000000..430747548cd3 --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/State.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +/** + * The {@code State} class represents a state with an integer value. This class encapsulates the + * value and provides methods to retrieve it. + */ +public record State(int value) {} diff --git a/bloc/src/main/java/com/iluwatar/bloc/StateListener.java b/bloc/src/main/java/com/iluwatar/bloc/StateListener.java new file mode 100644 index 000000000000..77aac172e4e3 --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/StateListener.java @@ -0,0 +1,42 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +/** + * The {@code StateListener} interface defines the contract for listening to state changes. + * Implementations of this interface should handle state changes and define actions to take when the + * state changes. + * + * @param the type of state that this listener will handle + */ +public interface StateListener { + + /** + * This method is called when the state has changed. + * + * @param state the updated state + */ + void onStateChange(T state); +} diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java new file mode 100644 index 000000000000..98e34b8d4a22 --- /dev/null +++ b/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BlocTest { + private Bloc bloc; + private AtomicInteger stateValue; + + @BeforeEach + void setUp() { + bloc = new Bloc(); + stateValue = new AtomicInteger(0); + } + + @Test + void initialState() { + assertTrue(bloc.getListeners().isEmpty(), "No listeners should be present initially."); + } + + @Test + void IncrementUpdateState() { + bloc.addListener(state -> stateValue.set(state.value())); + bloc.increment(); + assertEquals(1, stateValue.get(), "State should increment to 1"); + } + + @Test + void DecrementUpdateState() { + bloc.addListener(state -> stateValue.set(state.value())); + bloc.decrement(); + assertEquals(-1, stateValue.get(), "State should decrement to -1"); + } + + @Test + void addingListener() { + bloc.addListener(state -> {}); + assertEquals(1, bloc.getListeners().size(), "Listener count should be 1."); + } + + @Test + void removingListener() { + StateListener listener = state -> {}; + bloc.addListener(listener); + bloc.removeListener(listener); + assertTrue(bloc.getListeners().isEmpty(), "Listener count should be 0 after removal."); + } + + @Test + void multipleListeners() { + AtomicInteger secondValue = new AtomicInteger(); + bloc.addListener(state -> stateValue.set(state.value())); + bloc.addListener(state -> secondValue.set(state.value())); + bloc.increment(); + assertEquals(1, stateValue.get(), "First listener should receive state 1."); + assertEquals(1, secondValue.get(), "Second listener should receive state 1."); + } +} diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java new file mode 100644 index 000000000000..1327e2cb2197 --- /dev/null +++ b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java @@ -0,0 +1,121 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.awt.*; +import javax.swing.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class BlocUiTest { + + private JFrame frame; + private JLabel counterLabel; + private JButton incrementButton; + private JButton decrementButton; + private JButton toggleListenerButton; + private Bloc bloc; + private StateListener stateListener; + + @BeforeEach + public void setUp() { + bloc = new Bloc(); // Re-initialize the Bloc for each test + + frame = new JFrame("BloC example"); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.setSize(400, 300); + + counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); + counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); + + incrementButton = new JButton("Increment"); + decrementButton = new JButton("Decrement"); + toggleListenerButton = new JButton("Disable Listener"); + + frame.setLayout(new BorderLayout()); + frame.add(counterLabel, BorderLayout.CENTER); + frame.add(incrementButton, BorderLayout.NORTH); + frame.add(decrementButton, BorderLayout.SOUTH); + frame.add(toggleListenerButton, BorderLayout.EAST); + + stateListener = state -> counterLabel.setText("Counter: " + state.value()); + bloc.addListener(stateListener); + + incrementButton.addActionListener(e -> bloc.increment()); + decrementButton.addActionListener(e -> bloc.decrement()); + toggleListenerButton.addActionListener( + e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); + + frame.setVisible(true); + } + + @AfterEach + public void tearDown() { + frame.dispose(); + bloc = new Bloc(); // Reset Bloc state after each test to avoid state carryover + } + + @Test + public void testIncrementButton() { + simulateButtonClick(incrementButton); + assertEquals("Counter: 1", counterLabel.getText()); + } + + @Test + public void testDecrementButton() { + simulateButtonClick(decrementButton); + assertEquals("Counter: -1", counterLabel.getText()); + } + + @Test + public void testToggleListenerButton() { + // Disable listener + simulateButtonClick(toggleListenerButton); + simulateButtonClick(incrementButton); + assertEquals("Counter: 0", counterLabel.getText()); // Listener is disabled + + // Enable listener + simulateButtonClick(toggleListenerButton); + simulateButtonClick(incrementButton); + assertEquals("Counter: 2", counterLabel.getText()); // Listener is re-enabled + } + + private void simulateButtonClick(JButton button) { + for (var listener : button.getActionListeners()) { + listener.actionPerformed(null); + } + } +} diff --git a/bridge/README.md b/bridge/README.md index a679df25f248..9c45078d2d64 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -1,26 +1,32 @@ --- -title: Bridge +title: "Bridge Pattern in Java: Decouple Abstraction from Implementation" +shortTitle: Bridge +description: "Learn about the Bridge design pattern in Java. Decouple abstraction from implementation to enhance flexibility and extensibility. Explore real-world examples, class diagrams, and use cases." category: Structural language: en tag: + - Abstraction - Decoupling - Extensibility - Gang of Four + - Object composition --- ## Also known as -Handle/Body +* Handle/Body -## Intent +## Intent of Bridge Design Pattern -Decouple an abstraction from its implementation so that the two can vary independently. +The Bridge design pattern is a structural pattern in Java that decouples an abstraction from its implementation, allowing both to vary independently. This pattern is essential for developing flexible and extensible software systems. -## Explanation +## Detailed Explanation of Bridge Pattern with Real-World Examples 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 second. +> In Java, the Bridge pattern is commonly used in GUI frameworks, database drivers, and device drivers. For instance, a universal remote control (abstraction) can operate various TV brands (implementations) through a consistent interface. +> +> Imagine a universal remote control (abstraction) that can operate different brands and types of televisions (implementations). The remote control provides a consistent interface for operations like turning on/off, changing channels, and adjusting the volume. Each television brand or type has its own specific implementation of these operations. By using the Bridge pattern, the remote control interface is decoupled from the television implementations, allowing the remote control to work with any television regardless of its brand or internal workings. This separation allows new television models to be added without changing the remote control's code, and different remote controls can be developed to work with the same set of televisions. In Plain Words @@ -30,143 +36,152 @@ Wikipedia says > The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently" -**Programmatic Example** +## Programmatic Example of Bridge Pattern in Java -Translating our weapon example from above. Here we have the `Weapon` hierarchy: +Imagine you have a weapon that can have various enchantments, and you need to combine different weapons with different enchantments. How would you handle this? Would you create multiple copies of each weapon, each with a different enchantment, or would you create separate enchantments and apply them to the weapon as needed? The Bridge pattern enables you to do the latter. + +Here we have the `Weapon` hierarchy: ```java public interface Weapon { - void wield(); - void swing(); - void unwield(); - Enchantment getEnchantment(); + void wield(); + + void swing(); + + void unwield(); + + Enchantment getEnchantment(); } public class Sword implements Weapon { - private final Enchantment enchantment; - - public Sword(Enchantment enchantment) { - this.enchantment = enchantment; - } - - @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 unwield() { - LOGGER.info("The sword is unwielded."); - enchantment.onDeactivate(); - } - - @Override - public Enchantment getEnchantment() { - return enchantment; - } + private final Enchantment enchantment; + + public Sword(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @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 unwield() { + LOGGER.info("The sword is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } } public class Hammer implements Weapon { - private final Enchantment enchantment; - - public Hammer(Enchantment enchantment) { - this.enchantment = enchantment; - } - - @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 unwield() { - LOGGER.info("The hammer is unwielded."); - enchantment.onDeactivate(); - } - - @Override - public Enchantment getEnchantment() { - return enchantment; - } + private final Enchantment enchantment; + + public Hammer(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @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 unwield() { + LOGGER.info("The hammer is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } } ``` -Here's the separate enchantment hierarchy: +Here's the separate `Enchantment` hierarchy: ```java public interface Enchantment { - void onActivate(); - void apply(); - void onDeactivate(); + void onActivate(); + + void apply(); + + 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."); + } } ``` 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(); - -LOGGER.info("The valkyrie receives an enchanted hammer."); -var hammer = new Hammer(new FlyingEnchantment()); -hammer.wield(); -hammer.swing(); -hammer.unwield(); +public static void main(String[] args) { + LOGGER.info("The knight receives an enchanted sword."); + 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(); +} ``` Here's the console output. @@ -188,27 +203,31 @@ The hammer is unwielded. The item's glow fades. ``` -## Class diagram +## Bridge Pattern Class Diagram + +![Bridge](./etc/bridge.urm.png "Bridge class diagram") -![alt text](./etc/bridge.urm.png "Bridge class diagram") +## When to Use the Bridge Pattern in Java -## Applicability +Consider using the Bridge pattern when: -Use the Bridge pattern when +* You need to avoid a permanent binding between an abstraction and its implementation, such as when the implementation must be chosen or switched at runtime. +* Both the abstractions and their implementations should be extendable via subclassing, allowing independent extension of each component. +* Changes to the implementation of an abstraction should not affect clients, meaning their code should not require recompilation. +* You encounter a large number of classes in your hierarchy, indicating the need to split an object into two parts, a concept referred to as "nested generalizations" by Rumbaugh. +* You want to share an implementation among multiple objects, potentially using reference counting, while keeping this detail hidden from the client, as exemplified by Coplien's String class, where multiple objects can share the same string representation. -* You want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time. -* Both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently. -* Changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled. -* You have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies. -* You want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation. +## Bridge Pattern Java Tutorials -## Known uses +* [Bridge Pattern Tutorial (DigitalOcean)](https://www.digitalocean.com/community/tutorials/bridge-design-pattern-java) + +## Real-World Applications of Bridge Pattern in Java * GUI Frameworks where the abstraction is the window, and the implementation could be the underlying OS windowing system. * Database Drivers where the abstraction is a generic database interface, and the implementations are database-specific drivers. * Device Drivers where the abstraction is the device-independent code, and the implementation is the device-dependent code. -## Consequences +## Benefits and Trade-offs of Bridge Pattern Benefits: @@ -221,19 +240,17 @@ Trade-offs: * Increased Complexity: The pattern can complicate the system architecture and code, especially for clients unfamiliar with the pattern. * Runtime Overhead: The extra layer of abstraction can introduce a performance penalty, although it is often negligible in practice. -## Related Patterns +## Related Java Design Patterns -* [Adapter](https://java-design-patterns.com/patterns/adapter/): The Adapter pattern is used to provide a different interface to an object, while the Bridge pattern is used to separate an object's interface from its implementation. -* [Strategy](https://java-design-patterns.com/patterns/strategy/): The Strategy pattern is like the Bridge pattern, but with a different intent. Both patterns are based on composition: Strategy uses composition to change the behavior of a class, while Bridge uses composition to separate an abstraction from its implementation. * [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): The Abstract Factory pattern can be used along with the Bridge pattern to create platforms that are independent of the concrete classes used to create their objects. +* [Adapter](https://java-design-patterns.com/patterns/adapter/): The Adapter pattern is used to provide a different interface to an object, while the Bridge pattern is used to separate an object's interface from its implementation. * [Composite](https://java-design-patterns.com/patterns/composite/): The Bridge pattern is often used with the Composite pattern to model the implementation details of a component. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): The Strategy pattern is like the Bridge pattern, but with a different intent. Both patterns are based on composition: Strategy uses composition to change the behavior of a class, while Bridge uses composition to separate an abstraction from its implementation. -## Tutorials - -* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java) - -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) * [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3TEnhtl) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/bridge/pom.xml b/bridge/pom.xml index 915aee30a3ed..3cfd33997c05 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -34,6 +34,14 @@ bridge + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/bridge/src/main/java/com/iluwatar/bridge/App.java b/bridge/src/main/java/com/iluwatar/bridge/App.java index 1550782a6f23..c3ea3d50c35a 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/App.java +++ b/bridge/src/main/java/com/iluwatar/bridge/App.java @@ -35,9 +35,9 @@ * have their own class hierarchies. The interface of the implementations can be changed without * affecting the clients. * - *

In this example we have two class hierarchies. One of weapons and another one of - * enchantments. We can easily combine any weapon with any enchantment using composition instead of - * creating deep class hierarchy. + *

In this example we have two class hierarchies. One of weapons and another one of enchantments. + * We can easily combine any weapon with any enchantment using composition instead of creating deep + * class hierarchy. */ @Slf4j public class App { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java index 95f4cc351a7f..4bdd4502fd47 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java @@ -24,9 +24,7 @@ */ package com.iluwatar.bridge; -/** - * Enchantment. - */ +/** Enchantment. */ public interface Enchantment { void onActivate(); diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java index 530fb3e5eb37..42da3523fb31 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * FlyingEnchantment. - */ +/** FlyingEnchantment. */ @Slf4j public class FlyingEnchantment implements Enchantment { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Hammer.java b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java index a7a237c14ebb..328f3b79e9d6 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Hammer.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java @@ -27,9 +27,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Hammer. - */ +/** Hammer. */ @Slf4j @AllArgsConstructor public class Hammer implements Weapon { diff --git a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java index 3c311ddbbdc5..ed22174673d3 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SoulEatingEnchantment. - */ +/** SoulEatingEnchantment. */ @Slf4j public class SoulEatingEnchantment implements Enchantment { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Sword.java b/bridge/src/main/java/com/iluwatar/bridge/Sword.java index 7b75ead21e78..417bc9334046 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Sword.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Sword.java @@ -27,9 +27,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Sword. - */ +/** Sword. */ @Slf4j @AllArgsConstructor public class Sword implements Weapon { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Weapon.java b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java index e9452343dcc2..a9f0ab4bbf52 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Weapon.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java @@ -24,9 +24,7 @@ */ package com.iluwatar.bridge; -/** - * Weapon. - */ +/** Weapon. */ public interface Weapon { void wield(); diff --git a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java index 5db5f3eb1eb9..d1136fc90f41 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.bridge; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java b/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java index d6c38300c46c..d8853647cb84 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for hammer - */ +/** Tests for hammer */ class HammerTest extends WeaponTest { /** @@ -43,4 +41,4 @@ void testHammer() { final var hammer = spy(new Hammer(mock(FlyingEnchantment.class))); testBasicWeaponActions(hammer); } -} \ No newline at end of file +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java index d5849e78a3d5..b021cd08d00c 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for sword - */ +/** Tests for sword */ class SwordTest extends WeaponTest { /** @@ -43,4 +41,4 @@ void testSword() { final var sword = spy(new Sword(mock(FlyingEnchantment.class))); testBasicWeaponActions(sword); } -} \ No newline at end of file +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java index 47515f9b1b2a..67648ea6391e 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java @@ -28,9 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -/** - * Base class for weapon tests - */ +/** Base class for weapon tests */ abstract class WeaponTest { /** @@ -54,6 +52,5 @@ final void testBasicWeaponActions(final Weapon weapon) { weapon.unwield(); verify(enchantment).onDeactivate(); verifyNoMoreInteractions(enchantment); - } } diff --git a/builder/README.md b/builder/README.md index 930bca0f4311..6a36c334c644 100644 --- a/builder/README.md +++ b/builder/README.md @@ -1,20 +1,26 @@ --- -title: Builder +title: "Builder Pattern in Java: Crafting Custom Objects with Clarity" +shortTitle: Builder +description: "Discover the Builder design pattern in Java, a powerful creational pattern that simplifies object construction. Learn how to separate the construction of a complex object from its representation with practical examples and use cases." category: Creational language: en tag: - - Gang of Four + - Gang of Four + - Instantiation + - Object composition --- -## Intent +## Intent of Builder Design Pattern -Separate the construction of a complex object from its representation so that the same construction process can create different representations. +The Builder design pattern in Java, a fundamental creational pattern, allows for the step-by-step construction of complex objects. It separates the construction of a complex object from its representation so that the same construction process can create different representations. -## Explanation +## Detailed Explanation of Builder Pattern with Real-World Examples Real-world example -> Imagine a character generator for a role-playing game. The easiest option is to let the computer create the character for you. If you want to manually select the character details like profession, gender, hair color, etc. the character generation becomes a step-by-step process that completes when all the selections are ready. +> The Java Builder pattern is particularly useful in scenarios where object creation involves numerous parameters. +> +> Imagine you are building a customizable sandwich at a deli. The Builder design pattern in this context would involve a SandwichBuilder that allows you to specify each component of the sandwich, such as the type of bread, meat, cheese, vegetables, and condiments. Instead of having to know how to construct the sandwich from scratch, you use the SandwichBuilder to add each desired component step-by-step, ensuring you get exactly the sandwich you want. This separation of construction from the final product representation ensures that the same construction process can yield different types of sandwiches based on the specified components. In plain words @@ -24,40 +30,45 @@ Wikipedia says > The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor antipattern. -Having said that let me add a bit about what telescoping constructor antipattern is. At one point or the other, we have all seen a constructor like below: +With that in mind, let's explain what the telescoping constructor antipattern is. At some point, we have all encountered a constructor like the one below: ```java -public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { +public Hero(Profession profession,String name,HairType hairType,HairColor hairColor,Armor armor,Weapon weapon){ + // Value assignments } ``` -As you can see the number of constructor parameters can quickly get out of hand, and it may become difficult to understand the arrangement of parameters. Plus this parameter list could keep on growing if you would want to add more options in the future. This is called telescoping constructor antipattern. +As you can see, the number of constructor parameters can quickly become overwhelming, making it difficult to understand their arrangement. Additionally, this list of parameters might continue to grow if you decide to add more options in the future. This is known as the telescoping constructor antipattern. -**Programmatic Example** +## Programmatic Example of Builder Pattern in Java -The sane alternative is to use the Builder pattern. First of all, we have our hero that we want to create: +In this Java Builder pattern example, we construct different types of `Hero` objects with varying attributes. + +Imagine a character generator for a role-playing game. The simplest option is to let the computer generate the character for you. However, if you prefer to manually select character details such as profession, gender, hair color, etc., the character creation becomes a step-by-step process that concludes once all selections are made. + +A more sensible approach is to use the Builder pattern. First, let's consider the `Hero` that we want to create: ```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 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 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; + } } ``` -Then we have the builder: +Then we have the `Builder`: ```java public static class Builder { @@ -69,64 +80,103 @@ Then we have the builder: private Weapon weapon; 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; + if (profession == null || name == null) { + throw new IllegalArgumentException("profession and name can not be null"); + } + this.profession = profession; + this.name = name; } public Builder withHairType(HairType hairType) { - this.hairType = hairType; - return this; + this.hairType = hairType; + return this; } public Builder withHairColor(HairColor hairColor) { - this.hairColor = hairColor; - return this; + this.hairColor = hairColor; + return this; } public Builder withArmor(Armor armor) { - this.armor = armor; - return this; + this.armor = armor; + return this; } public Builder withWeapon(Weapon weapon) { - this.weapon = weapon; - return this; + this.weapon = weapon; + return this; } public Hero build() { - return new Hero(this); + return new Hero(this); } - } +} ``` Then it can be used as: ```java -var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); + public static void main(String[] args) { + + var mage = new Hero.Builder(Profession.MAGE, "Riobard") + .withHairColor(HairColor.BLACK) + .withWeapon(Weapon.DAGGER) + .build(); + LOGGER.info(mage.toString()); + + var warrior = new Hero.Builder(Profession.WARRIOR, "Amberjill") + .withHairColor(HairColor.BLOND) + .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD) + .build(); + LOGGER.info(warrior.toString()); + + var thief = new Hero.Builder(Profession.THIEF, "Desmond") + .withHairType(HairType.BALD) + .withWeapon(Weapon.BOW) + .build(); + LOGGER.info(thief.toString()); +} +``` + +Program output: + +``` +16:28:06.058 [main] INFO com.iluwatar.builder.App -- This is a mage named Riobard with black hair and wielding a dagger. +16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a warrior named Amberjill with blond long curly hair wearing chain mail and wielding a sword. +16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a thief named Desmond with bald head and wielding a bow. ``` -## Class diagram +## Builder Pattern Class Diagram -![alt text](./etc/builder.urm.png "Builder class diagram") +![Builder](./etc/builder.urm.png "Builder class diagram") -## Applicability +## When to Use the Builder Pattern in Java Use the Builder pattern when +* The Builder pattern is ideal for Java applications requiring complex object creation. * The algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled * The construction process must allow different representations for the object that's constructed * It's particularly useful when a product requires a lot of steps to be created and when these steps need to be executed in a specific sequence -## Known Uses +## Builder Pattern Java Tutorials + +* [Builder Design Pattern in Java (DigitalOcean)](https://www.journaldev.com/1425/builder-design-pattern-in-java) +* [Builder (Refactoring Guru)](https://refactoring.guru/design-patterns/builder) +* [Exploring Joshua Bloch’s Builder design pattern in Java (Java Magazine)](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) -* Java.lang.StringBuilder +## Real-World Applications of Builder Pattern in Java + +* StringBuilder in Java for constructing strings. +* java.lang.StringBuffer used to create mutable string objects. * Java.nio.ByteBuffer as well as similar buffers such as FloatBuffer, IntBuffer, and others * javax.swing.GroupLayout.Group#addComponent() +* Various GUI builders in IDEs that construct UI components. +* All implementations of [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) +* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) -## Consequences +## Benefits and Trade-offs of Builder Pattern Benefits: @@ -138,29 +188,17 @@ Benefits: Trade-offs: * The overall complexity of the code can increase since the pattern requires creating multiple new classes +* May increase memory usage due to the necessity of creating multiple builder objects -## Tutorials - -* [Refactoring Guru](https://refactoring.guru/design-patterns/builder) -* [Oracle Blog](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) -* [Journal Dev](https://www.journaldev.com/1425/builder-design-pattern-in-java) - -## Known uses - -* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) -* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) as well as similar buffers such as FloatBuffer, IntBuffer and so on. -* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) -* All implementations of [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) -* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) -* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) - -## Related patterns +## Related Java Design Patterns -* [Step Builder](https://java-design-patterns.com/patterns/step-builder/) is a variation of the Builder pattern that generates a complex object using a step-by-step approach. The Step Builder pattern is a good choice when you need to build an object with a large number of optional parameters, and you want to avoid the telescoping constructor antipattern. +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Can be used in conjunction with Builder to build parts of a complex object. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Builders often create objects from a prototype. +* [Step Builder](https://java-design-patterns.com/patterns/step-builder/): It is a variation of the Builder pattern that generates a complex object using a step-by-step approach. The Step Builder pattern is a good choice when you need to build an object with a large number of optional parameters, and you want to avoid the telescoping constructor antipattern. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/builder/pom.xml b/builder/pom.xml index 37176ce89207..3677c187d5e6 100644 --- a/builder/pom.xml +++ b/builder/pom.xml @@ -34,6 +34,14 @@ builder + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/builder/src/main/java/com/iluwatar/builder/App.java b/builder/src/main/java/com/iluwatar/builder/App.java index 347798cc105c..acec73a48ee1 100644 --- a/builder/src/main/java/com/iluwatar/builder/App.java +++ b/builder/src/main/java/com/iluwatar/builder/App.java @@ -58,22 +58,27 @@ public class App { */ public static void main(String[] args) { - var mage = new Hero.Builder(Profession.MAGE, "Riobard") - .withHairColor(HairColor.BLACK) - .withWeapon(Weapon.DAGGER) - .build(); + var mage = + new Hero.Builder(Profession.MAGE, "Riobard") + .withHairColor(HairColor.BLACK) + .withWeapon(Weapon.DAGGER) + .build(); LOGGER.info(mage.toString()); - var warrior = new Hero.Builder(Profession.WARRIOR, "Amberjill") - .withHairColor(HairColor.BLOND) - .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD) - .build(); + var warrior = + new Hero.Builder(Profession.WARRIOR, "Amberjill") + .withHairColor(HairColor.BLOND) + .withHairType(HairType.LONG_CURLY) + .withArmor(Armor.CHAIN_MAIL) + .withWeapon(Weapon.SWORD) + .build(); LOGGER.info(warrior.toString()); - var thief = new Hero.Builder(Profession.THIEF, "Desmond") - .withHairType(HairType.BALD) - .withWeapon(Weapon.BOW) - .build(); + var thief = + new Hero.Builder(Profession.THIEF, "Desmond") + .withHairType(HairType.BALD) + .withWeapon(Weapon.BOW) + .build(); LOGGER.info(thief.toString()); } } diff --git a/builder/src/main/java/com/iluwatar/builder/Armor.java b/builder/src/main/java/com/iluwatar/builder/Armor.java index 52cbe1a06a88..1710f569af5e 100644 --- a/builder/src/main/java/com/iluwatar/builder/Armor.java +++ b/builder/src/main/java/com/iluwatar/builder/Armor.java @@ -26,12 +26,9 @@ import lombok.AllArgsConstructor; -/** - * Armor enumeration. - */ +/** Armor enumeration. */ @AllArgsConstructor public enum Armor { - CLOTHES("clothes"), LEATHER("leather"), CHAIN_MAIL("chain mail"), diff --git a/builder/src/main/java/com/iluwatar/builder/HairColor.java b/builder/src/main/java/com/iluwatar/builder/HairColor.java index 47fa9b4a9e15..7f767c98d661 100644 --- a/builder/src/main/java/com/iluwatar/builder/HairColor.java +++ b/builder/src/main/java/com/iluwatar/builder/HairColor.java @@ -24,11 +24,8 @@ */ package com.iluwatar.builder; -/** - * HairColor enumeration. - */ +/** HairColor enumeration. */ public enum HairColor { - WHITE, BLOND, RED, @@ -39,5 +36,4 @@ public enum HairColor { public String toString() { return name().toLowerCase(); } - } diff --git a/builder/src/main/java/com/iluwatar/builder/HairType.java b/builder/src/main/java/com/iluwatar/builder/HairType.java index 45b514fb39aa..7ac31d0fa03e 100644 --- a/builder/src/main/java/com/iluwatar/builder/HairType.java +++ b/builder/src/main/java/com/iluwatar/builder/HairType.java @@ -26,12 +26,9 @@ import lombok.AllArgsConstructor; -/** - * HairType enumeration. - */ +/** HairType enumeration. */ @AllArgsConstructor public enum HairType { - BALD("bald"), SHORT("short"), CURLY("curly"), diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java index 1d15ac2f0a18..a87137e51fe0 100644 --- a/builder/src/main/java/com/iluwatar/builder/Hero.java +++ b/builder/src/main/java/com/iluwatar/builder/Hero.java @@ -24,24 +24,30 @@ */ package com.iluwatar.builder; -/** - * Hero,the record class. - */ - -public record Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { +/** Hero,the record class. */ +public record Hero( + Profession profession, + String name, + HairType hairType, + HairColor hairColor, + Armor armor, + Weapon weapon) { private Hero(Builder builder) { - this(builder.profession, builder.name, builder.hairType, builder.hairColor, builder.armor, builder.weapon); + this( + builder.profession, + builder.name, + builder.hairType, + builder.hairColor, + builder.armor, + builder.weapon); } @Override public String toString() { var sb = new StringBuilder(); - sb.append("This is a ") - .append(profession) - .append(" named ") - .append(name); + sb.append("This is a ").append(profession).append(" named ").append(name); if (hairColor != null || hairType != null) { sb.append(" with "); if (hairColor != null) { @@ -62,9 +68,7 @@ public String toString() { return sb.toString(); } - /** - * The builder class. - */ + /** The builder class. */ public static class Builder { private final Profession profession; @@ -74,9 +78,7 @@ public static class Builder { private Armor armor; private Weapon weapon; - /** - * Constructor. - */ + /** Constructor. */ public Builder(Profession profession, String name) { if (profession == null || name == null) { throw new IllegalArgumentException("profession and name can not be null"); diff --git a/builder/src/main/java/com/iluwatar/builder/Profession.java b/builder/src/main/java/com/iluwatar/builder/Profession.java index 9ab05467703c..c1be949840f8 100644 --- a/builder/src/main/java/com/iluwatar/builder/Profession.java +++ b/builder/src/main/java/com/iluwatar/builder/Profession.java @@ -24,12 +24,12 @@ */ package com.iluwatar.builder; -/** - * Profession enumeration. - */ +/** Profession enumeration. */ public enum Profession { - - WARRIOR, THIEF, MAGE, PRIEST; + WARRIOR, + THIEF, + MAGE, + PRIEST; @Override public String toString() { diff --git a/builder/src/main/java/com/iluwatar/builder/Weapon.java b/builder/src/main/java/com/iluwatar/builder/Weapon.java index 060482705661..03a9565d0cbe 100644 --- a/builder/src/main/java/com/iluwatar/builder/Weapon.java +++ b/builder/src/main/java/com/iluwatar/builder/Weapon.java @@ -24,12 +24,13 @@ */ package com.iluwatar.builder; -/** - * Weapon enumeration. - */ +/** Weapon enumeration. */ public enum Weapon { - - DAGGER, SWORD, AXE, WARHAMMER, BOW; + DAGGER, + SWORD, + AXE, + WARHAMMER, + BOW; @Override public String toString() { diff --git a/builder/src/test/java/com/iluwatar/builder/AppTest.java b/builder/src/test/java/com/iluwatar/builder/AppTest.java index d7b8c2579333..367d3da1c1ac 100644 --- a/builder/src/test/java/com/iluwatar/builder/AppTest.java +++ b/builder/src/test/java/com/iluwatar/builder/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.builder; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/builder/src/test/java/com/iluwatar/builder/HeroTest.java b/builder/src/test/java/com/iluwatar/builder/HeroTest.java index fe70d1547b88..5f67a56aa999 100644 --- a/builder/src/test/java/com/iluwatar/builder/HeroTest.java +++ b/builder/src/test/java/com/iluwatar/builder/HeroTest.java @@ -30,42 +30,33 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/6/15 - 11:01 PM - * - * @author Jeroen Meulemeester - */ +/** HeroTest */ class HeroTest { - /** - * Test if we get the expected exception when trying to create a hero without a profession - */ + /** Test if we get the expected exception when trying to create a hero without a profession */ @Test void testMissingProfession() { assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(null, "Sir without a job")); } - /** - * Test if we get the expected exception when trying to create a hero without a name - */ + /** Test if we get the expected exception when trying to create a hero without a name */ @Test void testMissingName() { assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(Profession.THIEF, null)); } - /** - * Test if the hero build by the builder has the correct attributes, as requested - */ + /** Test if the hero build by the builder has the correct attributes, as requested */ @Test void testBuildHero() { final String heroName = "Sir Lancelot"; - final var hero = new Hero.Builder(Profession.WARRIOR, heroName) - .withArmor(Armor.CHAIN_MAIL) - .withWeapon(Weapon.SWORD) - .withHairType(HairType.LONG_CURLY) - .withHairColor(HairColor.BLOND) - .build(); + final var hero = + new Hero.Builder(Profession.WARRIOR, heroName) + .withArmor(Armor.CHAIN_MAIL) + .withWeapon(Weapon.SWORD) + .withHairType(HairType.LONG_CURLY) + .withHairColor(HairColor.BLOND) + .build(); assertNotNull(hero); assertNotNull(hero.toString()); @@ -75,7 +66,5 @@ void testBuildHero() { assertEquals(Weapon.SWORD, hero.weapon()); assertEquals(HairType.LONG_CURLY, hero.hairType()); assertEquals(HairColor.BLOND, hero.hairColor()); - } - -} \ No newline at end of file +} diff --git a/business-delegate/README.md b/business-delegate/README.md index 87534b869757..3a3203b230dc 100644 --- a/business-delegate/README.md +++ b/business-delegate/README.md @@ -1,24 +1,32 @@ --- -title: Business Delegate +title: "Business Delegate Pattern in Java: Simplifying Business Service Interaction" +shortTitle: Business Delegate +description: "Learn about the Business Delegate pattern in Java. This design pattern adds an abstraction layer between presentation and business tiers, ensuring loose coupling and easier service interaction. Includes examples and class diagrams." category: Structural language: en tag: - - Decoupling + - Business + - Decoupling + - Delegation + - Enterprise patterns + - Layered architecture --- -## Intent +## Also known as -The Business Delegate pattern adds an abstraction layer between presentation and business tiers. By using the pattern we gain loose coupling between the tiers and encapsulate knowledge about how to locate, connect to, and interact with the business objects that make up the application. +* Service Representative -## Also known as +## Intent of Business Delegate Design Pattern -Service Representative +The Business Delegate pattern is a structural design pattern in Java that adds an abstraction layer between the presentation and business tiers. By using the pattern we gain loose coupling between the tiers and encapsulate knowledge about how to locate, connect to, and interact with the business objects that make up the application. -## Explanation +## Detailed Explanation of Business Delegate Pattern with Real-World Examples -Real world example +Real-world example -> A mobile phone application promises to stream any movie in existence to your device. It captures the user's search string and passes this on to the Business Delegate. The Business Delegate selects the most suitable video streaming service and plays the video from there. +> In an Enterprise application using Java EE, the Business Delegate pattern helps manage interactions between different business services. +> +> Imagine a restaurant where the waitstaff serves as intermediaries between the customers and the kitchen. When a customer places an order, the waiter takes the order to the kitchen, relays any specific requests, and later brings the prepared food back to the customer. The waitstaff abstracts the complexity of the kitchen operations from the customers, allowing the chefs to focus solely on cooking without needing to interact directly with customers. This setup allows both the customer service (presentation tier) and the kitchen (business service) to operate independently and efficiently. The waitstaff acts as the Business Delegate, managing communication and ensuring smooth interactions between the two distinct areas. In Plain Words @@ -28,64 +36,69 @@ Wikipedia says > Business Delegate is a Java EE design pattern. This pattern is directing to reduce the coupling in between business services and the connected presentation tier, and to hide the implementation details of services (including lookup and accessibility of EJB architecture). Business Delegates acts as an adaptor to invoke business objects from the presentation tier. -**Programmatic Example** +## Programmatic Example of Business Delegate Pattern in Java + +The following Java code demonstrates how to implement the Business Delegate pattern. This pattern is particularly useful in applications requiring loose coupling and efficient service interaction. + +A mobile phone application promises to stream any movie in existence to your device. It captures the user's search string and passes this on to the Business Delegate. The Business Delegate selects the most suitable video streaming service and plays the video from there. First, we have an abstraction for video streaming services and a couple of implementations. ```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"); + } } ``` Then, we have a lookup service that decides which video streaming service to use. ```java + @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; + } } - } } ``` -The Business Delegate uses a business lookup to route movie playback requests to a suitable -video streaming service. +The Business Delegate uses a business lookup to route movie playback requests to a suitable video streaming service. ```java + @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(); + } } ``` @@ -94,22 +107,22 @@ 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); + } } ``` Finally, we can demonstrate the complete example in action. ```java - public static void main(String[] args) { +public static void main(String[] args) { // prepare the objects var businessDelegate = new BusinessDelegate(); @@ -118,11 +131,11 @@ Finally, we can demonstrate the complete example in action. businessLookup.setYouTubeService(new YouTubeService()); businessDelegate.setLookupService(businessLookup); - // create the client and use the Business Delegate + // 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. @@ -132,33 +145,29 @@ Here is the console output. 21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing ``` -## Class diagram - -![alt text](./etc/business-delegate.urm.png "Business Delegate") - -## Related patterns +## Business Delegate Pattern Class Diagram -* [Service locator pattern](https://java-design-patterns.com/patterns/service-locator/) +![Business Delegate](./etc/business-delegate.urm.png "Business Delegate") -## Applicability +## When to Use the Business Delegate Pattern in Java Use the Business Delegate pattern when -* You want loose coupling between presentation and business tiers +* You need loose coupling between presentation and business tiers or need to abstract service lookups. * You want to orchestrate calls to multiple business services * You want to encapsulate service lookups and service calls * There is a need to abstract and encapsulate the communication between the client tier and business services -## Tutorials +## Business Delegate Pattern Java Tutorials -* [Business Delegate Pattern at TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) +* [Design Patterns - Business Delegate Pattern (TutorialsPoint)](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) -## Known Uses +## Real-World Applications of Business Delegate Pattern in Java * Enterprise applications using Java EE (Java Platform, Enterprise Edition) * Applications requiring remote access to business services -## Consequences +## Benefits and Trade-offs of Business Delegate Pattern Benefits: @@ -171,13 +180,14 @@ Trade-offs: * Complexity: Introduces additional layers and abstractions, which may increase complexity. * Performance Overhead: The additional indirection may incur a slight performance penalty. -## Related patterns +## Related Java Design Patterns * [Service Locator](https://java-design-patterns.com/patterns/service-locator/): Business Delegate uses Service Locator to locate business services. * [Session Facade](https://java-design-patterns.com/patterns/session-facade/): Business Delegate may use Session Facade to provide a unified interface to a set of business services. * [Composite Entity](https://java-design-patterns.com/patterns/composite-entity/): Business Delegate may use Composite Entity to manage the state of business services. -## Credits +## References and Credits -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) -* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml index 21046526f17c..a5bccb1529e7 100644 --- a/business-delegate/pom.xml +++ b/business-delegate/pom.xml @@ -34,6 +34,14 @@ business-delegate + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java index 682bf68cc41f..c23ed42caecd 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java @@ -34,9 +34,9 @@ * retrieved through service lookups. The Business Delegate itself may contain business logic too * potentially tying together multiple service calls, exception handling, retrying etc. * - *

In this example the client ({@link MobileClient}) utilizes a business delegate ( - * {@link BusinessDelegate}) to search for movies in video streaming services. The Business Delegate - * then selects the appropriate service and makes the service call. + *

In this example the client ({@link MobileClient}) utilizes a business delegate ( {@link + * BusinessDelegate}) to search for movies in video streaming services. The Business Delegate then + * selects the appropriate service and makes the service call. */ public class App { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java index 388407fb81ff..81920c857cd8 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java @@ -26,9 +26,7 @@ import lombok.Setter; -/** - * BusinessDelegate separates the presentation and business tiers. - */ +/** BusinessDelegate separates the presentation and business tiers. */ @Setter public class BusinessDelegate { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java index b2d45b9e6975..81a1b2f18fd0 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java @@ -27,9 +27,7 @@ import java.util.Locale; import lombok.Setter; -/** - * Class for performing service lookups. - */ +/** Class for performing service lookups. */ @Setter public class BusinessLookup { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java index cffc464d389a..01b5b642735b 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.business.delegate; -/** - * MobileClient utilizes BusinessDelegate to call the business tier. - */ +/** MobileClient utilizes BusinessDelegate to call the business tier. */ public class MobileClient { private final BusinessDelegate businessDelegate; diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java index 83b7cd46ef38..696480e678f3 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * NetflixService implementation. - */ +/** NetflixService implementation. */ @Slf4j public class NetflixService implements VideoStreamingService { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java index 9ac09f265b76..594b51850efb 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.business.delegate; -/** - * Interface for video streaming service implementations. - */ +/** Interface for video streaming service implementations. */ public interface VideoStreamingService { void doProcessing(); diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java index 9b239d585c6f..65c2e55ff6fa 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * YouTubeService implementation. - */ +/** YouTubeService implementation. */ @Slf4j public class YouTubeService implements VideoStreamingService { diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java index 1449f81bd6af..5f862bf6e5ff 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.business.delegate; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Business Delegate example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Business Delegate example runs without errors. */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java index 8e8c8eddc73d..4b8e0ee77d5c 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.business.delegate; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - - import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -/** - * Tests for the {@link BusinessDelegate} - */ +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Tests for the {@link BusinessDelegate} */ class BusinessDelegateTest { private NetflixService netflixService; @@ -62,8 +59,8 @@ void setup() { } /** - * In this example the client ({@link MobileClient}) utilizes a business delegate ( - * {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate + * In this example the client ({@link MobileClient}) utilizes a business delegate ( {@link + * BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate * service and makes the service call. */ @Test diff --git a/bytecode/README.md b/bytecode/README.md index c13565eb1378..889354cf3d04 100644 --- a/bytecode/README.md +++ b/bytecode/README.md @@ -1,196 +1,210 @@ --- -title: Bytecode +title: "Bytecode Pattern in Java: Interpreting Instructions with Custom Virtual Machines" +shortTitle: Bytecode +description: "Explore the Bytecode design pattern in Java, including its implementation, real-world examples, and use cases for efficient virtual machine instruction handling." category: Behavioral language: en tag: - - Game programming + - Abstraction + - Code simplification + - Encapsulation + - Game programming + - Performance + - Runtime --- -## Intent +## Intent of Bytecode Design Pattern -Allows encoding behavior as instructions for a virtual machine. +The Bytecode design pattern in Java allows encoding behavior as instructions for a virtual machine, making it a powerful tool in game development and other applications. -## Explanation +## Detailed Explanation of Bytecode Pattern with Real-World Examples -Real world example +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 machine. +> An analogous real-world example of the Bytecode design pattern can be seen in the process of translating a book into multiple languages. Instead of directly translating the book from the original language into every other language, the book is first translated into a common intermediate language, like Esperanto. This intermediate version is easier to translate because it is simpler and more structured. Translators for each target language then translate from Esperanto into their specific languages. This approach ensures consistency, reduces errors, and simplifies the translation process, similar to how bytecode serves as an intermediate representation to optimize and facilitate the execution of high-level programming languages across different platforms. In plain words > Bytecode pattern enables behavior driven by data instead of code. -[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) documentation -states: +[gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) documentation states: > An instruction set defines the low-level operations that can be performed. A series of instructions is encoded as a sequence of bytes. A virtual machine executes these instructions one at a time, using a stack for intermediate values. By combining instructions, complex high-level behavior can be defined. -**Programmatic Example** +## Programmatic Example of Bytecode Pattern in Java + +In this programmatic example, we show how the Bytecode pattern in Java can simplify the execution of complex virtual machine instructions through a well-defined set of operations. This real-world example demonstrates how the Bytecode design pattern in Java can streamline game programming by allowing wizards' behavior to be easily adjusted through bytecode instructions. + +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 machine. One of the most important game objects is the `Wizard` class. ```java + @AllArgsConstructor @Setter @Getter @Slf4j public class Wizard { - 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 spawnParticles() { - LOGGER.info("Spawning particles"); - 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 spawnParticles() { + LOGGER.info("Spawning particles"); + numberOfSpawnedParticles++; + } } ``` Next, we show the available instructions for our virtual machine. Each of the instructions has its own semantics on how it operates with the stack data. For example, the ADD instruction takes the top two items from the stack, adds them together and pushes the result to the stack. ```java + @AllArgsConstructor @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 + + // Other properties and methods... } ``` At the heart of our example is the `VirtualMachine` class. It takes instructions as input and executes them to provide the game object behavior. ```java + @Getter @Slf4j public class VirtualMachine { - private final Stack stack = new Stack<>(); - - 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()); + private final Stack stack = new Stack<>(); + + 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 void setHealth(int wizard, int amount) { - wizards[wizard].setHealth(amount); - } - // other setters -> - // ... + public void setHealth(int wizard, int amount) { + wizards[wizard].setHealth(amount); + } + + // Other properties and methods... } ``` Now we can show the full example utilizing the virtual machine. ```java - public static void main(String[] args) { +public static void main(String[] args) { 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")); - } + 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(String.format(HEALTH_PATTERN, "GET"))); + 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(String.format(HEALTH_PATTERN, "SET"))); +} ``` Here is the console output. @@ -210,11 +224,9 @@ Here is the console output. 16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains [] ``` -## Class diagram - -![alt text](./etc/bytecode.urm.png "Bytecode class diagram") +Utilizing the Bytecode design pattern in Java can significantly enhance the flexibility and maintainability of your virtual machine-based applications. -## Applicability +## When to Use the Bytecode Pattern in Java Use the Bytecode pattern when you have a lot of behavior you need to define and your game’s implementation language isn’t a good fit because: @@ -222,13 +234,13 @@ Use the Bytecode pattern when you have a lot of behavior you need to define and * Iterating on it takes too long due to slow compile times or other tooling issues. * It has too much trust. If you want to ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of the codebase. -## Known Uses +## Real-World Applications of Bytecode Pattern in Java * Java Virtual Machine (JVM) uses bytecode to allow Java programs to run on any device that has JVM installed * Python compiles its scripts to bytecode which is then interpreted by Python Virtual Machine * The .NET Framework uses a form of bytecode called Microsoft Intermediate Language (MSIL) -## Consequences +## Benefits and Trade-offs of Bytecode Pattern Benefits: @@ -241,13 +253,14 @@ Trade-offs: * Overhead: Running bytecode typically involves more overhead than running native code, potentially affecting performance. * Complexity: Implementing and maintaining a VM adds complexity to the system. -## Related patterns +## Related Java Design Patterns * [Interpreter](https://java-design-patterns.com/patterns/interpreter/) is often used within the implementation of VMs to interpret bytecode instructions * [Command](https://java-design-patterns.com/patterns/command/): Bytecode instructions can be seen as commands executed by the VM. * [Factory Method](https://java-design-patterns.com/patterns/factory-method/): VMs may use factory methods to instantiate operations or instructions defined in the bytecode. -## Credits +## References and Credits -* [Game programming patterns](http://gameprogrammingpatterns.com/bytecode.html) +* [Game Programming Patterns](https://amzn.to/3K96fOn) * [Programming Language Pragmatics](https://amzn.to/49Tusnn) +* [Bytecode (Game Programming Patterns)](http://gameprogrammingpatterns.com/bytecode.html) diff --git a/bytecode/pom.xml b/bytecode/pom.xml index eabd294e4ced..2fb01fe2f914 100644 --- a/bytecode/pom.xml +++ b/bytecode/pom.xml @@ -34,6 +34,14 @@ 4.0.0 bytecode + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/App.java b/bytecode/src/main/java/com/iluwatar/bytecode/App.java index c2e3a3b10f86..9293b5876ef5 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/App.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/App.java @@ -58,9 +58,7 @@ public class App { */ 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)); diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java index 91d394234191..25330e73fd78 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java @@ -27,24 +27,21 @@ import lombok.AllArgsConstructor; import lombok.Getter; -/** - * Representation of instructions understandable by virtual machine. - */ +/** Representation of instructions understandable by virtual machine. */ @AllArgsConstructor @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 + 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 + 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 private final int intValue; diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java index 9144288ce65b..7f835d402f15 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java @@ -29,9 +29,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * Implementation of virtual machine. - */ +/** Implementation of virtual machine. */ @Getter @Slf4j public class VirtualMachine { @@ -40,19 +38,13 @@ public class VirtualMachine { private final Wizard[] wizards = new Wizard[2]; - /** - * No-args constructor. - */ + /** No-args constructor. */ 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); + 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); } - /** - * Constructor taking the wizards as arguments. - */ + /** Constructor taking the wizards as arguments. */ public VirtualMachine(Wizard wizard1, Wizard wizard2) { wizards[0] = wizard1; wizards[1] = wizard2; @@ -67,59 +59,59 @@ 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. + 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: + } + case SET_AGILITY -> { var amount = stack.pop(); var wizard = stack.pop(); setAgility(wizard, amount); - break; - case SET_WISDOM: - amount = stack.pop(); - wizard = stack.pop(); + } + case SET_WISDOM -> { + var amount = stack.pop(); + var wizard = stack.pop(); setWisdom(wizard, amount); - break; - case SET_HEALTH: - amount = stack.pop(); - wizard = stack.pop(); + } + case SET_HEALTH -> { + var amount = stack.pop(); + var wizard = stack.pop(); setHealth(wizard, amount); - break; - case GET_HEALTH: - wizard = stack.pop(); + } + case GET_HEALTH -> { + var wizard = stack.pop(); stack.push(getHealth(wizard)); - break; - case GET_AGILITY: - wizard = stack.pop(); + } + case GET_AGILITY -> { + var wizard = stack.pop(); stack.push(getAgility(wizard)); - break; - case GET_WISDOM: - wizard = stack.pop(); + } + case GET_WISDOM -> { + var wizard = stack.pop(); stack.push(getWisdom(wizard)); - break; - case ADD: + } + case ADD -> { var a = stack.pop(); var b = stack.pop(); stack.push(a + b); - break; - case DIVIDE: - a = stack.pop(); - b = stack.pop(); + } + case DIVIDE -> { + var a = stack.pop(); + var b = stack.pop(); stack.push(b / a); - break; - case PLAY_SOUND: - wizard = stack.pop(); + } + case PLAY_SOUND -> { + var wizard = stack.pop(); getWizards()[wizard].playSound(); - break; - case SPAWN_PARTICLES: - wizard = stack.pop(); + } + case SPAWN_PARTICLES -> { + var wizard = stack.pop(); getWizards()[wizard].spawnParticles(); - break; - default: + } + default -> { throw new IllegalArgumentException("Invalid instruction value"); + } } LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack()); } diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java index 6501ac9a38f5..d45a2aa55787 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java @@ -26,9 +26,7 @@ import com.iluwatar.bytecode.Instruction; -/** - * Utility class used for instruction validation and conversion. - */ +/** Utility class used for instruction validation and conversion. */ public class InstructionConverterUtil { /** * Converts instructions represented as String. diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java index c56e849399b0..72d00eb34fb3 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.bytecode; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java index b6ad5dfe6925..1d9a5539f51b 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Test for {@link VirtualMachine} - */ +/** Test for {@link VirtualMachine} */ class VirtualMachineTest { @Test @@ -55,7 +53,7 @@ void testSetHealth() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // health amount + bytecode[3] = 50; // health amount bytecode[4] = SET_HEALTH.getIntValue(); var vm = new VirtualMachine(); @@ -71,7 +69,7 @@ void testSetAgility() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // agility amount + bytecode[3] = 50; // agility amount bytecode[4] = SET_AGILITY.getIntValue(); var vm = new VirtualMachine(); @@ -87,7 +85,7 @@ void testSetWisdom() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // wisdom amount + bytecode[3] = 50; // wisdom amount bytecode[4] = SET_WISDOM.getIntValue(); var vm = new VirtualMachine(); @@ -103,7 +101,7 @@ void testGetHealth() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // health amount + bytecode[3] = 50; // health amount bytecode[4] = SET_HEALTH.getIntValue(); bytecode[5] = LITERAL.getIntValue(); bytecode[6] = wizardNumber; diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java index 494fc9a2e8c4..9dadba1eaf21 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Test for {@link InstructionConverterUtil} - */ +/** Test for {@link InstructionConverterUtil} */ class InstructionConverterUtilTest { @Test @@ -44,8 +42,9 @@ void testEmptyInstruction() { @Test void testInstructions() { - var instructions = "LITERAL 35 SET_HEALTH SET_WISDOM SET_AGILITY PLAY_SOUND" - + " SPAWN_PARTICLES GET_HEALTH ADD DIVIDE"; + var instructions = + "LITERAL 35 SET_HEALTH SET_WISDOM SET_AGILITY PLAY_SOUND" + + " SPAWN_PARTICLES GET_HEALTH ADD DIVIDE"; var bytecode = InstructionConverterUtil.convertToByteCode(instructions); @@ -61,5 +60,4 @@ void testInstructions() { Assertions.assertEquals(Instruction.ADD.getIntValue(), bytecode[8]); Assertions.assertEquals(Instruction.DIVIDE.getIntValue(), bytecode[9]); } - } diff --git a/caching/README.md b/caching/README.md index 001b6ec1ca76..b923da87b3da 100644 --- a/caching/README.md +++ b/caching/README.md @@ -1,202 +1,219 @@ --- -title: Caching +title: "Caching Pattern in Java: Accelerating Data Access Speeds" +shortTitle: Caching +description: "Learn how to optimize performance with the Java Caching Design Pattern. Explore various caching strategies, real-world examples, and implementation techniques for efficient resource management." category: Performance optimization language: en tag: - Caching + - Data access - Performance - - Cloud distributed + - Resource management --- -## Intent - -The caching pattern avoids expensive re-acquisition of resources by not releasing them immediately after use. The resources retain their identity, are kept in some fast-access storage, and are re-used to avoid having to acquire them again. - ## Also known as * Cache * Temporary Storage -## Explanation +## Intent of Caching Design Pattern + +The Java Caching Design Pattern is crucial for performance optimization and resource management. It involves various caching strategies such as write-through, read-through, and LRU cache to ensure efficient data access. The caching pattern avoids expensive re-acquisition of resources by not releasing them immediately after use. The resources retain their identity, are kept in some fast-access storage, and are re-used to avoid having to acquire them again. + +## Detailed Explanation of Caching Pattern with Real-World Examples -Real world example +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. +> A real-world example of the Caching Design Pattern in Java is a library's catalog system. By caching frequently searched book results, the system reduces database load and enhances performance. When patrons frequently search for popular books, the system can cache the results of these searches. Instead of querying the database every time a user searches for a popular book, the system quickly retrieves the results from the cache. This reduces the load on the database and provides faster response times for users, enhancing their overall experience. However, the system must also ensure that the cache is updated when new books are added or existing ones are checked out, to maintain accurate information. In plain words > Caching pattern keeps frequently needed data in fast-access storage to improve performance. -Wikipedia says: +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. -**Programmatic Example** +## Programmatic Example of Caching Pattern in Java + +In this programmatic example, we demonstrate different Java caching strategies, including write-through, write-around, and write-behind, using a user account management system. + +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. Let's first look at the data layer of our application. The interesting classes are `UserAccount` which is a simple Java object containing the user account details, and `DbManager` interface which handles reading and writing of these objects to/from database. ```java + @Data @AllArgsConstructor @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 disconnect(); - - UserAccount readFromDb(String userId); - UserAccount writeToDb(UserAccount userAccount); - UserAccount updateDb(UserAccount userAccount); - UserAccount upsertDb(UserAccount userAccount); + void connect(); + + void disconnect(); + + UserAccount readFromDb(String userId); + + UserAccount writeToDb(UserAccount userAccount); + + UserAccount updateDb(UserAccount userAccount); + + UserAccount upsertDb(UserAccount userAccount); } ``` -In the example, we are demonstrating various different caching policies +In the example, we are demonstrating various different caching policies. The following caching strategies are implemented in Java: Write-through, Write-around, Write-behind, and Cache-aside. Each strategy offers unique benefits for improving performance and reducing load on the database. * Write-through writes data to the cache and DB in a single transaction * Write-around writes data immediately into the DB instead of the cache -* Write-behind writes data into the cache initially whilst the data is only written into the DB - when the cache is full -* Cache-aside pushes the responsibility of keeping the data synchronized in both data sources to - the application itself -* Read-through strategy is also included in the aforementioned strategies, and it returns data from - the cache to the caller if it exists, otherwise queries from DB and stores it into the cache for - future use. - +* Write-behind writes data into the cache initially whilst the data is only written into the DB when the cache is full +* Cache-aside pushes the responsibility of keeping the data synchronized in both data sources to the application itself +* Read-through strategy is also included in the aforementioned strategies, and it returns data from the cache to the caller if it exists, otherwise queries from DB and stores it into the cache for future use. + The cache implementation in `LruCache` is a hash table accompanied by a doubly linked-list. The linked-list helps in capturing and maintaining the LRU data in the cache. When data is queried (from the cache), added (to the cache), or updated, the data is moved to the front of the list to depict itself as the most-recently-used data. The LRU data is always at the end of the list. ```java + @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 ... */ - - 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; + + // Other properties and methods... + + 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 remove(Node node) { /* ... */ } - public void setHead(Node node) { /* ... */ } - public void invalidate(String userId) { /* ... */ } - public boolean isFull() { /* ... */ } - public UserAccount getLruData() { /* ... */ } - public void clear() { /* ... */ } - public List getCacheDataInListForm() { /* ... */ } - public void setCapacity(int newCapacity) { /* ... */ } + + 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 boolean contains(String userId) { + return cache.containsKey(userId); + } + + public void remove(Node node) { /* ... */ } + + public void setHead(Node node) { /* ... */ } + + public void invalidate(String userId) { /* ... */ } + + public boolean isFull() { /* ... */ } + + public UserAccount getLruData() { /* ... */ } + + public void clear() { /* ... */ } + + public List getCacheDataInListForm() { /* ... */ } + + public void setCapacity(int newCapacity) { /* ... */ } } ``` The next layer we are going to look at is `CacheStore` which implements the different caching strategies. ```java + @Slf4j public class CacheStore { - private static final int CAPACITY = 3; - private static LruCache cache; - private final DbManager dbManager; - - /* ... details omitted ... */ - - public UserAccount readThrough(final String userId) { - if (cache.contains(userId)) { - LOGGER.info("# Found in Cache!"); - return cache.get(userId); + private static final int CAPACITY = 3; + private static LruCache cache; + private final DbManager dbManager; + + // Other properties and methods... + + 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 ... } ``` @@ -204,106 +221,211 @@ public class CacheStore { `AppManager` helps to bridge the gap in communication between the main class and the application's back-end. DB connection is initialized through this class. The chosen caching strategy/policy is also initialized here. Before the cache can be used, the size of the cache has to be set. Depending on the chosen caching policy, `AppManager` will call the appropriate function in the `CacheStore` class. ```java + @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() { - } + private AppManager() { + } - public void initDb() { /* ... */ } + public void initDb() { /* ... */ } - public static void initCachingPolicy(CachingPolicy policy) { /* ... */ } + public static void initCachingPolicy(CachingPolicy policy) { /* ... */ } - public static void initCacheCapacity(int capacity) { /* ... */ } + 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); + 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; } - 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 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 static String printCacheContent() { - return CacheStore.print(); - } + public static String printCacheContent() { + return CacheStore.print(); + } - /* ... details omitted ... */ + // Other properties and methods... } ``` Here is what we do in the main class of the application. ```java + @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); - - var userAccount1 = new UserAccount("001", "John", "He is a boy."); - - appManager.save(userAccount1); - LOGGER.info(appManager.printCacheContent()); - appManager.find("001"); - appManager.find("001"); - } - - public void useReadThroughAndWriteAroundStrategy() { /* ... */ } - - public void useReadThroughAndWriteBehindStrategy() { /* ... */ } - - public void useCacheAsideStrategy() { /* ... */ } + + public void useReadAndWriteThroughStrategy() { + LOGGER.info("# CachingPolicy.THROUGH"); + appManager.initCachingPolicy(CachingPolicy.THROUGH); + + var userAccount1 = new UserAccount("001", "John", "He is a boy."); + + appManager.save(userAccount1); + LOGGER.info(appManager.printCacheContent()); + appManager.find("001"); + appManager.find("001"); + } + + public void useReadThroughAndWriteAroundStrategy() { /* ... */ } + + public void useReadThroughAndWriteBehindStrategy() { /* ... */ } + + public void useCacheAsideStrategy() { /* ... */ } } ``` -## Class diagram +The program output: + +``` +17:00:56.302 [main] INFO com.iluwatar.caching.App -- Using the 'in Memory' database to run the application. +17:00:56.304 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.THROUGH +17:00:56.305 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.308 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=001, userName=John, additionalInfo=He is a boy.) +---- +17:00:56.308 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 001 in cache +17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache! +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 001 in cache +17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache! +17:00:56.309 [main] INFO com.iluwatar.caching.App -- ============================================== +17:00:56.309 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.AROUND +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.309 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +---- +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache +17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Not found in cache! Go to DB!! +17:00:56.309 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=002, userName=Jane, additionalInfo=She is a girl.) +---- +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache +17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache! +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.309 [main] INFO com.iluwatar.caching.LruCache -- # 002 has been updated! Removing older version from cache... +17:00:56.309 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +---- +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache +17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Not found in cache! Go to DB!! +17:00:56.309 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=002, userName=Jane G., additionalInfo=She is a girl.) +---- +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache +17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache! +17:00:56.309 [main] INFO com.iluwatar.caching.App -- ============================================== +17:00:56.309 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.BEHIND +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.310 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=005, userName=Isaac, additionalInfo=He is allergic to mustard.) +UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.) +UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.) +---- +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 003 in cache +17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Found in cache! +17:00:56.310 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.) +UserAccount(userId=005, userName=Isaac, additionalInfo=He is allergic to mustard.) +UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.) +---- +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Cache is FULL! Writing LRU data to DB... +17:00:56.310 [main] INFO com.iluwatar.caching.LruCache -- # Cache is FULL! Removing 004 from cache... +17:00:56.310 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=006, userName=Yasha, additionalInfo=She is an only child.) +UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.) +UserAccount(userId=005, userName=Isaac, additionalInfo=He is allergic to mustard.) +---- +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 004 in cache +17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Not found in Cache! +17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Cache is FULL! Writing LRU data to DB... +17:00:56.310 [main] INFO com.iluwatar.caching.LruCache -- # Cache is FULL! Removing 005 from cache... +17:00:56.310 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.) +UserAccount(userId=006, userName=Yasha, additionalInfo=She is an only child.) +UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.) +---- +17:00:56.310 [main] INFO com.iluwatar.caching.App -- ============================================== +17:00:56.310 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.ASIDE +17:00:56.310 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +---- +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record! +17:00:56.310 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +---- +17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 003 in cache +17:00:56.313 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.) +---- +17:00:56.313 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 004 in cache +17:00:56.313 [main] INFO com.iluwatar.caching.App -- +--CACHE CONTENT-- +UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.) +UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.) +---- +17:00:56.313 [main] INFO com.iluwatar.caching.App -- ============================================== +17:00:56.314 [Thread-0] INFO com.iluwatar.caching.CacheStore -- # flushCache... +``` -![alt text](./etc/caching.png "Caching") +Implementing the Java Caching Design Pattern using various strategies like LRU cache and write-through caching significantly enhances application performance and scalability. -## Applicability +## When to Use the Caching Pattern in Java Use the Caching pattern when @@ -311,14 +433,14 @@ Use the Caching pattern when * In scenarios where the cost of recomputing or re-fetching data is significantly higher than storing and retrieving it from cache * For read-heavy applications with relatively static data or data that changes infrequently -## Known Uses +## Real-World Applications of Caching Pattern in Java * Web page caching to reduce server load and improve response time * Database query caching to avoid repeated expensive SQL queries * Caching results of CPU-intensive computations * Content Delivery Networks (CDNs) for caching static resources like images, CSS, and JavaScript files closer to the end users -## Consequences +## Benefits and Trade-offs of Caching Pattern Benefits: @@ -332,22 +454,21 @@ Trade-Offs: * Resource Utilization: Requires additional memory or storage resources to maintain the cache * Stale Data: There's a risk of serving outdated data if the cache is not properly invalidated or updated when the underlying data changes -## Related patterns +## Related Java Design Patterns * [Proxy](https://java-design-patterns.com/patterns/proxy/): Caching can be implemented using the Proxy pattern, where the proxy object intercepts requests and returns cached data if available * [Observer](https://java-design-patterns.com/patterns/observer/): Can be used to notify the cache when the underlying data changes, so that it can be updated or invalidated accordingly * [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used to add caching behavior to an existing object without modifying its code * [Strategy](https://java-design-patterns.com/patterns/strategy/): Different caching strategies can be implemented using the Strategy pattern, allowing the application to switch between them at runtime -## Credits +## References and Credits -* [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) -* [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177) -* [Cache-Aside pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside) -* [Java EE 8 High Performance: Master techniques such as memory optimization, caching, concurrency, and multithreading to achieve maximum performance from your enterprise applications](https://www.amazon.com/gp/product/178847306X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=178847306X&linkId=e948720055599f248cdac47da9125ff4) -* [Java Performance: In-Depth Advice for Tuning and Programming Java 8, 11, and Beyond](https://www.amazon.com/gp/product/1492056111/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1492056111&linkId=7e553581559b9ec04221259e52004b08) -* [Effective Java](https://www.amazon.com/gp/product/B078H61SCH/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B078H61SCH&linkId=f06607a0b48c76541ef19c5b8b9e7882) -* [Java Performance: The Definitive Guide: Getting the Most Out of Your Code](https://www.amazon.com/gp/product/1449358454/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1449358454&linkId=475c18363e350630cc0b39ab681b2687) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [High Performance Browser Networking](https://amzn.to/3TiNNY4) +* [Java EE 8 High Performance](https://amzn.to/44T8vmH) +* [Java Performance: In-Depth Advice for Tuning and Programming Java 8, 11, and Beyond](https://amzn.to/3yyD58W) +* [Java Performance: The Definitive Guide: Getting the Most Out of Your Code](https://amzn.to/3Wu5neF) * [Patterns of Enterprise Application Architecture](https://amzn.to/3PMAHRZ) * [Scalable Internet Architectures](https://amzn.to/48V3ni9) -* [High Performance Browser Networking](https://amzn.to/3TiNNY4) +* [Write-through, write-around, write-back: Cache explained (ComputerWeekly)](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) +* [Cache-Aside Pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside) diff --git a/caching/etc/caching.urm.puml b/caching/etc/caching.urm.puml index a9dae801eb20..f6f2e4732005 100644 --- a/caching/etc/caching.urm.puml +++ b/caching/etc/caching.urm.puml @@ -13,7 +13,7 @@ package com.iluwatar.caching { - LOGGER : Logger {static} + App() + main(args : String[]) {static} - + useCacheAsideStategy() + + useCacheAsideStrategy() + useReadAndWriteThroughStrategy() + useReadThroughAndWriteAroundStrategy() + useReadThroughAndWriteBehindStrategy() @@ -116,4 +116,4 @@ Node --> "-previous" Node AppManager --> "-cachingPolicy" CachingPolicy Node --> "-userAccount" UserAccount CacheStore --> "-cache" LruCache -@enduml \ No newline at end of file +@enduml diff --git a/caching/pom.xml b/caching/pom.xml index d7470b5e46d8..3ffce74af493 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -34,6 +34,14 @@ caching + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index 3adf7e681f9a..8d6af6c090d8 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -29,59 +29,44 @@ import lombok.extern.slf4j.Slf4j; /** - * The Caching pattern describes how to avoid expensive re-acquisition of - * resources by not releasing the resources immediately after their use. - * The resources retain their identity, are kept in some fast-access storage, - * and are re-used to avoid having to acquire them again. There are four main - * caching strategies/techniques in this pattern; each with their own pros and - * cons. They are write-through which writes data to the cache and - * DB in a single transaction, write-around which writes data - * immediately into the DB instead of the cache, write-behind - * which writes data into the cache initially whilst the data is only - * written into the DB when the cache is full, and cache-aside - * which pushes the responsibility of keeping the data synchronized in both - * data sources to the application itself. The read-through - * strategy is also included in the mentioned four strategies -- - * returns data from the cache to the caller if it exists else - * queries from DB and stores it into the cache for future use. These strategies - * determine when the data in the cache should be written back to the backing - * store (i.e. Database) and help keep both data sources - * synchronized/up-to-date. This pattern can improve performance and also helps - * to maintainconsistency between data held in the cache and the data in - * the underlying data store. + * The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing + * the resources immediately after their use. The resources retain their identity, are kept in some + * fast-access storage, and are re-used to avoid having to acquire them again. There are four main + * caching strategies/techniques in this pattern; each with their own pros and cons. They are + * write-through which writes data to the cache and DB in a single transaction, + * write-around which writes data immediately into the DB instead of the cache, + * write-behind which writes data into the cache initially whilst the data is only written + * into the DB when the cache is full, and cache-aside which pushes the responsibility + * of keeping the data synchronized in both data sources to the application itself. The + * read-through strategy is also included in the mentioned four strategies -- returns data + * from the cache to the caller if it exists else queries from DB and stores it into + * the cache for future use. These strategies determine when the data in the cache should be written + * back to the backing store (i.e. Database) and help keep both data sources + * synchronized/up-to-date. This pattern can improve performance and also helps to + * maintainconsistency between data held in the cache and the data in the underlying data store. * - *

In this example, the user account ({@link UserAccount}) entity is used - * as the underlying application data. The cache itself is implemented as an - * internal (Java) data structure. It adopts a Least-Recently-Used (LRU) - * strategy for evicting data from itself when its full. The four - * strategies are individually tested. The testing of the cache is restricted - * towards saving and querying of user accounts from the - * underlying data store( {@link DbManager}). The main class ( {@link App} - * is not aware of the underlying mechanics of the application - * (i.e. save and query) and whether the data is coming from the cache or the - * DB (i.e. separation of concern). The AppManager ({@link AppManager}) handles - * the transaction of data to-and-from the underlying data store (depending on - * the preferred caching policy/strategy). - *

- * {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> - * DBManager} - *

+ *

In this example, the user account ({@link UserAccount}) entity is used as the underlying + * application data. The cache itself is implemented as an internal (Java) data structure. It adopts + * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The four + * strategies are individually tested. The testing of the cache is restricted towards saving and + * querying of user accounts from the underlying data store( {@link DbManager}). The main class ( + * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and + * whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager + * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store + * (depending on the preferred caching policy/strategy). * - *

- * There are 2 ways to launch the application. - * - to use "in Memory" database. - * - to use the MongoDb as a database + *

{@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager} * - * To run the application with "in Memory" database, just launch it without parameters - * Example: 'java -jar app.jar' + *

There are 2 ways to launch the application. - to use "in Memory" database. - to use the + * MongoDb as a database * - * To run the application with MongoDb you need to be installed the MongoDb - * in your system, or to launch it in the docker container. - * You may launch docker container from the root of current module with command: - * 'docker-compose up' - * Then you can start the application with parameter --mongo - * Example: 'java -jar app.jar --mongo' - *

+ *

To run the application with "in Memory" database, just launch it without parameters Example: + * 'java -jar app.jar' + * + *

To run the application with MongoDb you need to be installed the MongoDb in your system, or to + * launch it in the docker container. You may launch docker container from the root of current + * module with command: 'docker-compose up' Then you can start the application with parameter + * --mongo Example: 'java -jar app.jar --mongo' * * @see CacheStore * @see LruCache @@ -89,13 +74,10 @@ */ @Slf4j public class App { - /** - * Constant parameter name to use mongoDB. - */ + /** Constant parameter name to use mongoDB. */ private static final String USE_MONGO_DB = "--mongo"; - /** - * Application manager. - */ + + /** Application manager. */ private final AppManager appManager; /** @@ -133,7 +115,7 @@ public static void main(final String[] args) { LOGGER.info(splitLine); app.useReadThroughAndWriteBehindStrategy(); LOGGER.info(splitLine); - app.useCacheAsideStategy(); + app.useCacheAsideStrategy(); LOGGER.info(splitLine); } @@ -152,9 +134,7 @@ private static boolean isDbMongo(final String[] args) { return false; } - /** - * Read-through and write-through. - */ + /** Read-through and write-through. */ public void useReadAndWriteThroughStrategy() { LOGGER.info("# CachingPolicy.THROUGH"); appManager.initCachingPolicy(CachingPolicy.THROUGH); @@ -167,9 +147,7 @@ public void useReadAndWriteThroughStrategy() { appManager.find("001"); } - /** - * Read-through and write-around. - */ + /** Read-through and write-around. */ public void useReadThroughAndWriteAroundStrategy() { LOGGER.info("# CachingPolicy.AROUND"); appManager.initCachingPolicy(CachingPolicy.AROUND); @@ -189,22 +167,14 @@ public void useReadThroughAndWriteAroundStrategy() { appManager.find("002"); } - /** - * Read-through and write-behind. - */ + /** Read-through and write-behind. */ public void useReadThroughAndWriteBehindStrategy() { LOGGER.info("# CachingPolicy.BEHIND"); appManager.initCachingPolicy(CachingPolicy.BEHIND); - var userAccount3 = new UserAccount("003", - "Adam", - "He likes food."); - var userAccount4 = new UserAccount("004", - "Rita", - "She hates cats."); - var userAccount5 = new UserAccount("005", - "Isaac", - "He is allergic to mustard."); + var userAccount3 = new UserAccount("003", "Adam", "He likes food."); + var userAccount4 = new UserAccount("004", "Rita", "She hates cats."); + var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard."); appManager.save(userAccount3); appManager.save(userAccount4); @@ -212,32 +182,22 @@ public void useReadThroughAndWriteBehindStrategy() { LOGGER.info(appManager.printCacheContent()); appManager.find("003"); LOGGER.info(appManager.printCacheContent()); - UserAccount userAccount6 = new UserAccount("006", - "Yasha", - "She is an only child."); + UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child."); appManager.save(userAccount6); LOGGER.info(appManager.printCacheContent()); appManager.find("004"); LOGGER.info(appManager.printCacheContent()); } - /** - * Cache-Aside. - */ - public void useCacheAsideStategy() { + /** Cache-Aside. */ + public void useCacheAsideStrategy() { LOGGER.info("# CachingPolicy.ASIDE"); appManager.initCachingPolicy(CachingPolicy.ASIDE); LOGGER.info(appManager.printCacheContent()); - var userAccount3 = new UserAccount("003", - "Adam", - "He likes food."); - var userAccount4 = new UserAccount("004", - "Rita", - "She hates cats."); - var userAccount5 = new UserAccount("005", - "Isaac", - "He is allergic to mustard."); + var userAccount3 = new UserAccount("003", "Adam", "He likes food."); + var userAccount4 = new UserAccount("004", "Rita", "She hates cats."); + var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard."); appManager.save(userAccount3); appManager.save(userAccount4); appManager.save(userAccount5); diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index 3d298537521f..c1d21fea33fe 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -29,26 +29,21 @@ import lombok.extern.slf4j.Slf4j; /** - * AppManager helps to bridge the gap in communication between the main class - * and the application's back-end. DB connection is initialized through this - * class. The chosen caching strategy/policy is also initialized here. - * Before the cache can be used, the size of the cache has to be set. - * Depending on the chosen caching policy, AppManager will call the - * appropriate function in the CacheStore class. + * AppManager helps to bridge the gap in communication between the main class and the application's + * back-end. DB connection is initialized through this class. The chosen caching strategy/policy is + * also initialized here. Before the cache can be used, the size of the cache has to be set. + * Depending on the chosen caching policy, AppManager will call the appropriate function in the + * CacheStore class. */ @Slf4j public class AppManager { - /** - * Caching Policy. - */ + /** Caching Policy. */ private CachingPolicy cachingPolicy; - /** - * Database Manager. - */ + + /** Database Manager. */ private final DbManager dbManager; - /** - * Cache Store. - */ + + /** Cache Store. */ private final CacheStore cacheStore; /** @@ -62,9 +57,9 @@ public AppManager(final DbManager newDbManager) { } /** - * Developer/Tester is able to choose whether the application should use - * MongoDB as its underlying data storage or a simple Java data structure - * to (temporarily) store the data/objects during runtime. + * Developer/Tester is able to choose whether the application should use MongoDB as its underlying + * data storage or a simple Java data structure to (temporarily) store the data/objects during + * runtime. */ public void initDb() { dbManager.connect(); @@ -91,8 +86,7 @@ public void initCachingPolicy(final CachingPolicy policy) { */ public UserAccount find(final String userId) { LOGGER.info("Trying to find {} in cache", userId); - if (cachingPolicy == CachingPolicy.THROUGH - || cachingPolicy == CachingPolicy.AROUND) { + if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) { return cacheStore.readThrough(userId); } else if (cachingPolicy == CachingPolicy.BEHIND) { return cacheStore.readThroughWithWriteBackPolicy(userId); @@ -147,12 +141,12 @@ private void saveAside(final UserAccount userAccount) { */ private UserAccount findAside(final String userId) { return Optional.ofNullable(cacheStore.get(userId)) - .or(() -> { - Optional userAccount = - Optional.ofNullable(dbManager.readFromDb(userId)); + .or( + () -> { + Optional userAccount = Optional.ofNullable(dbManager.readFromDb(userId)); userAccount.ifPresent(account -> cacheStore.set(userId, account)); return userAccount; }) - .orElse(null); + .orElse(null); } } diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 2c7184f57b3f..b26c52b22159 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -30,27 +30,21 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -/** - * The caching strategies are implemented in this class. - */ +/** The caching strategies are implemented in this class. */ @Slf4j public class CacheStore { - /** - * Cache capacity. - */ + /** Cache capacity. */ private static final int CAPACITY = 3; - /** - * Lru cache see {@link LruCache}. - */ + /** Lru cache see {@link LruCache}. */ private LruCache cache; - /** - * DbManager. - */ + + /** DbManager. */ private final DbManager dbManager; /** * Cache Store. + * * @param dataBaseManager {@link DbManager} */ public CacheStore(final DbManager dataBaseManager) { @@ -60,6 +54,7 @@ public CacheStore(final DbManager dataBaseManager) { /** * Init cache capacity. + * * @param capacity int */ public void initCapacity(final int capacity) { @@ -72,6 +67,7 @@ public void initCapacity(final int capacity) { /** * Get user account using read-through cache. + * * @param userId {@link String} * @return {@link UserAccount} */ @@ -88,6 +84,7 @@ public UserAccount readThrough(final String userId) { /** * Get user account using write-through cache. + * * @param userAccount {@link UserAccount} */ public void writeThrough(final UserAccount userAccount) { @@ -101,6 +98,7 @@ public void writeThrough(final UserAccount userAccount) { /** * Get user account using write-around cache. + * * @param userAccount {@link UserAccount} */ public void writeAround(final UserAccount userAccount) { @@ -116,6 +114,7 @@ public void writeAround(final UserAccount userAccount) { /** * Get user account using read-through cache with write-back policy. + * * @param userId {@link String} * @return {@link UserAccount} */ @@ -137,6 +136,7 @@ public UserAccount readThroughWithWriteBackPolicy(final String userId) { /** * Set user account. + * * @param userAccount {@link UserAccount} */ public void writeBehind(final UserAccount userAccount) { @@ -148,18 +148,14 @@ public void writeBehind(final UserAccount userAccount) { cache.set(userAccount.getUserId(), userAccount); } - /** - * Clears cache. - */ + /** Clears cache. */ public void clearCache() { if (cache != null) { cache.clear(); } } - /** - * Writes remaining content in the cache into the DB. - */ + /** Writes remaining content in the cache into the DB. */ public void flushCache() { LOGGER.info("# flushCache..."); Optional.ofNullable(cache) @@ -171,6 +167,7 @@ public void flushCache() { /** * Print user accounts. + * * @return {@link String} */ public String print() { @@ -184,6 +181,7 @@ public String print() { /** * Delegate to backing cache store. + * * @param userId {@link String} * @return {@link UserAccount} */ @@ -193,6 +191,7 @@ public UserAccount get(final String userId) { /** * Delegate to backing cache store. + * * @param userId {@link String} * @param userAccount {@link UserAccount} */ @@ -202,6 +201,7 @@ public void set(final String userId, final UserAccount userAccount) { /** * Delegate to backing cache store. + * * @param userId {@link String} */ public void invalidate(final String userId) { diff --git a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java index dcd5711dbf6e..0ec07ced7b8f 100644 --- a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java +++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java @@ -27,31 +27,19 @@ import lombok.AllArgsConstructor; import lombok.Getter; -/** - * Enum class containing the four caching strategies implemented in the pattern. - */ +/** Enum class containing the four caching strategies implemented in the pattern. */ @AllArgsConstructor @Getter public enum CachingPolicy { - /** - * Through. - */ + /** Through. */ THROUGH("through"), - /** - * AROUND. - */ + /** AROUND. */ AROUND("around"), - /** - * BEHIND. - */ + /** BEHIND. */ BEHIND("behind"), - /** - * ASIDE. - */ + /** ASIDE. */ ASIDE("aside"); - /** - * Policy value. - */ + /** Policy value. */ private final String policy; } diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java index ef94bf482161..9c9107de6f88 100644 --- a/caching/src/main/java/com/iluwatar/caching/LruCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -30,42 +30,33 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; - /** - * Data structure/implementation of the application's cache. The data structure - * consists of a hash table attached with a doubly linked-list. The linked-list - * helps in capturing and maintaining the LRU data in the cache. When a data is - * queried (from the cache), added (to the cache), or updated, the data is - * moved to the front of the list to depict itself as the most-recently-used - * data. The LRU data is always at the end of the list. + * Data structure/implementation of the application's cache. The data structure consists of a hash + * table attached with a doubly linked-list. The linked-list helps in capturing and maintaining the + * LRU data in the cache. When a data is queried (from the cache), added (to the cache), or updated, + * the data is moved to the front of the list to depict itself as the most-recently-used data. The + * LRU data is always at the end of the list. */ @Slf4j public class LruCache { - /** - * Static class Node. - */ + /** Static class Node. */ static class Node { - /** - * user id. - */ + /** user id. */ private final String userId; - /** - * User Account. - */ + + /** User Account. */ private UserAccount userAccount; - /** - * previous. - */ + + /** previous. */ private Node previous; - /** - * next. - */ + + /** next. */ private Node next; /** * Node definition. * - * @param id String + * @param id String * @param account {@link UserAccount} */ Node(final String id, final UserAccount account) { @@ -74,21 +65,16 @@ static class Node { } } - /** - * Capacity of Cache. - */ + /** Capacity of Cache. */ private int capacity; - /** - * Cache {@link HashMap}. - */ + + /** Cache {@link HashMap}. */ private Map cache = new HashMap<>(); - /** - * Head. - */ + + /** Head. */ private Node head; - /** - * End. - */ + + /** End. */ private Node end; /** @@ -155,7 +141,7 @@ public void setHead(final Node node) { * Set user account. * * @param userAccount {@link UserAccount} - * @param userId {@link String} + * @param userId {@link String} */ public void set(final String userId, final UserAccount userAccount) { if (cache.containsKey(userId)) { @@ -195,14 +181,14 @@ public boolean contains(final String userId) { public void invalidate(final String userId) { var toBeRemoved = cache.remove(userId); if (toBeRemoved != null) { - LOGGER.info("# {} has been updated! " - + "Removing older version from cache...", userId); + LOGGER.info("# {} has been updated! " + "Removing older version from cache...", userId); remove(toBeRemoved); } } /** * Check if the cache is full. + * * @return boolean */ public boolean isFull() { @@ -218,9 +204,7 @@ public UserAccount getLruData() { return end.userAccount; } - /** - * Clear cache. - */ + /** Clear cache. */ public void clear() { head = null; end = null; diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java index 73100c0a6c24..561c942ac781 100644 --- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java +++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java @@ -29,24 +29,18 @@ import lombok.EqualsAndHashCode; import lombok.ToString; -/** - * Entity class (stored in cache and DB) used in the application. - */ +/** Entity class (stored in cache and DB) used in the application. */ @Data @AllArgsConstructor @ToString @EqualsAndHashCode public class UserAccount { - /** - * User Id. - */ + /** User Id. */ private String userId; - /** - * User Name. - */ + + /** User Name. */ private String userName; - /** - * Additional Info. - */ + + /** Additional Info. */ private String additionalInfo; } diff --git a/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java b/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java index 908fca8ea0e3..5e8fc415df4c 100644 --- a/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java +++ b/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java @@ -24,30 +24,20 @@ */ package com.iluwatar.caching.constants; -/** - * Constant class for defining constants. - */ +/** Constant class for defining constants. */ public final class CachingConstants { - /** - * User Account. - */ + /** User Account. */ public static final String USER_ACCOUNT = "user_accounts"; - /** - * User ID. - */ + + /** User ID. */ public static final String USER_ID = "userID"; - /** - * User Name. - */ + + /** User Name. */ public static final String USER_NAME = "userName"; - /** - * Additional Info. - */ + + /** Additional Info. */ public static final String ADD_INFO = "additionalInfo"; - /** - * Constructor. - */ - private CachingConstants() { - } + /** Constructor. */ + private CachingConstants() {} } diff --git a/caching/src/main/java/com/iluwatar/caching/constants/package-info.java b/caching/src/main/java/com/iluwatar/caching/constants/package-info.java index 9356e7e7b6c3..b94476cbabeb 100644 --- a/caching/src/main/java/com/iluwatar/caching/constants/package-info.java +++ b/caching/src/main/java/com/iluwatar/caching/constants/package-info.java @@ -22,7 +22,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/** - * Constants. - */ +/** Constants. */ package com.iluwatar.caching.constants; diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java index 16b60fc82d63..14b98a66b52b 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java @@ -27,19 +27,15 @@ import com.iluwatar.caching.UserAccount; /** - *

DBManager handles the communication with the underlying data store i.e. - * Database. It contains the implemented methods for querying, inserting, - * and updating data. MongoDB was used as the database for the application.

+ * DBManager handles the communication with the underlying data store i.e. Database. It contains the + * implemented methods for querying, inserting, and updating data. MongoDB was used as the database + * for the application. */ public interface DbManager { - /** - * Connect to DB. - */ + /** Connect to DB. */ void connect(); - /** - * Disconnect from DB. - */ + /** Disconnect from DB. */ void disconnect(); /** diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java index ee7a4a04b936..92031b7c95b0 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java @@ -24,15 +24,10 @@ */ package com.iluwatar.caching.database; -/** - * Creates the database connection according the input parameter. - */ +/** Creates the database connection according the input parameter. */ public final class DbManagerFactory { - /** - * Private constructor. - */ - private DbManagerFactory() { - } + /** Private constructor. */ + private DbManagerFactory() {} /** * Init database. diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java index f2fa696cca5e..e47eef55cd8c 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java +++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java @@ -40,10 +40,7 @@ import lombok.extern.slf4j.Slf4j; import org.bson.Document; -/** - * Implementation of DatabaseManager. - * implements base methods to work with MongoDb. - */ +/** Implementation of DatabaseManager. implements base methods to work with MongoDb. */ @Slf4j public class MongoDb implements DbManager { private static final String DATABASE_NAME = "admin"; @@ -56,14 +53,11 @@ void setDb(MongoDatabase db) { this.db = db; } - /** - * Connect to Db. Check th connection - */ + /** Connect to Db. Check th connection */ @Override public void connect() { - MongoCredential mongoCredential = MongoCredential.createCredential(MONGO_USER, - DATABASE_NAME, - MONGO_PASSWORD.toCharArray()); + MongoCredential mongoCredential = + MongoCredential.createCredential(MONGO_USER, DATABASE_NAME, MONGO_PASSWORD.toCharArray()); MongoClientOptions options = MongoClientOptions.builder().build(); client = new MongoClient(new ServerAddress(), mongoCredential, options); db = client.getDatabase(DATABASE_NAME); @@ -82,9 +76,8 @@ public void disconnect() { */ @Override public UserAccount readFromDb(final String userId) { - var iterable = db - .getCollection(CachingConstants.USER_ACCOUNT) - .find(new Document(USER_ID, userId)); + var iterable = + db.getCollection(CachingConstants.USER_ACCOUNT).find(new Document(USER_ID, userId)); if (iterable.first() == null) { return null; } @@ -106,11 +99,11 @@ public UserAccount readFromDb(final String userId) { */ @Override public UserAccount writeToDb(final UserAccount userAccount) { - db.getCollection(USER_ACCOUNT).insertOne( + db.getCollection(USER_ACCOUNT) + .insertOne( new Document(USER_ID, userAccount.getUserId()) - .append(USER_NAME, userAccount.getUserName()) - .append(ADD_INFO, userAccount.getAdditionalInfo()) - ); + .append(USER_NAME, userAccount.getUserName()) + .append(ADD_INFO, userAccount.getAdditionalInfo())); return userAccount; } @@ -123,10 +116,10 @@ public UserAccount writeToDb(final UserAccount userAccount) { @Override public UserAccount updateDb(final UserAccount userAccount) { Document id = new Document(USER_ID, userAccount.getUserId()); - Document dataSet = new Document(USER_NAME, userAccount.getUserName()) + Document dataSet = + new Document(USER_NAME, userAccount.getUserName()) .append(ADD_INFO, userAccount.getAdditionalInfo()); - db.getCollection(CachingConstants.USER_ACCOUNT) - .updateOne(id, new Document("$set", dataSet)); + db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(id, new Document("$set", dataSet)); return userAccount; } @@ -141,15 +134,15 @@ public UserAccount upsertDb(final UserAccount userAccount) { String userId = userAccount.getUserId(); String userName = userAccount.getUserName(); String additionalInfo = userAccount.getAdditionalInfo(); - db.getCollection(CachingConstants.USER_ACCOUNT).updateOne( + db.getCollection(CachingConstants.USER_ACCOUNT) + .updateOne( new Document(USER_ID, userId), - new Document("$set", - new Document(USER_ID, userId) - .append(USER_NAME, userName) - .append(ADD_INFO, additionalInfo) - ), - new UpdateOptions().upsert(true) - ); + new Document( + "$set", + new Document(USER_ID, userId) + .append(USER_NAME, userName) + .append(ADD_INFO, additionalInfo)), + new UpdateOptions().upsert(true)); return userAccount; } } diff --git a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java index 6155e1d69ee6..6040ca174a21 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java +++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java @@ -28,19 +28,12 @@ import java.util.HashMap; import java.util.Map; -/** - * Implementation of DatabaseManager. - * implements base methods to work with hashMap as database. - */ +/** Implementation of DatabaseManager. implements base methods to work with hashMap as database. */ public class VirtualDb implements DbManager { - /** - * Virtual DataBase. - */ + /** Virtual DataBase. */ private Map db; - /** - * Creates new HashMap. - */ + /** Creates new HashMap. */ @Override public void connect() { db = new HashMap<>(); diff --git a/caching/src/main/java/com/iluwatar/caching/database/package-info.java b/caching/src/main/java/com/iluwatar/caching/database/package-info.java index 535771a7d275..631cb4c584cd 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/package-info.java +++ b/caching/src/main/java/com/iluwatar/caching/database/package-info.java @@ -22,7 +22,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/** - * Database classes. - */ +/** Database classes. */ package com.iluwatar.caching.database; diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java index 510b3a256f5f..35e01edbc37e 100644 --- a/caching/src/test/java/com/iluwatar/caching/AppTest.java +++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java @@ -24,23 +24,20 @@ */ package com.iluwatar.caching; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Caching example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Caching example runs without errors. */ class AppTest { /** * Issue: Add at least one assertion to this test case. - *

- * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + * + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/caching/src/test/java/com/iluwatar/caching/CachingTest.java b/caching/src/test/java/com/iluwatar/caching/CachingTest.java index 0bcb83897d59..d17cff5bd2ef 100644 --- a/caching/src/test/java/com/iluwatar/caching/CachingTest.java +++ b/caching/src/test/java/com/iluwatar/caching/CachingTest.java @@ -24,20 +24,16 @@ */ package com.iluwatar.caching; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -/** - * Application test - */ +/** Application test */ class CachingTest { private App app; - /** - * Setup of application test includes: initializing DB connection and cache size/capacity. - */ + /** Setup of application test includes: initializing DB connection and cache size/capacity. */ @BeforeEach void setUp() { // VirtualDB (instead of MongoDB) was used in running the JUnit tests @@ -68,6 +64,6 @@ void testReadThroughAndWriteBehindStrategy() { @Test void testCacheAsideStrategy() { assertNotNull(app); - app.useCacheAsideStategy(); + app.useCacheAsideStrategy(); } } diff --git a/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java b/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java index 5cb130a34e33..87cc1ed6f587 100644 --- a/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java +++ b/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java @@ -24,6 +24,13 @@ */ package com.iluwatar.caching.database; +import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO; +import static com.iluwatar.caching.constants.CachingConstants.USER_ID; +import static com.iluwatar.caching.constants.CachingConstants.USER_NAME; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + import com.iluwatar.caching.UserAccount; import com.iluwatar.caching.constants.CachingConstants; import com.mongodb.client.FindIterable; @@ -34,20 +41,12 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO; -import static com.iluwatar.caching.constants.CachingConstants.USER_ID; -import static com.iluwatar.caching.constants.CachingConstants.USER_NAME; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; - class MongoDbTest { private static final String ID = "123"; private static final String NAME = "Some user"; private static final String ADDITIONAL_INFO = "Some app Info"; - @Mock - MongoDatabase db; + @Mock MongoDatabase db; private MongoDb mongoDb = new MongoDb(); private UserAccount userAccount; @@ -66,9 +65,8 @@ void connect() { @Test void readFromDb() { - Document document = new Document(USER_ID, ID) - .append(USER_NAME, NAME) - .append(ADD_INFO, ADDITIONAL_INFO); + Document document = + new Document(USER_ID, ID).append(USER_NAME, NAME).append(ADD_INFO, ADDITIONAL_INFO); MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); @@ -77,27 +75,36 @@ void readFromDb() { when(findIterable.first()).thenReturn(document); - assertEquals(mongoDb.readFromDb(ID),userAccount); + assertEquals(mongoDb.readFromDb(ID), userAccount); } @Test void writeToDb() { MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); - assertDoesNotThrow(()-> {mongoDb.writeToDb(userAccount);}); + assertDoesNotThrow( + () -> { + mongoDb.writeToDb(userAccount); + }); } @Test void updateDb() { MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); - assertDoesNotThrow(()-> {mongoDb.updateDb(userAccount);}); + assertDoesNotThrow( + () -> { + mongoDb.updateDb(userAccount); + }); } @Test void upsertDb() { MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); - assertDoesNotThrow(()-> {mongoDb.upsertDb(userAccount);}); + assertDoesNotThrow( + () -> { + mongoDb.upsertDb(userAccount); + }); } -} \ No newline at end of file +} diff --git a/callback/README.md b/callback/README.md index 543b8626ffeb..1c90cdcea5f2 100644 --- a/callback/README.md +++ b/callback/README.md @@ -1,5 +1,7 @@ --- -title: Callback +title: "Callback Pattern in Java: Mastering Asynchronous Communication" +shortTitle: Callback +description: "Learn about the Java Callback Design Pattern, including its intent, usage scenarios, benefits, trade-offs, and real-world examples. Understand how to implement and effectively use callbacks in your Java applications." category: Functional language: en tag: @@ -9,75 +11,85 @@ tag: - Reactive --- -## Intent - -Callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at some convenient time. - ## Also known as +* Call-After * Event-Subscription * Listener -## Explanation +## Intent of Callback Design Pattern + +The Java Callback Design Pattern is a piece of executable code passed as an argument to other code, which is expected to call back (execute) the argument at a convenient time. -Real world example +## Detailed Explanation of Callback Pattern with Real-World Examples -> We need to be notified after the executing task has finished. We pass a callback method for the executor and wait for it to call back on us. +Real-world example + +> A real-world analogy for the Callback design pattern can be found in the restaurant industry. Imagine a situation where you place an order at a busy restaurant. Instead of waiting at the counter for your food to be ready, you provide the cashier with your phone number. Once your order is prepared, the kitchen staff calls or sends a text message to notify you that your meal is ready for pickup. +> +> In this analogy, placing your order is analogous to initiating an asynchronous task. Providing your phone number is akin to passing a callback function. The kitchen preparing your order represents the asynchronous processing, and the notification you receive is the callback being executed, allowing you to retrieve your meal without having to wait idly. This separation of task initiation and task completion is the essence of the Callback design pattern. In plain words -> Callback is a method passed to an executor which will be called at a defined moment. +> Callback is a method passed to an executor which will be called at a defined moment. Wikipedia says > In computer programming, a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time. -**Programmatic Example** +## Programmatic Example of Callback Pattern in Java + +We need to be notified after the executing task has finished. We pass a callback method for the executor and wait for it to call back on us. -Callback is a simple interface with single method. +`Callback` is a simple interface with single method. ```java public interface Callback { - void call(); + void call(); } ``` -Next we define a task that will execute the callback after the task execution has finished. +Next we define `Task` that will execute the callback after the task execution has finished. ```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."); + } } ``` Finally, here's how we execute a task and receive a callback when it's finished. ```java +public static void main(final String[] args) { var task = new SimpleTask(); task.executeWith(() -> LOGGER.info("I'm done now.")); +} ``` -## Class diagram +Program output: -![Callback pattern class diagram](./etc/callback.png "Callback") +``` +17:12:11.680 [main] INFO com.iluwatar.callback.SimpleTask -- Perform some important activity and after call the callback method. +17:12:11.682 [main] INFO com.iluwatar.callback.App -- I'm done now. +``` -## Applicability +## When to Use the Callback Pattern in Java Use the Callback pattern when @@ -85,13 +97,14 @@ Use the Callback pattern when * Implementing notification mechanisms where certain events need to trigger actions in other components. * Decoupling modules or components that need to interact without having a direct dependency on each other -## Known uses +## Real-World Applications of Callback Pattern in Java * GUI frameworks often use callbacks for event handling, such as user interactions (clicks, key presses) * Node.js heavily relies on callbacks for non-blocking I/O operations * Frameworks that deal with asynchronous operations, like Promises in JavaScript, use callbacks to handle the resolution or rejection of asynchronous tasks +* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept a callback that will be triggered every time a barrier is tripped. -## Consequences +## Benefits and Trade-offs of Callback Pattern Benefits: @@ -105,14 +118,14 @@ Trade-offs: * Inversion of control can lead to harder-to-follow code flow, making debugging more challenging * Potential issues with error handling, especially in languages or environments where exceptions are used, as errors might need to be propagated through callbacks -## Related patterns +## Related Java Design Patterns -[Observer](https://java-design-patterns.com/patterns/observer/): Callbacks can be seen as a more dynamic and lightweight form of the Observer pattern, with the ability to subscribe and unsubscribe callback functions dynamically -[Command](https://java-design-patterns.com/patterns/command/): Callbacks can be implemented as Command objects in scenarios where more flexibility or statefulness is required in the callback operation -[Promise](https://java-design-patterns.com/patterns/promise/): In some languages or frameworks, Promises or Futures can be used to handle asynchronous operations more cleanly, often using callbacks for success or failure cases +* [Command](https://java-design-patterns.com/patterns/command/): Callbacks can be implemented as Command objects in scenarios where more flexibility or statefulness is required in the callback operation +* [Observer](https://java-design-patterns.com/patterns/observer/): Callbacks can be seen as a more dynamic and lightweight form of the Observer pattern, with the ability to subscribe and unsubscribe callback functions dynamically +* [Promise](https://java-design-patterns.com/patterns/promise/): In some languages or frameworks, Promises or Futures can be used to handle asynchronous operations more cleanly, often using callbacks for success or failure cases -## Real world examples +## References and Credits -* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) constructor can accept a callback that will be triggered every time a barrier is tripped. -* [JavaScript: The Good Parts](https://amzn.to/3TiQV61) -* [Node.js Design Patterns - Third edition: Design and implement production-grade Node.js applications using proven patterns and techniques](https://amzn.to/3VssjKG) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) diff --git a/callback/pom.xml b/callback/pom.xml index cdae8e87044e..772615f457f9 100644 --- a/callback/pom.xml +++ b/callback/pom.xml @@ -34,6 +34,14 @@ callback + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/callback/src/main/java/com/iluwatar/callback/App.java b/callback/src/main/java/com/iluwatar/callback/App.java index a574126c4cc9..7b630f8da247 100644 --- a/callback/src/main/java/com/iluwatar/callback/App.java +++ b/callback/src/main/java/com/iluwatar/callback/App.java @@ -34,12 +34,9 @@ @Slf4j public final class App { - private App() { - } + private App() {} - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(final String[] args) { var task = new SimpleTask(); task.executeWith(() -> LOGGER.info("I'm done now.")); diff --git a/callback/src/main/java/com/iluwatar/callback/Callback.java b/callback/src/main/java/com/iluwatar/callback/Callback.java index 14de46b21386..7b75b5c71077 100644 --- a/callback/src/main/java/com/iluwatar/callback/Callback.java +++ b/callback/src/main/java/com/iluwatar/callback/Callback.java @@ -24,9 +24,7 @@ */ package com.iluwatar.callback; -/** - * Callback interface. - */ +/** Callback interface. */ public interface Callback { void call(); diff --git a/callback/src/main/java/com/iluwatar/callback/SimpleTask.java b/callback/src/main/java/com/iluwatar/callback/SimpleTask.java index a7ac0a9394e5..bbf060a6fc9f 100644 --- a/callback/src/main/java/com/iluwatar/callback/SimpleTask.java +++ b/callback/src/main/java/com/iluwatar/callback/SimpleTask.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of task that need to be executed. - */ +/** Implementation of task that need to be executed. */ @Slf4j public final class SimpleTask extends Task { diff --git a/callback/src/main/java/com/iluwatar/callback/Task.java b/callback/src/main/java/com/iluwatar/callback/Task.java index 30481f747cde..d69697454dff 100644 --- a/callback/src/main/java/com/iluwatar/callback/Task.java +++ b/callback/src/main/java/com/iluwatar/callback/Task.java @@ -26,14 +26,10 @@ import java.util.Optional; -/** - * Template-method class for callback hook execution. - */ +/** Template-method class for callback hook execution. */ public abstract class Task { - /** - * Execute with callback. - */ + /** Execute with callback. */ final void executeWith(Callback callback) { execute(); Optional.ofNullable(callback).ifPresent(Callback::call); diff --git a/callback/src/test/java/com/iluwatar/callback/AppTest.java b/callback/src/test/java/com/iluwatar/callback/AppTest.java index 26c5df95f698..ca0e93072f0f 100644 --- a/callback/src/test/java/com/iluwatar/callback/AppTest.java +++ b/callback/src/test/java/com/iluwatar/callback/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.callback; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Callback example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Callback example runs without errors. */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/callback/src/test/java/com/iluwatar/callback/CallbackTest.java b/callback/src/test/java/com/iluwatar/callback/CallbackTest.java index d8dab98e676d..99939d491f4e 100644 --- a/callback/src/test/java/com/iluwatar/callback/CallbackTest.java +++ b/callback/src/test/java/com/iluwatar/callback/CallbackTest.java @@ -31,8 +31,8 @@ /** * Add a field as a counter. Every time the callback method is called increment this field. Unit * test checks that the field is being incremented. - *

- * Could be done with mock objects as well where the call method call is verified. + * + *

Could be done with mock objects as well where the call method call is verified. */ class CallbackTest { @@ -53,6 +53,5 @@ void test() { task.executeWith(callback); assertEquals(Integer.valueOf(2), callingCount, "Callback called twice"); - } } diff --git a/chain-of-responsibility/README.md b/chain-of-responsibility/README.md index 175143491664..f8c926973f67 100644 --- a/chain-of-responsibility/README.md +++ b/chain-of-responsibility/README.md @@ -1,10 +1,14 @@ --- -title: Chain of responsibility +title: "Chain of Responsibility Pattern in Java: Building Robust Request Handling Mechanisms" +shortTitle: Chain of Responsibility +description: "Learn the Chain of Responsibility design pattern in Java with real-world examples, code snippets, and class diagrams. Enhance your coding skills with our detailed explanations." category: Behavioral language: en tag: - - Gang of Four - Decoupling + - Event-driven + - Gang of Four + - Messaging --- ## Also known as @@ -13,66 +17,61 @@ tag: * Chain of Objects * Responsibility Chain -## Intent +## Intent of Chain of Responsibility Design Pattern -Avoid coupling the sender of a request to its receiver by giving more than one object a chance to -handle the request. Chain the receiving objects and pass the request along the chain until an object -handles it. +The Chain of Responsibility pattern in Java is a behavioral design pattern that decouples the sender of a request from its receivers by giving more than one object a chance to handle the request. The receiving objects are chained and the request is passed along the chain until an object handles it. -## Explanation +## Detailed Explanation of Chain of Responsibility Pattern with Real-World Examples Real-world example -> The Orc King gives loud orders to his army. The closest one to react is the commander, then -> an officer, and then a soldier. The commander, officer, and soldier form a chain of responsibility. +> A real-world example of the Chain of Responsibility pattern in Java is a technical support call center. When implementing this Java design pattern, each level of support represents a handler in the chain. When a customer calls in with an issue, the call is first received by a front-line support representative. If the issue is simple, the representative handles it directly. If the issue is more complex, the representative forwards the call to a second-level support technician. This process continues, with the call being escalated through multiple levels of support until it reaches a specialist who can resolve the problem. Each level of support represents a handler in the chain, and the call is passed along the chain until it finds an appropriate handler, thereby decoupling the request from the specific receiver. In plain words -> It helps to build a chain of objects. A request enters from one end and keeps going from an object -> to another until it finds a suitable handler. +> It helps to build a chain of objects. A request enters from one end and keeps going from an object to another until it finds a suitable handler. Wikipedia says -> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of -> a source of command objects and a series of processing objects. Each processing object contains -> logic that defines the types of command objects that it can handle; the rest are passed to the -> next processing object in the chain. +> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. -**Programmatic Example** +## Programmatic Example of Chain of Responsibility Pattern -Translating our example with the orcs from above. First, we have the `Request` class: +In this Java example, the Orc King gives orders which are processed by a chain of command representing the Chain of Responsibility pattern. Learn how to implement this design pattern in Java with the following code snippet. -```java -import lombok.Getter; +The Orc King gives loud orders to his army. The closest one to react is the commander, then an officer, and then a soldier. The commander, officer, and soldier form a chain of responsibility. +First, we have the `Request` class: + +```java @Getter public class Request { - 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 void markHandled() { - this.handled = true; - } - - @Override - public String toString() { - return getRequestDescription(); - } + 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 void markHandled() { + this.handled = true; + } + + @Override + public String toString() { + return getRequestDescription(); + } } public enum RequestType { - DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX + DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX } ``` -Next, we show the request handler hierarchy. +Next, we show the `RequestHandler` hierarchy. ```java public interface RequestHandler { @@ -110,46 +109,49 @@ public class OrcCommander implements RequestHandler { } } -// OrcOfficer and OrcSoldier are defined similarly as OrcCommander +// OrcOfficer and OrcSoldier are defined similarly as OrcCommander ... ``` -The Orc King gives the orders and forms the chain. +The `OrcKing` gives the orders and forms the chain. ```java public class OrcKing { - private List handlers; + private List 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)); + } } ``` 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")); + public static void main(String[] args) { + + 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")); +} ``` -The console output. +The console output: ``` Orc commander handling request "defend castle" @@ -157,11 +159,11 @@ Orc officer handling request "torture prisoner" Orc soldier handling request "collect tax" ``` -## Class diagram +## Chain of Responsibility Pattern Class Diagram -![alt text](./etc/chain-of-responsibility.urm.png "Chain of Responsibility class diagram") +![Chain of Responsibility](./etc/chain-of-responsibility.urm.png "Chain of Responsibility class diagram") -## Applicability +## When to Use the Chain of Responsibility Pattern in Java Use Chain of Responsibility when @@ -169,7 +171,7 @@ Use Chain of Responsibility when * You want to issue a request to one of several objects without specifying the receiver explicitly. * The set of objects that can handle a request should be specified dynamically. -## Known uses +## Real-World Applications of Chain of Responsibility Pattern in Java * Event bubbling in GUI frameworks where an event might be handled at multiple levels of a UI component hierarchy * Middleware frameworks where a request passes through a chain of processing objects @@ -178,7 +180,7 @@ Use Chain of Responsibility when * [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) * [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) -## Consequences +## Benefits and Trade-offs of Chain of Responsibility Pattern Benefits: @@ -192,16 +194,16 @@ Trade-Offs: * The request might end up unhandled if the chain doesn't include a catch-all handler. * Performance concerns might arise due to potentially going through several handlers before finding the right one, or not finding it at all. -## Related Patterns +## Related Java Design Patterns -[Command](https://java-design-patterns.com/patterns/command/): can be used to encapsulate a request as an object, which might be passed along the chain. -[Composite](https://java-design-patterns.com/patterns/composite/): the Chain of Responsibility is often applied in conjunction with the Composite pattern. -[Decorator](https://java-design-patterns.com/patterns/decorator/): decorators can be chained in a similar manner as responsibilities in the Chain of Responsibility pattern. +* [Command](https://java-design-patterns.com/patterns/command/): can be used to encapsulate a request as an object, which might be passed along the chain. +* [Composite](https://java-design-patterns.com/patterns/composite/): the Chain of Responsibility is often applied in conjunction with the Composite pattern. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): decorators can be chained in a similar manner as responsibilities in the Chain of Responsibility pattern. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) * [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5) * [Refactoring to Patterns](https://amzn.to/3VOO4F5) * [Pattern languages of program design 3](https://amzn.to/4a4NxTH) diff --git a/chain-of-responsibility/pom.xml b/chain-of-responsibility/pom.xml index f72502045af9..e6a7fb974be7 100644 --- a/chain-of-responsibility/pom.xml +++ b/chain-of-responsibility/pom.xml @@ -34,6 +34,14 @@ chain-of-responsibility + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java index 70ea09463507..ad3749c985ca 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * OrcCommander. - */ +/** OrcCommander. */ @Slf4j public class OrcCommander implements RequestHandler { @Override diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java index c01aa151a5be..7500ebf3af77 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java @@ -28,9 +28,7 @@ import java.util.Comparator; import java.util.List; -/** - * OrcKing makes requests that are handled by the chain. - */ +/** OrcKing makes requests that are handled by the chain. */ public class OrcKing { private List handlers; @@ -43,12 +41,9 @@ private void buildChain() { handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier()); } - /** - * Handle request by the chain. - */ + /** Handle request by the chain. */ public void makeRequest(Request req) { - handlers - .stream() + handlers.stream() .sorted(Comparator.comparing(RequestHandler::getPriority)) .filter(handler -> handler.canHandleRequest(req)) .findFirst() diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java index 7138a001ce55..0edb579119e1 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * OrcOfficer. - */ +/** OrcOfficer. */ @Slf4j public class OrcOfficer implements RequestHandler { @Override @@ -52,4 +50,3 @@ public String name() { return "Orc officer"; } } - diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java index 6650b34746ca..7398844cd0e2 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * OrcSoldier. - */ +/** OrcSoldier. */ @Slf4j public class OrcSoldier implements RequestHandler { @Override diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java index 05c0d88d5961..2f94422647a4 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java @@ -27,9 +27,7 @@ import java.util.Objects; import lombok.Getter; -/** - * Request. - */ +/** Request. */ @Getter public class Request { @@ -39,9 +37,7 @@ public class Request { */ private final RequestType requestType; - /** - * A description of the request. - */ + /** A description of the request. */ private final String requestDescription; /** @@ -53,7 +49,7 @@ public class Request { /** * Create a new request of the given type and accompanied description. * - * @param requestType The type of request + * @param requestType The type of request * @param requestDescription The description of the request */ public Request(final RequestType requestType, final String requestDescription) { @@ -61,9 +57,7 @@ public Request(final RequestType requestType, final String requestDescription) { this.requestDescription = Objects.requireNonNull(requestDescription); } - /** - * Mark the request as handled. - */ + /** Mark the request as handled. */ public void markHandled() { this.handled = true; } @@ -72,5 +66,4 @@ public void markHandled() { public String toString() { return getRequestDescription(); } - } diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java index ca46c44bbc2d..89eafbdcb43c 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java @@ -24,9 +24,7 @@ */ package com.iluwatar.chain; -/** - * RequestHandler. - */ +/** RequestHandler. */ public interface RequestHandler { boolean canHandleRequest(Request req); diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java index 17277cad36a2..f3cc73c97a08 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java @@ -24,13 +24,9 @@ */ package com.iluwatar.chain; -/** - * RequestType enumeration. - */ +/** RequestType enumeration. */ public enum RequestType { - DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX - } diff --git a/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java b/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java index 702d58dcb3fd..4d2cd68991d9 100644 --- a/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java +++ b/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.chain; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java b/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java index af1cd709ef20..ec53a411787a 100644 --- a/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java +++ b/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java @@ -29,33 +29,26 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * Date: 12/6/15 - 9:29 PM - * - * @author Jeroen Meulemeester - */ +/** OrcKingTest */ class OrcKingTest { - /** - * All possible requests - */ - private static final List REQUESTS = List.of( - new Request(RequestType.DEFEND_CASTLE, "Don't let the barbarians enter my castle!!"), - new Request(RequestType.TORTURE_PRISONER, "Don't just stand there, tickle him!"), - new Request(RequestType.COLLECT_TAX, "Don't steal, the King hates competition ...") - ); + /** All possible requests */ + private static final List REQUESTS = + List.of( + new Request(RequestType.DEFEND_CASTLE, "Don't let the barbarians enter my castle!!"), + new Request(RequestType.TORTURE_PRISONER, "Don't just stand there, tickle him!"), + new Request(RequestType.COLLECT_TAX, "Don't steal, the King hates competition ...")); @Test void testMakeRequest() { final var king = new OrcKing(); - REQUESTS.forEach(request -> { - king.makeRequest(request); - assertTrue( - request.isHandled(), - "Expected all requests from King to be handled, but [" + request + "] was not!" - ); - }); + REQUESTS.forEach( + request -> { + king.makeRequest(request); + assertTrue( + request.isHandled(), + "Expected all requests from King to be handled, but [" + request + "] was not!"); + }); } - -} \ No newline at end of file +} diff --git a/circuit-breaker/README.md b/circuit-breaker/README.md index 9d43ac8924f2..b8e0ff248122 100644 --- a/circuit-breaker/README.md +++ b/circuit-breaker/README.md @@ -1,71 +1,108 @@ --- -title: Circuit Breaker +title: "Circuit Breaker Pattern in Java: Enhancing System Resilience" +shortTitle: Circuit Breaker +description: "Learn about the Circuit Breaker pattern in Java design, which ensures fault tolerance and prevents cascading failures in distributed systems and microservices architectures." category: Resilience language: en tag: - Cloud distributed - Fault tolerance - Microservices + - Retry --- ## Also known as -* Fault tolerance switch +* Fault Tolerance Switch -## Intent +## Intent of Circuit Breaker Design Pattern -The Circuit Breaker pattern aims to prevent a software system from making calls to a part of the system that is either failing or showing signs of distress. It is a way to gracefully degrade functionality when a dependent service is not responding, rather than failing completely. +The Circuit Breaker pattern is a critical Java design pattern that helps ensure fault tolerance and resilience in microservices and distributed systems. Using Circuit Breaker, it is possible to prevent a system from repeatedly trying to execute an operation likely to fail, allowing it to recover from faults and prevent cascading failures. -## Explanation +## Detailed Explanation of Circuit Breaker Pattern with Real-World Examples -Real world example +Real-world example -> Imagine a web application that has both local files/images and remote services that are used for -> fetching data. These remote services may be either healthy and responsive at times, or may become -> slow and unresponsive at some point of time due to variety of reasons. So if one of the remote -> 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. +> Consider a real-world example of an e-commerce website that depends on multiple external payment gateways to process transactions. If one of the payment gateways becomes unresponsive or slow, the Circuit Breaker pattern can be used to detect the failure and prevent the system from repeatedly attempting to use the problematic gateway. Instead, it can quickly switch to alternative payment gateways or display an error message to the user, ensuring that the rest of the website remains functional and responsive. This avoids resource exhaustion and provides a better user experience by allowing transactions to be processed through other available services. This way, the Circuit Breaker pattern handles external API failures, ensuring the system remains functional. In plain words -> Circuit Breaker allows graceful handling of failed remote services. It's especially useful when -> all parts of our application are highly decoupled from each other, and failure of one component -> doesn't mean the other parts will stop working. +> Circuit Breaker allows graceful handling of failed remote services. It's especially useful when all parts of our application are highly decoupled from each other, and failure of one component doesn't mean the other parts will stop working. Wikipedia says -> Circuit breaker is a design pattern used in modern software development. It is used to detect -> failures and encapsulates the logic of preventing a failure from constantly recurring, during -> maintenance, temporary external system failure or unexpected system difficulties. +> Circuit breaker is a design pattern used in modern software development. It is used to detect failures and encapsulates the logic of preventing a failure from constantly recurring, during maintenance, temporary external system failure or unexpected system difficulties. -## Programmatic Example +## Programmatic Example of Circuit Breaker Pattern in Java -So, how does this all come together? With the above example in mind we will imitate the -functionality in a simple example. A monitoring service mimics the web app and makes both local and -remote calls. +This Java example demonstrates how the Circuit Breaker pattern can manage remote service failures and maintain system stability. -The service architecture is as follows: +Imagine a web application that uses both local files/images and remote services to fetch data. Remote services can become slow or unresponsive, which may cause the application to hang due to thread starvation. The Circuit Breaker pattern can help detect such failures and allow the application to degrade gracefully. -![alt text](./etc/ServiceDiagram.png "Service Diagram") +1. **Simulating a Delayed Remote Service** -In terms of code, the end user application is: +```java +// The DelayedRemoteService simulates a remote service that responds after a certain delay. +var delayedService = new DelayedRemoteService(serverStartTime, 5); +``` + +2. **Setting Up the Circuit Breaker** + +```java +// The DefaultCircuitBreaker wraps the remote service and monitors for failures. +var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2, 2000 * 1000 * 1000); +``` + +3. **Monitoring Service to Handle Requests** + +```java +// The MonitoringService is responsible for calling the remote services. +var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, quickServiceCircuitBreaker); + +// 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()); +``` + +4. **Handling Circuit Breaker States** + +```java +// Fetch current state of delayed service circuit breaker after crossing failure threshold limit +LOGGER.info(delayedServiceCircuitBreaker.getState()); // Should be OPEN + +// Meanwhile, the delayed service is down, fetch response from the healthy quick service +LOGGER.info(monitoringService.quickServiceResponse()); +LOGGER.info(quickServiceCircuitBreaker.getState()); +``` + +5. **Recovering from Failure** ```java -@Slf4j -public class App { +// 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()); - private static final Logger LOGGER = LoggerFactory.getLogger(App.class); +// As successful response is fetched, it should be CLOSED again. +LOGGER.info(delayedServiceCircuitBreaker.getState()); +``` + +6. **Full example** - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { +```java +public static void main(String[] args) { var serverStartTime = System.nanoTime(); @@ -110,209 +147,44 @@ public class App { LOGGER.info(monitoringService.delayedServiceResponse()); //As successful response is fetched, it should be CLOSED again. LOGGER.info(delayedServiceCircuitBreaker.getState()); - } } ``` -The monitoring service: - -```java -public class MonitoringService { - - private final CircuitBreaker delayedService; - - private final CircuitBreaker quickService; +Summary of the example - public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) { - this.delayedService = delayedService; - this.quickService = quickService; - } +- Initialize the Circuit Breaker with parameters: `timeout`, `failureThreshold`, and `retryTimePeriod`. +- Start in the `closed` state. +- On successful calls, reset the state. +- On failures exceeding the threshold, transition to the `open` state to prevent further calls. +- After the retry timeout, transition to the `half-open` state to test the service. +- On success in `half-open` state, transition back to `closed`. On failure, return to `open`. - //Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic - public String localResourceResponse() { - return "Local Service is working"; - } +Program output: - /** - * 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(); - } - } -} ``` -As it can be seen, it does the call to get local resources directly, but it wraps the call to -remote (costly) service in a circuit breaker object, which prevents faults as follows: - -```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; - - /** - * 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; - } - } - - @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; - } - } - } -} +16:59:19.767 [main] INFO com.iluwatar.circuitbreaker.App -- Local Service is working +16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Delayed service is down +16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Delayed service is down +16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- OPEN +16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Quick Service is working +16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- CLOSED +16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Waiting for delayed service to become responsive +16:59:24.779 [main] INFO com.iluwatar.circuitbreaker.App -- HALF_OPEN +16:59:24.780 [main] INFO com.iluwatar.circuitbreaker.App -- Delayed service is working +16:59:24.780 [main] INFO com.iluwatar.circuitbreaker.App -- CLOSED ``` -How does the above pattern prevent failures? Let's understand via this finite state machine -implemented by it. +This example demonstrates how the Circuit Breaker pattern can help maintain application stability and resilience by managing remote service failures. -![alt text](./etc/StateDiagram.png "State Diagram") +## When to Use the Circuit Breaker Pattern in Java -- We initialize the Circuit Breaker object with certain parameters: `timeout`, `failureThreshold` and `retryTimePeriod` which help determine how resilient the API is. -- Initially, we are in the `closed` state and nos remote calls to the API have occurred. -- Every time the call succeeds, we reset the state to as it was in the beginning. -- If the number of failures cross a certain threshold, we move to the `open` state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```) -- Once we exceed the retry timeout period, we move to the `half-open` state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A failure sets it back to `open` state and another attempt is made after retry timeout period, while a success sets it to `closed` state so that everything starts working normally again. - -## Class diagram - -![alt text](./etc/circuit-breaker.urm.png "Circuit Breaker class diagram") - -## Applicability +The Circuit Breaker pattern is applicable: * In distributed systems where individual service failures can lead to cascading system-wide failures * For applications that interact with third-party services or databases that might become unresponsive or slow * In microservices architectures where the failure of one service can affect the availability of others -## Known Uses +## Real-World Applications of Circuit Breaker Pattern in Java * Cloud-based services to gracefully handle the failure of external services * E-commerce platforms to manage high volumes of transactions and dependency on external APIs @@ -320,12 +192,12 @@ implemented by it. * [Spring Circuit Breaker module](https://spring.io/guides/gs/circuit-breaker) * [Netflix Hystrix API](https://github.com/Netflix/Hystrix) -## Consequences +## Benefits and Trade-offs of Circuit Breaker Pattern Benefits: * Prevents the system from performing futile operations that are likely to fail, thus saving resources -* Helps in maintaining the stability and performance of the application during partial system failures +* Helps in maintaining the system stability and performance of the application during partial system failures * Facilitates faster system recovery by avoiding the overwhelming of failing services with repeated requests Trade-Offs: @@ -336,15 +208,15 @@ Trade-Offs: ## Related Patterns +- Bulkhead: Can be used to isolate different parts of the system to prevent failures from spreading across the system - [Retry Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/retry): Can be used in conjunction with the Circuit Breaker pattern to retry failed operations before opening the circuit -- [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead): Can be used to isolate different parts of the system to prevent failures from spreading across the system -## Credits +## References and Credits -* [Understanding Circuit Breaker Pattern](https://itnext.io/understand-circuitbreaker-design-pattern-with-simple-practical-example-92a752615b42) -* [Martin Fowler on Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html) -* [Fault tolerance in a high volume, distributed system](https://medium.com/netflix-techblog/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a) -* [Circuit Breaker pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) -* [Release It! Design and Deploy Production-Ready Software](https://amzn.to/4aqTNEP) -* [Microservices Patterns: With examples in Java](https://amzn.to/3xaZwk0) * [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/43Dx86g) +* [Microservices Patterns: With examples in Java](https://amzn.to/3xaZwk0) +* [Release It! Design and Deploy Production-Ready Software](https://amzn.to/4aqTNEP) +* [Understand CircuitBreaker Design Pattern with Simple Practical Example (ITNEXT)](https://itnext.io/understand-circuitbreaker-design-pattern-with-simple-practical-example-92a752615b42) +* [Circuit Breaker (Martin Fowler)](https://martinfowler.com/bliki/CircuitBreaker.html) +* [Fault tolerance in a high volume, distributed system (Netflix)](https://medium.com/netflix-techblog/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a) +* [Circuit Breaker pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) diff --git a/circuit-breaker/etc/ServiceDiagram.png b/circuit-breaker/etc/ServiceDiagram.png deleted file mode 100644 index 885320a4d901..000000000000 Binary files a/circuit-breaker/etc/ServiceDiagram.png and /dev/null differ diff --git a/circuit-breaker/etc/StateDiagram.png b/circuit-breaker/etc/StateDiagram.png deleted file mode 100644 index 38485526d342..000000000000 Binary files a/circuit-breaker/etc/StateDiagram.png and /dev/null differ diff --git a/circuit-breaker/pom.xml b/circuit-breaker/pom.xml index eda24a4f4d5a..5b2be0c5605a 100644 --- a/circuit-breaker/pom.xml +++ b/circuit-breaker/pom.xml @@ -34,6 +34,14 @@ circuit-breaker + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java index a29b6d769826..6011aa9d10e6 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java @@ -27,33 +27,29 @@ import lombok.extern.slf4j.Slf4j; /** - *

* The intention of the Circuit Builder pattern is to handle remote failures robustly, which is to * mean that if a service is dependent on n number of other services, and m of them fail, we should * be able to recover from that failure by ensuring that the user can still use the services that * are actually functional, and resources are not tied up by uselessly by the services which are not * working. However, we should also be able to detect when any of the m failing services become * operational again, so that we can use it - *

- *

- * In this example, the circuit breaker pattern is demonstrated by using three services: {@link + * + *

In this example, the circuit breaker pattern is demonstrated by using three services: {@link * DelayedRemoteService}, {@link QuickRemoteService} and {@link MonitoringService}. The monitoring - * service is responsible for calling three services: a local service, a quick remove service - * {@link QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by - * using the circuit breaker construction we ensure that if the call to remote service is going to - * fail, we are going to save our resources and not make the function call at all, by wrapping our - * call to the remote services in the {@link DefaultCircuitBreaker} implementation object. - *

- *

- * This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states: - * Open, Closed and Half-Open, which represents the real world circuits. If - * the state is closed (initial), we assume everything is alright and perform the function call. + * service is responsible for calling three services: a local service, a quick remove service {@link + * QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by using the + * circuit breaker construction we ensure that if the call to remote service is going to fail, we + * are going to save our resources and not make the function call at all, by wrapping our call to + * the remote services in the {@link DefaultCircuitBreaker} implementation object. + * + *

This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states: + * Open, Closed and Half-Open, which represents the real world circuits. If the + * state is closed (initial), we assume everything is alright and perform the function call. * However, every time the call fails, we note it and once it crosses a threshold, we set the state * to Open, preventing any further calls to the remote server. Then, after a certain retry period * (during which we expect thee service to recover), we make another call to the remote server and * this state is called the Half-Open state, where it stays till the service is down, and once it * recovers, it goes back to the closed state and the cycle continues. - *

*/ @Slf4j public class App { @@ -68,45 +64,45 @@ public static void main(String[] args) { var serverStartTime = System.nanoTime(); var delayedService = new DelayedRemoteService(serverStartTime, 5); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2, - 2000 * 1000 * 1000); + 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 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 + // Fetch response from local resource LOGGER.info(monitoringService.localResourceResponse()); - //Fetch response from delayed service 2 times, to meet the failure threshold + // 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 + // 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 + // 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 + // 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 + // 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 + // 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. + // As successful response is fetched, it should be CLOSED again. LOGGER.info(delayedServiceCircuitBreaker.getState()); } } diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java index aaccd65b1dce..31e11751a4ea 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java @@ -24,9 +24,7 @@ */ package com.iluwatar.circuitbreaker; -/** - * The Circuit breaker interface. - */ +/** The Circuit breaker interface. */ public interface CircuitBreaker { // Success response. Reset everything to defaults diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java index 18febbb6b1b7..762c04d6b589 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java @@ -45,14 +45,14 @@ public class DefaultCircuitBreaker implements CircuitBreaker { /** * Constructor to create an instance of Circuit Breaker. * - * @param timeout Timeout for the API request. Not necessary for this simple example + * @param timeout Timeout for the API request. Not necessary for this simple example * @param failureThreshold Number of failures we receive from the depended on service before - * changing state to 'OPEN' - * @param retryTimePeriod Time, in nanoseconds, period after which a new request is made to - * remote service for status check. + * changing state to 'OPEN' + * @param retryTimePeriod Time, in nanoseconds, period after which a new request is made to remote + * service for status check. */ - DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold, - long retryTimePeriod) { + 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; @@ -61,7 +61,7 @@ public class DefaultCircuitBreaker implements CircuitBreaker { // 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 + // An absurd amount of time in future which basically indicates the last failure never happened this.lastFailureTime = System.nanoTime() + futureTime; this.failureCount = 0; } @@ -84,16 +84,16 @@ public void recordFailure(String 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 (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 + // We have waited long enough and should try checking if service is up state = State.HALF_OPEN; } else { - //Service would still probably be down + // Service would still probably be down state = State.OPEN; } } else { - //Everything is working fine + // Everything is working fine state = State.CLOSED; } } @@ -140,9 +140,9 @@ public String attemptRequest() throws RemoteServiceException { } 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 + // 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(); diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java index 4db2988146f4..ad87f1a6e71d 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java @@ -56,12 +56,12 @@ public DelayedRemoteService() { @Override public String call() throws RemoteServiceException { var currentTime = System.nanoTime(); - //Since currentTime and serverStartTime are both in nanoseconds, we convert it to - //seconds by diving by 10e9 and ensure floating point division by multiplying it - //with 1.0 first. We then check if it is greater or less than specified delay and then - //send the reply + // Since currentTime and serverStartTime are both in nanoseconds, we convert it to + // seconds by diving by 10e9 and ensure floating point division by multiplying it + // with 1.0 first. We then check if it is greater or less than specified delay and then + // send the reply if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) { - //Can use Thread.sleep() here to block and simulate a hung server + // Can use Thread.sleep() here to block and simulate a hung server throw new RemoteServiceException("Delayed service is down"); } return "Delayed service is working"; diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java index 33564acc16ff..3fa5cd776d8c 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java @@ -39,7 +39,7 @@ public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickServ this.quickService = quickService; } - //Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic + // Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic public String localResourceResponse() { return "Local Service is working"; } @@ -69,4 +69,4 @@ public String quickServiceResponse() { return e.getMessage(); } } -} \ No newline at end of file +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java index 404f1c05b4c7..2367e49233ae 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.circuitbreaker; -/** - * A quick response remote service, that responds healthy without any delay or failure. - */ +/** A quick response remote service, that responds healthy without any delay or failure. */ public class QuickRemoteService implements RemoteService { @Override diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java index dac616f03414..ced5d3ac9d47 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java @@ -30,6 +30,6 @@ */ public interface RemoteService { - //Fetch response from remote service. + // Fetch response from remote service. String call() throws RemoteServiceException; } diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java index eb033cd8ebfa..48deec756b75 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.circuitbreaker; -/** - * Exception thrown when {@link RemoteService} does not respond successfully. - */ +/** Exception thrown when {@link RemoteService} does not respond successfully. */ public class RemoteServiceException extends Exception { public RemoteServiceException(String message) { diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java index e59b6ce8b41f..f2668281eb57 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java @@ -24,11 +24,9 @@ */ package com.iluwatar.circuitbreaker; -/** - * Enumeration for states the circuit breaker could be in. - */ +/** Enumeration for states the circuit breaker could be in. */ public enum State { CLOSED, OPEN, HALF_OPEN -} \ No newline at end of file +} diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java index 108695706a07..3cbeadcd13de 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java @@ -31,20 +31,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * App Test showing usage of circuit breaker. - */ +/** App Test showing usage of circuit breaker. */ class AppTest { private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class); - //Startup delay for delayed service (in seconds) + // Startup delay for delayed service (in seconds) private static final int STARTUP_DELAY = 4; - //Number of failed requests for circuit breaker to open + // Number of failed requests for circuit breaker to open private static final int FAILURE_THRESHOLD = 1; - //Time period in seconds for circuit breaker to retry service + // Time period in seconds for circuit breaker to retry service private static final int RETRY_PERIOD = 2; private MonitoringService monitoringService; @@ -62,75 +60,75 @@ class AppTest { @BeforeEach void setupCircuitBreakers() { var delayedService = new DelayedRemoteService(System.nanoTime(), STARTUP_DELAY); - //Set the circuit Breaker parameters - delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - FAILURE_THRESHOLD, - RETRY_PERIOD * 1000 * 1000 * 1000); + // Set the circuit Breaker parameters + delayedServiceCircuitBreaker = + new DefaultCircuitBreaker( + delayedService, 3000, FAILURE_THRESHOLD, RETRY_PERIOD * 1000 * 1000 * 1000); var quickService = new QuickRemoteService(); - //Set the circuit Breaker parameters - quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, FAILURE_THRESHOLD, - RETRY_PERIOD * 1000 * 1000 * 1000); - - monitoringService = new MonitoringService(delayedServiceCircuitBreaker, - quickServiceCircuitBreaker); + // Set the circuit Breaker parameters + quickServiceCircuitBreaker = + new DefaultCircuitBreaker( + quickService, 3000, FAILURE_THRESHOLD, RETRY_PERIOD * 1000 * 1000 * 1000); + monitoringService = + new MonitoringService(delayedServiceCircuitBreaker, quickServiceCircuitBreaker); } @Test void testFailure_OpenStateTransition() { - //Calling delayed service, which will be unhealthy till 4 seconds + // Calling delayed service, which will be unhealthy till 4 seconds assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //As failure threshold is "1", the circuit breaker is changed to OPEN + // As failure threshold is "1", the circuit breaker is changed to OPEN assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); - //As circuit state is OPEN, we expect a quick fallback response from circuit breaker. + // As circuit state is OPEN, we expect a quick fallback response from circuit breaker. assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //Meanwhile, the quick service is responding and the circuit state is CLOSED + // Meanwhile, the quick service is responding and the circuit state is CLOSED assertEquals("Quick Service is working", monitoringService.quickServiceResponse()); assertEquals("CLOSED", quickServiceCircuitBreaker.getState()); - } @Test void testFailure_HalfOpenStateTransition() { - //Calling delayed service, which will be unhealthy till 4 seconds + // Calling delayed service, which will be unhealthy till 4 seconds assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //As failure threshold is "1", the circuit breaker is changed to OPEN + // As failure threshold is "1", the circuit breaker is changed to OPEN assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); - //Waiting for recovery period of 2 seconds for circuit breaker to retry service. + // Waiting for recovery period of 2 seconds for circuit breaker to retry service. try { LOGGER.info("Waiting 2s for delayed service to become responsive"); Thread.sleep(2000); } catch (InterruptedException e) { LOGGER.error("An error occurred: ", e); } - //After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching response from service again + // After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching + // response from service again assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState()); - } @Test void testRecovery_ClosedStateTransition() { - //Calling delayed service, which will be unhealthy till 4 seconds + // Calling delayed service, which will be unhealthy till 4 seconds assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //As failure threshold is "1", the circuit breaker is changed to OPEN + // As failure threshold is "1", the circuit breaker is changed to OPEN assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); - //Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond successfully. + // Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond + // successfully. try { LOGGER.info("Waiting 4s for delayed service to become responsive"); Thread.sleep(4000); } catch (InterruptedException e) { LOGGER.error("An error occurred: ", e); } - //As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back in HALF_OPEN state. + // As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back + // in HALF_OPEN state. assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState()); - //Check the success response from delayed service. + // Check the success response from delayed service. assertEquals("Delayed service is working", monitoringService.delayedServiceResponse()); - //As the response is success, the state should be CLOSED + // As the response is success, the state should be CLOSED assertEquals("CLOSED", delayedServiceCircuitBreaker.getState()); } - } diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java index 465371a3aad3..c184a8376220 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java @@ -28,29 +28,27 @@ import org.junit.jupiter.api.Test; -/** - * Circuit Breaker test - */ +/** Circuit Breaker test */ class DefaultCircuitBreakerTest { - //long timeout, int failureThreshold, long retryTimePeriod + // long timeout, int failureThreshold, long retryTimePeriod @Test void testEvaluateState() { var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 100); - //Right now, failureCountfailureThreshold, and lastFailureTime is nearly equal to current time, - //state should be half-open + // Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time, + // state should be half-open assertEquals(circuitBreaker.getState(), "HALF_OPEN"); - //Since failureCount>failureThreshold, and lastFailureTime is much lesser current time, - //state should be open + // Since failureCount>failureThreshold, and lastFailureTime is much lesser current time, + // state should be open circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000; circuitBreaker.evaluateState(); assertEquals(circuitBreaker.getState(), "OPEN"); - //Now set it back again to closed to test idempotency + // Now set it back again to closed to test idempotency circuitBreaker.failureCount = 0; circuitBreaker.evaluateState(); assertEquals(circuitBreaker.getState(), "CLOSED"); @@ -59,23 +57,24 @@ void testEvaluateState() { @Test void testSetStateForBypass() { var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 2000 * 1000 * 1000); - //Right now, failureCount { - var obj = new DelayedRemoteService(); - obj.call(); - }); + Assertions.assertThrows( + RemoteServiceException.class, + () -> { + var obj = new DelayedRemoteService(); + obj.call(); + }); } /** @@ -54,7 +54,7 @@ void testDefaultConstructor() throws RemoteServiceException { */ @Test void testParameterizedConstructor() throws RemoteServiceException { - var obj = new DelayedRemoteService(System.nanoTime()-2000*1000*1000,1); - assertEquals("Delayed service is working",obj.call()); + var obj = new DelayedRemoteService(System.nanoTime() - 2000 * 1000 * 1000, 1); + assertEquals("Delayed service is working", obj.call()); } } diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java index 51dde10ece8b..f7781fd1c58d 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java @@ -28,28 +28,25 @@ import org.junit.jupiter.api.Test; -/** - * Monitoring Service test - */ +/** Monitoring Service test */ class MonitoringServiceTest { - //long timeout, int failureThreshold, long retryTimePeriod + // long timeout, int failureThreshold, long retryTimePeriod @Test void testLocalResponse() { - var monitoringService = new MonitoringService(null,null); + var monitoringService = new MonitoringService(null, null); var response = monitoringService.localResourceResponse(); assertEquals(response, "Local Service is working"); } @Test void testDelayedRemoteResponseSuccess() { - var delayedService = new DelayedRemoteService(System.nanoTime()-2*1000*1000*1000, 2); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - 1, - 2 * 1000 * 1000 * 1000); + var delayedService = new DelayedRemoteService(System.nanoTime() - 2 * 1000 * 1000 * 1000, 2); + var delayedServiceCircuitBreaker = + new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000); - var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); - //Set time in past to make the server work + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null); + // Set time in past to make the server work var response = monitoringService.delayedServiceResponse(); assertEquals(response, "Delayed service is working"); } @@ -57,11 +54,10 @@ void testDelayedRemoteResponseSuccess() { @Test void testDelayedRemoteResponseFailure() { var delayedService = new DelayedRemoteService(System.nanoTime(), 2); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - 1, - 2 * 1000 * 1000 * 1000); - var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); - //Set time as current time as initially server fails + var delayedServiceCircuitBreaker = + new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000); + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null); + // Set time as current time as initially server fails var response = monitoringService.delayedServiceResponse(); assertEquals(response, "Delayed service is down"); } @@ -69,11 +65,10 @@ void testDelayedRemoteResponseFailure() { @Test void testQuickRemoteServiceResponse() { var delayedService = new QuickRemoteService(); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - 1, - 2 * 1000 * 1000 * 1000); - var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); - //Set time as current time as initially server fails + var delayedServiceCircuitBreaker = + new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000); + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null); + // Set time as current time as initially server fails var response = monitoringService.delayedServiceResponse(); assertEquals(response, "Quick Service is working"); } diff --git a/clean-architecture/README.md b/clean-architecture/README.md new file mode 100644 index 000000000000..0e2e7f5da99e --- /dev/null +++ b/clean-architecture/README.md @@ -0,0 +1,278 @@ +--- +title: "Clean Architecture - A Software Maintainable Architectural style." +shortTitle: Clean Architecture +description: "Learn the Clean Architecture Style in Java with real-world examples, code snippets, and class diagrams. Enhance your coding skills with our detailed explanations." +category: Behavioral +language: en +tag: + - Decoupling + - Architectural Style +--- + +## Also known as + +* Hexagonal Architecture. + +## Intent of Clean Architecture. + +The clean architecture is a software design architectural style which ensures the software application is easy to understand, maintainable and can be extend easily as per business requirement. + +## Detailed Explanation of Clean Architecture Pattern with Real-World Examples + +Real World. + +A real world example of clean architecture is like teh shopping mall example. There some employee is assigned to work on the filling of the products in the counter, one person is responsible for the billing purpose, one person is taking care of the security, one person is taking care of the product they have in storage. The work of every individual is separate and they are focussed on the specific task. Clean architecture also suggests to make the component separate and each component should perform some task. Clean Architecture proposes a layered architecture with clear boundaries between different system components to achieve independence of frameworks, UI, databases, and delivery mechanisms and the possibility to test in isolation. + +In plain word + +It helps to make the system more maintainable and easy to extend. + +Wikipedia says + +> The clean architecture proposed by Robert C. Martin in 2012 combines the principles of the hexagonal architecture, the onion architecture and several other variants. It provides additional levels of detail of the component, which are presented as concentric rings. It isolates adapters and interfaces (user interface, databases, external systems, devices) in the outer rings of the architecture and leaves the inner rings for use cases and entities. +> +> The clean architecture uses the principle of dependency inversion with the strict rule that dependencies shall only exist between an outer ring to an inner ring and never the contrary. + + +## Clean architecture Class Diagram + +![Clean Architecture](./etc/cleanArchitectureUMLDiagram.png "Clean Architecture class diagram") + +## When to Use the Clean Architecture Pattern in Java + +In all application we can use the clean architecture style and make the component separate and business logic separate from the UI and database. + +## Real-World Applications of Chain of Responsibility Pattern in Java. + +In the application say Ecommerce application user gives teh order and the application is represented using teh clean architecture pattern. + +There are facility like the **product** where user can see the product details like the price and the features, **Cart** user can add the product they have selected and the **Order** where user can see the total order and calculate the price of the order. Learn how to implement this design pattern in Java with the following code snippet. + +## Programmatic Example of Clean Architecture Pattern + +First we have the entity class like the `Product`, `Order` and teh `Cart` +```java +public class Product { + private String id; + private String name; + private double price; + + public Product(String id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } +} +``` + +```java +public class Cart { + private Product product; + private int quantity; + + public CartItem(Product product, int quantity) { + this.product = product; + this.quantity = quantity; + } + + public double getTotalPrice() { + return product.getPrice() * quantity; + } +} +``` + +```java +public class Order { + private String orderId; + private List items; + private double totalPrice; + + public Order(String orderId, List items) { + this.orderId = orderId; + this.items = items; + this.totalPrice = items.stream().mapToDouble(CartItem::getTotalPrice).sum(); + } +} +``` +The repository interfaces are created. +```java +public interface CartRepository { + void addItemToCart(String userId, Product product, int quantity); + void removeItemFromCart(String userId, String productId); + List getItemsInCart(String userId); + double calculateTotal(String userId); + void clearCart(String userId); +} +``` +```java +public interface ProductRepository { + Product getProductById(String productId); +} +``` +```java +public interface OrderRepository { + void saveOrder(Order order); +} +``` + + +The in memory data store in the cart and order. +```java +public class InMemoryCartRepository implements CartRepository { + private final Map> userCarts = new HashMap<>(); + + @Override + public void addItemToCart(String userId, Product product, int quantity) { + List cart = userCarts.getOrDefault(userId, new ArrayList<>()); + cart.add(new Cart(product, quantity)); + userCarts.put(userId, cart); + } + + @Override + public void removeItemFromCart(String userId, String productId) { + List cart = userCarts.get(userId); + if (cart != null) { + cart.removeIf(item -> item.getProduct().getId().equals(productId)); + } + } + + @Override + public List getItemsInCart(String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()); + } + + @Override + public double calculateTotal(String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()) + .stream() + .mapToDouble(Cart::getTotalPrice) + .sum(); + } + + @Override + public void clearCart(String userId) { + userCarts.remove(userId); + } +} +``` +```java +public class InMemoryOrderRepository implements OrderRepository { + private final List orders = new ArrayList<>(); + + @Override + public void saveOrder(Order order) { + orders.add(order); + } +} +``` + +```java +public class InMemoryProductRepository implements ProductRepository { + private final Map products = new HashMap<>(); + + public InMemoryProductRepository() { + products.put("1", new Product("1", "Laptop", 1000.0)); + products.put("2", new Product("2", "Smartphone", 500.0)); + } + + @Override + public Product getProductById(String productId) { + return products.get(productId); + } +} +``` + +The order controller. +```java +public class OrderController{ + private final ShoppingCartService shoppingCartUseCase; + + public OrderController(ShoppingCartService shoppingCartUseCase) { + this.shoppingCartUseCase = shoppingCartUseCase; + } + + public Order checkout(String userId) { + return shoppingCartUseCase.checkout(userId); + } +} +``` +The cart controller. +```java +public class CartController { + private final ShoppingCartService shoppingCartUseCase; + + public CartController(ShoppingCartService shoppingCartUseCase) { + this.shoppingCartUseCase = shoppingCartUseCase; + } + + public void addItemToCart(String userId, String productId, int quantity) { + shoppingCartUseCase.addItemToCart(userId, productId, quantity); + } + + public void removeItemFromCart(String userId, String productId) { + shoppingCartUseCase.removeItemFromCart(userId, productId); + } + + public double calculateTotal(String userId) { + return shoppingCartUseCase.calculateTotal(userId); + } +} +``` + +The clean architecture in action. +```java +public static void main(String[] args) { + + ProductRepository productRepository = new InMemoryProductRepository(); + CartRepository cartRepository = new InMemoryCartRepository(); + OrderRepository orderRepository = new InMemoryOrderRepository(); + + ShoppingCartService shoppingCartUseCase = + new ShoppingCartService(productRepository, cartRepository, orderRepository); + + CartController cartController = new CartController(shoppingCartUseCase); + OrderController orderController = new OrderController(shoppingCartUseCase); + + String userId = "user123"; + cartController.addItemToCart(userId, "1", 1); + cartController.addItemToCart(userId, "2", 2); + + System.out.println("Total: $" + cartController.calculateTotal(userId)); + + Order order = orderController.checkout(userId); + System.out.println( + "Order placed! Order ID: " + order.getOrderId() + ", Total: $" + order.getTotalPrice()); + } +``` + +The output of the code. +```md +Total: $2000.0 +Order placed! Order ID: ORDER-1743349969254, Total: $2000.0 +``` + +## Benefits and Trade-offs of Clean Architecture Pattern. + +Benefits: + +The main benefits of the Clean Architecture involves - +**Scalability** - It allows to add new features without any issue. +**Modularity** - It makes the code loosely coupled and making the change in any component becomes easier. +**Testability** - The architecture promotes unit testing, integration testing, and acceptance testing of different layers independently. + +Trade-Offs: + +Initially the design needs to be done with high precision and the UML diagram should cover all the architectural structure. It will in return help to make it more channelised and the code extensibility will increase. + +## Related Java Design Patterns + +* Dependency Injection - Dependency Injection (DI) is a key concept in Clean Architecture. It promotes loose coupling between classes by allowing dependencies to be injected rather than directly created by the class itself. +* Singleton Pattern - The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. This is often used for shared services or resources, such as a configuration manager, logging service, or database connection pool. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) +* [Pattern languages of program design 3](https://amzn.to/4a4NxTH) \ No newline at end of file diff --git a/clean-architecture/etc/cleanArchitectureUMLDiagram.PNG b/clean-architecture/etc/cleanArchitectureUMLDiagram.PNG new file mode 100644 index 000000000000..f107d34a14d8 Binary files /dev/null and b/clean-architecture/etc/cleanArchitectureUMLDiagram.PNG differ diff --git a/clean-architecture/pom.xml b/clean-architecture/pom.xml new file mode 100644 index 000000000000..8222c6d9d80d --- /dev/null +++ b/clean-architecture/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + clean-architecture + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.cleanarchitecture.App + + + + + + + + + diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java new file mode 100644 index 000000000000..ef6785391280 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java @@ -0,0 +1,47 @@ +package com.iluwatar.cleanarchitecture; + +import lombok.extern.slf4j.Slf4j; + +/** + * Clean Architecture ensures separation of concerns by organizing code, into layers and making it + * scalable and maintainable. + * + *

In the example there are Entities (Core Models) – Product, Cart, Order handle business logic. + * Use Cases (Application Logic) – ShoppingCartService manages operations like adding items and + * checkout. Interfaces & Adapters – Repositories (CartRepository, OrderRepository) abstract data + * handling, while controllers (CartController, OrderController) manage interactions. + */ +@Slf4j +public final class App { + + private App() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(final String[] args) { + ProductRepository productRepository = new InMemoryProductRepository(); + CartRepository cartRepository = new InMemoryCartRepository(); + OrderRepository orderRepository = new InMemoryOrderRepository(); + + ShoppingCartService shoppingCartUseCase = + new ShoppingCartService(productRepository, cartRepository, orderRepository); + + CartController cartController = new CartController(shoppingCartUseCase); + OrderController orderController = new OrderController(shoppingCartUseCase); + + String userId = "user123"; + cartController.addItemToCart(userId, "1", 1); + cartController.addItemToCart(userId, "2", 2); + + Order order = orderController.checkout(userId); + LOGGER.info("Total: ${}" + cartController.calculateTotal(userId)); + + LOGGER.info( + "Order placed! Order ID: {}, Total: ${}", order.getOrderId(), order.getTotalPrice()); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java new file mode 100644 index 000000000000..045ca8d22d5d --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java @@ -0,0 +1,66 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import lombok.Getter; + +/** + * Represents a shopping cart containing a product and its quantity. This class calculates the total + * price of the product based on its price and quantity. + */ +@Getter +public class Cart { + /** The product in the cart. It holds the product details such as name, price, and description. */ + private final Product product; + + /** + * The quantity of the product in the cart. It represents how many units of the product are added + * to the cart. + */ + private final int quantity; + + /** + * Constructs a new Cart instance with a specified product and quantity. + * + * @param prod the product to be added to the cart. + * @param qty the quantity of the product in the cart. + */ + public Cart(final Product prod, final int qty) { + this.product = prod; + this.quantity = qty; + } + + /** + * Calculates the total price of the products in the cart. The total price is the product's price + * multiplied by the quantity. + * + * @return the total price of the products in the cart. + */ + public double getTotalPrice() { + return product.getPrice() * quantity; + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java new file mode 100644 index 000000000000..9cfaa118e807 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java @@ -0,0 +1,80 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.cleanarchitecture; + +/** + * Controller class for handling shopping cart operations. + * + *

This class provides methods to add, remove, and calculate the total price of items in a user's + * shopping cart. + */ +public class CartController { + + /** Service layer responsible for cart operations. */ + private final ShoppingCartService shoppingCartUseCase; + + /** + * Constructs a CartController with the specified shopping cart service. + * + * @param shoppingCart The shopping cart service to handle cart operations. + */ + public CartController(final ShoppingCartService shoppingCart) { + this.shoppingCartUseCase = shoppingCart; + } + + /** + * Adds an item to the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be added. + * @param quantity The quantity of the product. + */ + public void addItemToCart(final String userId, final String productId, final int quantity) { + shoppingCartUseCase.addItemToCart(userId, productId, quantity); + } + + /** + * Removes an item from the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + public void removeItemFromCart(final String userId, final String productId) { + shoppingCartUseCase.removeItemFromCart(userId, productId); + } + + /** + * Calculates the total cost of items in the user's cart. + * + * @param userId The ID of the user. + * @return The total price of all items in the cart. + */ + public double calculateTotal(final String userId) { + return shoppingCartUseCase.calculateTotal(userId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java new file mode 100644 index 000000000000..1463500766d4 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java @@ -0,0 +1,72 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.List; + +/** CartRepository. */ +public interface CartRepository { + /** + * Adds an item to the user's cart. + * + * @param userId The ID of the user. + * @param product The product to be added. + * @param quantity The quantity of the product. + */ + void addItemToCart(String userId, Product product, int quantity); + + /** + * Removes an item from the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + void removeItemFromCart(String userId, String productId); + + /** + * Retrieves the list of items in the user's cart. + * + * @param userId The ID of the user. + * @return A list of items in the cart. + */ + List getItemsInCart(String userId); + + /** + * Calculates the total price of the items in the user's cart. + * + * @param userId The ID of the user. + * @return The total price of all items in the cart. + */ + double calculateTotal(String userId); + + /** + * Clears all items from the user's cart. + * + * @param userId The ID of the user. + */ + void clearCart(String userId); +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java new file mode 100644 index 000000000000..afdc866d7702 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java @@ -0,0 +1,104 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@link CartRepository} that stores cart items in memory. + * + *

This class maintains a map of user carts where each user has a list of cart items. + */ +public class InMemoryCartRepository implements CartRepository { + /** A map storing user carts with their respective cart items. */ + private final Map> userCarts = new HashMap<>(); + + /** + * Adds an item to the user's cart. + * + * @param userId The ID of the user. + * @param product The product to be added. + * @param quantity The quantity of the product. + */ + @Override + public void addItemToCart(final String userId, final Product product, final int quantity) { + List cart = userCarts.getOrDefault(userId, new ArrayList<>()); + cart.add(new Cart(product, quantity)); + userCarts.put(userId, cart); + } + + /** + * Removes an item from the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + @Override + public void removeItemFromCart(final String userId, final String productId) { + List cart = userCarts.get(userId); + if (cart != null) { + cart.removeIf(item -> item.getProduct().getId().equals(productId)); + } + } + + /** + * Retrieves all items in the user's cart. + * + * @param userId The ID of the user. + * @return A list of {@link Cart} items in the user's cart. + */ + @Override + public List getItemsInCart(final String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()); + } + + /** + * Calculates the total price of items in the user's cart. + * + * @param userId The ID of the user. + * @return The total price of the cart. + */ + @Override + public double calculateTotal(final String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()).stream() + .mapToDouble(Cart::getTotalPrice) + .sum(); + } + + /** + * Clears all items from the user's cart. + * + * @param userId The ID of the user. + */ + @Override + public void clearCart(final String userId) { + userCarts.remove(userId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java new file mode 100644 index 000000000000..cce7a3818b18 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.ArrayList; +import java.util.List; + +/** + * An in-memory implementation of the {@link OrderRepository}. + * + *

This class stores orders in a list, allowing orders to be saved but not persisted beyond the + * application's runtime. + */ +public class InMemoryOrderRepository implements OrderRepository { + /** A list to store orders in memory. */ + private final List orders = new ArrayList<>(); + + /** + * Saves an order to the in-memory repository. + * + * @param order The order to be saved. + */ + @Override + public void saveOrder(final Order order) { + orders.add(order); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java new file mode 100644 index 000000000000..af052ef14781 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java @@ -0,0 +1,73 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.HashMap; +import java.util.Map; + +/** + * In-memory implementation of the {@link ProductRepository} interface. + * + *

This repository stores products in memory allowing retrieval by product ID. + */ +public class InMemoryProductRepository implements ProductRepository { + /** A map to store products by their unique product ID. */ + private final Map products = new HashMap<>(); + + /** + * The price of the Laptop in USD. + * + *

Used in the in-memory product repository to define the cost of a Laptop. + */ + private static final double LAPTOP_PRICE = 1000.0; + + /** + * The price of the Smartphone in USD. + * + *

Used in the in-memory product repository to define the cost of a Smartphone. + */ + private static final double SMARTPHONE_PRICE = 500.0; + + /** + * Constructs an {@code InMemoryProductRepository} and initializes it with some example products. + */ + public InMemoryProductRepository() { + products.put("1", new Product("1", "Laptop", LAPTOP_PRICE)); + products.put("2", new Product("2", "Smartphone", SMARTPHONE_PRICE)); + } + + /** + * Retrieves a product by its unique ID. + * + * @param productId The ID of the product to retrieve. + * @return The {@link Product} corresponding to the given ID {@code null} if not found. + */ + @Override + public Product getProductById(final String productId) { + return products.get(productId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java new file mode 100644 index 000000000000..63505dcf073b --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java @@ -0,0 +1,60 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.List; +import lombok.Getter; + +/** + * Represents an order placed by a user containing the ordered items and total price. + * + *

An order includes a unique order ID, a list of cart items and the total price of the order. + */ +@Getter +public class Order { + /** The unique identifier for this order. */ + private final String orderId; + + /** The list of items included in this order. */ + private final List items; + + /** The list of items included in this order. */ + private final double totalPrice; + + /** + * Constructs an {@code Order} with the given order ID and list of cart items. The total price is + * based on the individual item prices in the cart. + * + * @param id The unique identifier for the order. + * @param item The list of cart items included in the order. + */ + public Order(final String id, final List item) { + this.orderId = id; + this.items = item; + this.totalPrice = items.stream().mapToDouble(Cart::getTotalPrice).sum(); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java new file mode 100644 index 000000000000..36844288d9b0 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +/** + * Controller for handling order-related operations. + * + *

This class provides an endpoint for users to checkout their cart and place an order. + */ +public class OrderController { + /** Service for managing shopping cart operations. */ + private final ShoppingCartService shoppingCartUseCase; + + /** + * Constructs an {@code OrderController} with the given shopping cart service. + * + * @param shoppingCartUse The shopping cart service used to process orders. + */ + public OrderController(final ShoppingCartService shoppingCartUse) { + this.shoppingCartUseCase = shoppingCartUse; + } + + /** + * Processes the checkout for a given user and creates an order. + * + * @param userId The ID of the user checking out. + * @return The created {@link Order} after checkout. + */ + public Order checkout(final String userId) { + return shoppingCartUseCase.checkout(userId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java new file mode 100644 index 000000000000..f29b907592e1 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java @@ -0,0 +1,41 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +/** + * Repository interface for managing order persistence. + * + *

This interface defines the contract for storing orders in the system. + */ +public interface OrderRepository { + /** + * Saves an order to the repository. + * + * @param order The order to be saved. + */ + void saveOrder(Order order); +} diff --git a/embedded-value/src/main/java/com/iluwatar/embedded/value/ShippingAddress.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java similarity index 59% rename from embedded-value/src/main/java/com/iluwatar/embedded/value/ShippingAddress.java rename to clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java index 9b04519dd17e..dd7050ea9e4b 100644 --- a/embedded-value/src/main/java/com/iluwatar/embedded/value/ShippingAddress.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java @@ -1,5 +1,7 @@ /* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). * * The MIT License * Copyright © 2014-2022 Ilkka Seppälä @@ -22,33 +24,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.embedded.value; +package com.iluwatar.cleanarchitecture; import lombok.Getter; -import lombok.ToString; -/** - * Another POJO which wraps the Shipping details of order into Object. - */ -@ToString +/** Represents a product in the system. */ @Getter -public class ShippingAddress { - - private String city; - private String state; - private String pincode; - +public class Product { + /** The unique identifier for the product. */ + private final String id; + + /** The name of the product. */ + private final String name; + + /** The price of the product. */ + private final double price; + /** - * Constructor for Shipping Address object. - * @param city City name - * @param state State name - * @param pincode Pin code of the city - * + * Constructs a new Product with the given details. + * + * @param pdtId The unique identifier of the product. + * @param firstName The name of the product. + * @param p The price of the product. */ - public ShippingAddress(String city, String state, String pincode) { - this.city = city; - this.state = state; - this.pincode = pincode; + public Product(final String pdtId, final String firstName, final double p) { + this.id = pdtId; + this.name = firstName; + this.price = p; } -} - +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java new file mode 100644 index 000000000000..6b9324fd5d05 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java @@ -0,0 +1,38 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using ZK framework + * licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +/** Repository interface for handling product-related operations. */ +public interface ProductRepository { + /** + * Retrieves a product by its unique identifier. + * + * @param productId The unique ID of the product. + * @return The product corresponding to the given ID. + */ + Product getProductById(String productId); +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java new file mode 100644 index 000000000000..960b1a18b300 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java @@ -0,0 +1,114 @@ +/* + * This project is licensed under the MIT license. + * Module model-view-viewmodel is using + * ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.List; + +/** + * Service class for managing shopping cart operations. + * + *

This class provides functionalities to add and remove items from the cart, calculate the total + * price, and handle checkout operations. + */ +public class ShoppingCartService { + /** Repository for managing product data. */ + private final ProductRepository productRepository; + + /** Repository for managing cart data. */ + private final CartRepository cartRepository; + + /** Repository for managing order data. */ + private final OrderRepository orderRepository; + + /** + * Constructs a ShoppingCartService with the required repositories. + * + * @param pdtRepository The repository to fetch product details. + * @param repository The repository to manage cart operations. + * @param ordRepository The repository to handle order persistence. + */ + public ShoppingCartService( + final ProductRepository pdtRepository, + final CartRepository repository, + final OrderRepository ordRepository) { + this.productRepository = pdtRepository; + this.cartRepository = repository; + this.orderRepository = ordRepository; + } + + /** + * Adds an item to the user's shopping cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be added. + * @param quantity The quantity of the product. + */ + public void addItemToCart(final String userId, final String productId, final int quantity) { + Product product = productRepository.getProductById(productId); + if (product != null) { + cartRepository.addItemToCart(userId, product, quantity); + } + } + + /** + * Removes an item from the user's shopping cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + public void removeItemFromCart(final String userId, final String productId) { + cartRepository.removeItemFromCart(userId, productId); + } + + /** + * Calculates the total cost of items in the user's shopping cart. + * + * @param userId The ID of the user. + * @return The total price of all items in the cart. + */ + public double calculateTotal(final String userId) { + return cartRepository.calculateTotal(userId); + } + + /** + * Checks out the user's cart and creates an order. + * + *

This method retrieves the cart items, generates an order ID, creates a new order, saves it, + * and clears the cart. + * + * @param userId The ID of the user. + * @return The created order containing purchased items. + */ + public Order checkout(final String userId) { + List items = cartRepository.getItemsInCart(userId); + String orderId = "ORDER-" + System.currentTimeMillis(); + Order order = new Order(orderId, items); + orderRepository.saveOrder(order); + cartRepository.clearCart(userId); + return order; + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java new file mode 100644 index 000000000000..027c4b2dcf0e --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java @@ -0,0 +1,7 @@ +/** + * Provides classes and interfaces for the clean architecture pattern implementation. + * + *

This package includes classes for managing products, carts, orders, repositories, and services + * for a shopping cart system, following the clean architecture principles. + */ +package com.iluwatar.cleanarchitecture; diff --git a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java new file mode 100644 index 000000000000..e5904f3d0aae --- /dev/null +++ b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java @@ -0,0 +1,19 @@ +package com.iluwatar.cleanarchitecture; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class AppTest { + /** + * Issue: Add at least one assertion to this test case. + * + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App} throws an exception. + */ + @Test + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java new file mode 100644 index 000000000000..17af441718bc --- /dev/null +++ b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java @@ -0,0 +1,42 @@ +package com.iluwatar.cleanarchitecture; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CartControllerTest { + + private ShoppingCartService shoppingCartUseCase; + private CartController cartController; + + @BeforeEach + public void setUp() { + ProductRepository productRepository = new InMemoryProductRepository(); + CartRepository cartRepository = new InMemoryCartRepository(); + OrderRepository orderRepository = new InMemoryOrderRepository(); + shoppingCartUseCase = + new ShoppingCartService(productRepository, cartRepository, orderRepository); + cartController = new CartController(shoppingCartUseCase); + } + + @Test + void testRemoveItemFromCart() { + cartController.addItemToCart("user123", "1", 1); + cartController.addItemToCart("user123", "2", 2); + + assertEquals(2000.0, cartController.calculateTotal("user123")); + + cartController.removeItemFromCart("user123", "1"); + + assertEquals(1000.0, cartController.calculateTotal("user123")); + } + + @Test + void testRemoveNonExistentItem() { + cartController.addItemToCart("user123", "2", 2); + cartController.removeItemFromCart("user123", "999"); + + assertEquals(1000.0, cartController.calculateTotal("user123")); + } +} diff --git a/client-session/README.md b/client-session/README.md index 82204eb3495d..c49e53c7174b 100644 --- a/client-session/README.md +++ b/client-session/README.md @@ -1,55 +1,86 @@ --- -title: Client Session Pattern -category: Architectural +title: "Client-Session Pattern in Java: Streamlining Client Data Across Sessions" +shortTitle: Client Session +description: "Explore the Client Session design pattern in Java. Learn how to manage user state and data across multiple requests for seamless, personalized web application experiences." +category: Behavioral language: en tags: -- Decoupling + - Client-server + - Session management + - State tracking + - Web development --- -## Name +## Also known as -[Client Session pattern](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client) +* User Session -## Intent +## Intent of Client Session Design Pattern -- Create stateless servers that removes the problem of clustering, as users can switch between servers seamlessly. -- Makes data more resilient in case of server fail-over. -- Works well with smaller data sizes. +The Client Session design pattern is essential for web development involving client-server interactions. It aims to maintain a user's state and data across multiple requests within a web application, ensuring a continuous and personalized user experience. This pattern helps in creating a seamless user experience by managing user state and data effectively across different sessions, crucial for modern web applications. -## Explanation +## Detailed Explanation of Client Session Pattern with Real-World Examples -Real-World Example +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 in size 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. +> A real-world example of the Client Session pattern is a library membership system. When a member logs in, the system starts a session to track their borrowing activities. This session holds data such as the member's ID, current borrowed books, due dates, and any fines. As the member browses the catalog, borrows books, or returns them, the session maintains this stateful information, ensuring the member's interactions are consistent and personalized until they log out or the session expires. This approach helps the library system manage user-specific data efficiently across multiple interactions, providing a seamless and personalized experience for the members. -In Plain words +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 information. -> ([ref](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client)) +> The Client Session pattern manages user-specific data across multiple requests within a web application to maintain continuity and personalization. -**Programmatic Example** +Wikipedia says -Here is the sample code to describe the client-session pattern. In the below code we are first -creating an instance of the Server. This server instance will then be used to get Session objects -for two clients. As you can see from the code below the Session object can be used to store any -relevant information that are required by the server to process the client request. These session -objects will then be passed on with every Request to the server. The Request will have the Session -object that stores the relevant client details along with the required data for processing the -request. The session information in every request helps the server identify the client and process -the request accordingly. +> The client-server model on Wikipedia describes a system where client devices request services and resources from centralized servers. This model is crucial in web applications where client sessions are used to manage user-specific data across multiple requests. For example, when a bank customer accesses online banking services, their login credentials and session state are managed by the web server to maintain continuity of their interactions. + +## Programmatic Example of Client Session Pattern in Java + +The Client Session design pattern is a behavioral design pattern that maintains a user's state and data across multiple requests within a web application, ensuring a continuous and personalized user experience. This pattern is commonly used in web applications where user-specific data needs to be managed across multiple requests. + +In the given code, we have a `Server` class and a `Session` class. The `Server` class represents the server that processes incoming requests and assigns sessions to clients. The `Session` class represents a session that is assigned to a client. ```java -public class App { +// The Server class represents the server that processes incoming requests and assigns sessions to clients. +public class Server { + private String host; + private int port; + + public Server(String host, int port) { + this.host = host; + this.port = port; + } + + // Other methods... + + // This method returns a new session for a client. + public Session getSession(String name) { + return new Session(name, "ClientName"); + } + + // This method processes a request from a client. + public void process(Request request) { + // Process the request... + } +} +// The Session class represents a session that is assigned to a client. +public class Session { + private String id; + private String clientName; + + public Session(String id, String clientName) { + this.id = id; + this.clientName = clientName; + } + + // Other methods... +} +``` + +In the `main` method, we create an instance of `Server`, create two sessions for two different clients, and then pass these sessions to the server in the request along with the data. The server is then able to interpret the client based on the session associated with it. + +```java +public class App { public static void main(String[] args) { var server = new Server("localhost", 8080); var session1 = server.getSession("Session1"); @@ -60,55 +91,53 @@ public class App { server.process(request2); } } +``` -@Data -@AllArgsConstructor -public class Session { +In this example, the `Server` class is responsible for creating and managing sessions for clients, and the `Session` class represents the client's session. The `Request` class represents a request from a client, which includes the client's session and data. The server processes the request based on the client's session. - /** - * Session id. - */ - private String id; +Running the program produces the following console output: - /** - * Client name. - */ - private String clientName; - -} +``` +19:28:49.152 [main] INFO com.iluwatar.client.session.Server -- Processing Request with client: Session1 data: Data1 +19:28:49.154 [main] INFO com.iluwatar.client.session.Server -- Processing Request with client: Session2 data: Data2 +``` -@Data -@AllArgsConstructor -public class Request { +## When to Use the Client Session Pattern in Java - private String data; +Use the client state pattern when: - private Session session; +* Web applications requiring user authentication and authorization. +* Applications needing to track user activities and preferences over multiple requests or visits. +* Systems where server resources need to be optimized by offloading state management to the client side. -} -``` +## Real-World Applications of Client Session Pattern in Java -## Architecture Diagram +* E-commerce websites to track shopping cart contents across sessions. +* Online platforms that offer personalized content based on user preferences and history. +* Web applications requiring user login to access personalized or secured content. -![alt text](./etc/session_state_pattern.png "Session State Pattern") +## Benefits and Trade-offs of Client Session Pattern -## Applicability +Benefits: -Use the client state pattern when: +* Improved server performance by reducing the need to store user state on the server. +* Enhanced user experience through personalized content and seamless navigation across different parts of the application. +* Flexibility in managing sessions through various client-side storage mechanisms (e.g., cookies, Web Storage API). -- Processing smaller amounts of data to prevent larger requests and response sizes. -- Remove the need for servers to save client states. Doing so also removes the need to store session IDs. -- Clustering is an issue and needs to be avoided. Stateless servers allow clients to be easily distributed across servers. -- Creates resilience from data losses due to server fails. +Trade-offs: -## Consequences +* Potential security risks if sensitive information is stored in client sessions without proper encryption and validation. +* Dependence on client-side capabilities and settings, such as cookie policies, which can vary across browsers and user configurations. +* Increased complexity in session management logic, especially in handling session expiration, renewal, and synchronization across multiple devices or tabs. -- The server is stateless. Any compute API will not store any data. -- Struggles to deal with large amounts of data. Creates longer send and receive times due to larger amounts of session data to manage. -- Security. All data is stored on the client's machine. This means that any vulnerabilities on the clients side can expose all data being sent and received by the server. +## Related Patterns +* Server Session: Often used in conjunction with the Client Session pattern to provide a balance between client-side efficiency and server-side control. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Ensuring a single instance of a user's session throughout the application. +* [State](https://java-design-patterns.com/patterns/state/): Managing state transitions in a session, such as authenticated, guest, or expired states. -## Credits +## References and Credits -- [Dzone - Practical PHP patterns](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client) -- [Client Session State Design Pattern - Ram N Java](https://www.youtube.com/watch?v=ycOSj9g41pc) +* [Professional Java for Web Applications](https://amzn.to/4aazY59) +* [Securing Web Applications with Spring Security](https://amzn.to/3PCCEA1) +* [Client Session State Design Pattern: Explained Simply (Ram N Java)](https://www.youtube.com/watch?v=ycOSj9g41pc) diff --git a/client-session/pom.xml b/client-session/pom.xml index b7ff08637091..1b2ea4564ff9 100644 --- a/client-session/pom.xml +++ b/client-session/pom.xml @@ -34,6 +34,14 @@ client-session + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/client-session/src/main/java/com/iluwatar/client/session/App.java b/client-session/src/main/java/com/iluwatar/client/session/App.java index 282d8a7f3b28..8f744353ed1b 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/App.java +++ b/client-session/src/main/java/com/iluwatar/client/session/App.java @@ -29,12 +29,11 @@ * The Client-Session pattern allows the session data to be stored on the client side and send this * data to the server with each request. * - *

In this example, The {@link Server} class represents the server that would process the + *

In this example, The {@link Server} class represents the server that would process the * incoming {@link Request} and also assign {@link Session} to a client. Here one instance of Server * is created. The we create two sessions for two different clients. These sessions are then passed * on to the server in the request along with the data. The server is then able to interpret the * client based on the session associated with it. - *

*/ public class App { diff --git a/client-session/src/main/java/com/iluwatar/client/session/Request.java b/client-session/src/main/java/com/iluwatar/client/session/Request.java index 008011f180f7..e47ed2775ac4 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/Request.java +++ b/client-session/src/main/java/com/iluwatar/client/session/Request.java @@ -28,9 +28,7 @@ import lombok.AllArgsConstructor; import lombok.Data; -/** - * The Request class which contains the Session details and data. - */ +/** The Request class which contains the Session details and data. */ @Data @AllArgsConstructor public class Request { @@ -38,5 +36,4 @@ public class Request { private String data; private Session session; - } diff --git a/client-session/src/main/java/com/iluwatar/client/session/Server.java b/client-session/src/main/java/com/iluwatar/client/session/Server.java index 6d9dc3dbc80a..13a43a2dda81 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/Server.java +++ b/client-session/src/main/java/com/iluwatar/client/session/Server.java @@ -31,7 +31,8 @@ import lombok.extern.slf4j.Slf4j; /** - * The Server class. The client communicates with the server and request processing and getting a new session. + * The Server class. The client communicates with the server and request processing and getting a + * new session. */ @Slf4j @Data @@ -41,12 +42,10 @@ public class Server { private int port; - /** * Creates a new session. * * @param name name of the client - * * @return Session Object */ public Session getSession(String name) { @@ -59,7 +58,10 @@ public Session getSession(String name) { * @param request Request object with data and Session */ public void process(Request request) { - LOGGER.info("Processing Request with client: " + request.getSession().getClientName() + " data: " + request.getData()); + LOGGER.info( + "Processing Request with client: " + + request.getSession().getClientName() + + " data: " + + request.getData()); } - } diff --git a/client-session/src/main/java/com/iluwatar/client/session/Session.java b/client-session/src/main/java/com/iluwatar/client/session/Session.java index bb9f7246cb4e..a7639485b83c 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/Session.java +++ b/client-session/src/main/java/com/iluwatar/client/session/Session.java @@ -29,20 +29,16 @@ import lombok.Data; /** - * The Session class. Each client get assigned a Session which is then used for further communications. + * The Session class. Each client get assigned a Session which is then used for further + * communications. */ @Data @AllArgsConstructor public class Session { - /** - * Session id. - */ + /** Session id. */ private String id; - /** - * Client name. - */ + /** Client name. */ private String clientName; - } diff --git a/client-session/src/test/java/com/iluwatar/client/session/AppTest.java b/client-session/src/test/java/com/iluwatar/client/session/AppTest.java index 0e33f74f460b..63951ae48ae3 100644 --- a/client-session/src/test/java/com/iluwatar/client/session/AppTest.java +++ b/client-session/src/test/java/com/iluwatar/client/session/AppTest.java @@ -33,6 +33,6 @@ class AppTest { @Test void appStartsWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java b/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java index dc09441f0a6a..0037ca6339fb 100644 --- a/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java +++ b/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java @@ -1,8 +1,33 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.client.session; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + class ServerTest { @Test diff --git a/collecting-parameter/README.md b/collecting-parameter/README.md index 99e7bea35b4d..55609aebdcb9 100644 --- a/collecting-parameter/README.md +++ b/collecting-parameter/README.md @@ -1,116 +1,83 @@ --- -title: Collecting Parameter -category: Idiom +title: "Collecting Parameter Pattern in Java: Mastering Efficient Parameter Handling" +shortTitle: Collecting Parameter +description: "Discover how the Collecting Parameter design pattern simplifies Java method calls by aggregating multiple parameters into a single collection object. Enhance code readability and maintainability with practical examples and real-world applications." +category: Behavioral language: en tag: -- Generic + - Accumulation + - Data processing + - Data transfer + - Generic --- -## Name -Collecting Parameter +## Also known as -## Intent -To store the collaborative result of numerous methods within a collection. +* Collector +* Accumulator -## Explanation -### Real-world example -Within a large corporate building, there exists a global printer queue that is a collection of all the printing jobs -that are currently pending. Various floors contain different models of printers, each having a different printing -policy. We must construct a program that can continually add appropriate printing jobs to a collection, which is called the *collecting parameter*. +## Intent of Collecting Parameter Design Pattern -### In plain words -Instead of having one giant method that contains numerous policies for collecting information into a variable, we can -create numerous smaller functions that each take parameter, and append new information. We can pass the parameter to -all of these smaller functions and by the end, we will have what we wanted originally. This time, the code is cleaner -and easier to understand. Because the larger function has been broken down, the code is also easier to modify as changes -are localised to the smaller functions. +The Collecting Parameter pattern in Java design patterns aims to simplify method calls by aggregating multiple parameters into a single collection object. This pattern is particularly effective for methods that collect information by passing a single collection object through various method calls. Each method can then add results to this collection, instead of creating its own collection. This approach enhances code readability and maintainability, optimizing the process of information collection in Java programming. -### Wikipedia says -In the Collecting Parameter idiom a collection (list, map, etc.) is passed repeatedly as a parameter to a method which adds items to the collection. +## Detailed Explanation of Collecting Parameter Pattern with Real-World Examples -### Programmatic example -Coding our example from above, we may use the collection `result` as a collecting parameter. The following restrictions -are implemented: -- If an A4 paper is coloured, it must also be single-sided. All other non-coloured papers are accepted -- A3 papers must be non-coloured and single-sided -- A2 papers must be single-page, single-sided, and non-coloured +Real-world example + +> In software development, the Collecting Parameter pattern provides significant benefits by optimizing method calls and improving code maintainability. +> +> Imagine a scenario in a restaurant where a waiter needs to take an order from a customer. Instead of noting down each item separately (e.g., appetizer, main course, dessert, drink), the waiter uses an order form that collects all the items into a single document. This order form simplifies the communication between the waiter and the kitchen staff by aggregating all the details into one place. Similarly, in software, the Collecting Parameter pattern aggregates multiple parameters into a single object, streamlining method calls and improving code readability and maintainability. + +In plain words + +> The Collecting Parameter pattern simplifies method calls by encapsulating multiple parameters into a single object. + +Wikipedia says + +> In the Collecting Parameter idiom a collection (list, map, etc.) is passed repeatedly as a parameter to a method which adds items to the collection. + +## Programmatic Example of Collecting Parameter Pattern in Java + +Within a large corporate building, there exists a global printer queue that is a collection of all the printing jobs that are currently pending. Various floors contain different models of printers, each having a different printing policy. We must construct a program that can continually add appropriate printing jobs to a collection, which is called the collecting parameter. + +The following business rules are implemented: + +* If an A4 paper is coloured, it must also be single-sided. All other non-coloured papers are accepted +* A3 papers must be non-coloured and single-sided +* A2 papers must be single-page, single-sided, and non-coloured + +Let's see the implementation first and explain afterward. ```java -package com.iluwatar.collectingparameter; -import java.util.LinkedList; -import java.util.Queue; public class App { static PrinterQueue printerQueue = PrinterQueue.getInstance(); - /** - * 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)); - /* - This variable is the collecting parameter. - */ var result = new LinkedList(); - /* - * Using numerous sub-methods to collaboratively add information to the result collecting parameter - */ - addA4Papers(result); - addA3Papers(result); - addA2Papers(result); + addValidA4Papers(result); + addValidA3Papers(result); + addValidA2Papers(result); } -} -``` - -We use the `addA4Paper`, `addA3Paper`, and `addA2Paper` methods to populate the `result` collecting parameter with the -appropriate print jobs as per the policy described previously. The three policies are encoded below, -```java -public class App { - 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 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. - */ + public static void addValidA4Papers(Queue printerItemsCollection) { 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) { + if (isColouredAndSingleSided || !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 printerItemsCollection) { + public static void addValidA3Papers(Queue 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); @@ -119,18 +86,9 @@ public class App { } } - /** - * 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 printerItemsCollection) { + public static void addValidA2Papers(Queue 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) { @@ -142,56 +100,58 @@ public class App { } ``` -Each method takes a collecting parameter as an argument. It then adds elements, taken from a global variable, -to this collecting parameter if each element satisfies a given criteria. These methods can have whatever policy the client desires. +This `App` class is the main entry point of the application. It uses the Collecting Parameter design pattern to filter print jobs based on certain policies. + +1. **Initialization**: The `printerQueue` is initialized with three print jobs of different paper sizes (A4, A3, A2). + +2. **Creating the Collecting Parameter**: A `LinkedList` named `result` is created to store the print jobs that meet the policy requirements. + +3. **Adding Valid Jobs to the Collecting Parameter**: The `addValidA4Papers`, `addValidA3Papers`, and `addValidA2Papers` methods are called. These methods iterate over the `printerQueue` and add the print jobs that meet the policy requirements to the `result` list. + +The `result` list, which is the collecting parameter, accumulates the valid print jobs as it is passed from method to method. This is the essence of the Collecting Parameter design pattern. + +Utilizing the Collecting Parameter pattern in Java design patterns leads to more efficient method calls and improved overall code structure. + +## When to Use the Collecting Parameter Pattern in Java + +This pattern is useful for managing parameters in Java coding practices, ensuring efficient code refactoring and enhanced readability. + +* Use when a method needs to accept a large number of parameters, making the method signature unwieldy. +* Use when the same group of parameters is passed to multiple methods, reducing redundancy and potential errors. +* Use to improve the readability and maintainability of the code. + +## Collecting Parameter Pattern Java Tutorials -In this programmatic example, three print jobs are added to the queue. Only the first two print jobs should be added to -the collecting parameter as per the policy. The elements of the `result` variable after execution are, +* [Refactoring To Patterns (Joshua Kerivsky)](http://www.tarrani.net/RefactoringToPatterns.pdf) +* [Smalltalk Best Practice Patterns (Kent Beck)](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) -| paperSize | pageCount | isDoubleSided | isColour | -|-----------|-----------|---------------|----------| -| A4 | 5 | false | false | -| A3 | 2 | false | false | +## Real-World Applications of Collecting Parameter Pattern in Java -which is what we expected. +* Use when a method needs to accept a large number of parameters, making the method signature unwieldy. +* Use when the same group of parameters is passed to multiple methods, reducing redundancy and potential errors. +* Use to improve the readability and maintainability of the code. -## Class diagram -![alt text](./etc/collecting-parameter.urm.png "Collecting Parameter") +## Benefits and Trade-offs of Collecting Parameter Pattern -## Applicability -Use the Collecting Parameter design pattern when -- you want to return a collection or object that is the collaborative result of several methods -- You want to simplify a method that accumulates data as the original method is too complex +Benefits: -## Tutorials -Tutorials for this method are found in: -- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) by Joshua Kerivsky -- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) by Kent Beck +* Improves code readability by reducing the number of parameters in method signatures. +* Facilitates the reuse of parameter sets across different methods. +* Enhances maintainability by centralizing the parameter structure. -## Known uses -Joshua Kerivsky gives a real-world example in his book 'Refactoring to Patterns'. He gives an example of using the -Collecting Parameter Design Pattern to create a `toString()` method for an XML tree. Without using this design pattern, -this would require a bulky function with conditionals and concatenation that would worsen code readability. Such a method -can be broken down into smaller methods, each appending their own set of information to the collecting parameter. +Trade-offs: -See this in [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf). +* Introduces an additional class, which may increase complexity if not managed properly. +* Can lead to over-generalization if the parameter object becomes too large or unwieldy. -## Consequences -Pros: -- Makes code more readable -- Avoids 'linkages', where numerous methods reference the same global variable -- Increases maintainability by decomposing larger functions +## Related Java Design Patterns -Cons: -- May increase code length -- Adds 'layers' of methods +* [Command](https://java-design-patterns.com/patterns/command/): Commands may utilize Collecting Parameter to aggregate results from multiple operations executed by the command objects. +* [Composite](https://java-design-patterns.com/patterns/composite/): Can be used in tandem with Collecting Parameter when dealing with hierarchical structures, allowing results to be collected across a composite structure. +* [Visitor](https://java-design-patterns.com/patterns/visitor/): Often used together, where Visitor handles traversal and operations on a structure, and Collecting Parameter accumulates the results. -## Related patterns -- [Compose Methods](https://www.geeksforgeeks.org/composite-design-pattern/) +## References and Credits -## Credits -Following books were used: -- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) by Joshua Kerivsky -- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) by Kent Beck -Sites: -- [Wiki](https://wiki.c2.com/?CollectingParameter) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/4aApLP0) +* [Refactoring: Improving the Design of Existing Code](https://amzn.to/3TVEgaB) +* [Collecting Parameter (WikiWikiWeb)](https://wiki.c2.com/?CollectingParameter) diff --git a/collecting-parameter/pom.xml b/collecting-parameter/pom.xml index 8f7fdc2b52db..8c5c015468a4 100644 --- a/collecting-parameter/pom.xml +++ b/collecting-parameter/pom.xml @@ -39,11 +39,6 @@ junit-jupiter-engine test
- - junit - junit - test -
diff --git a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java index 93ead620da95..d505970fb84f 100644 --- a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java +++ b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java @@ -28,23 +28,23 @@ import java.util.Queue; /** - * The Collecting Parameter Design Pattern aims to return a result that is the collaborative result of several - * methods. This design pattern uses a 'collecting parameter' that is passed to several functions, accumulating results - * as it travels from method-to-method. This is different to the Composed Method design pattern, where a single - * collection is modified via several methods. + * The Collecting Parameter Design Pattern aims to return a result that is the collaborative result + * of several methods. This design pattern uses a 'collecting parameter' that is passed to several + * functions, accumulating results as it travels from method-to-method. This is different to the + * Composed Method design pattern, where a single collection is modified via several methods. * - *

This example is inspired by Kent Beck's example in his book, 'Smalltalk Best Practice Patterns'. The context for this - * situation is that there is a single printer queue {@link PrinterQueue} that holds numerous print jobs - * {@link PrinterItem} that must be distributed to various print centers. - * Each print center has its own requirements and printing limitations. In this example, the following requirements are: - * If an A4 document is coloured, it must also be single-sided. All other non-coloured A4 documents are accepted. - * All A3 documents must be non-coloured and single sided. All A2 documents must be a single page, single sided, and + *

This example is inspired by Kent Beck's example in his book, 'Smalltalk Best Practice + * Patterns'. The context for this situation is that there is a single printer queue {@link + * PrinterQueue} that holds numerous print jobs {@link PrinterItem} that must be distributed to + * various print centers. Each print center has its own requirements and printing limitations. In + * this example, the following requirements are: If an A4 document is coloured, it must also be + * single-sided. All other non-coloured A4 documents are accepted. All A3 documents must be + * non-coloured and single sided. All A2 documents must be a single page, single sided, and * non-coloured. * - *

A collecting parameter (the result variable) is used to filter the global printer queue so that it meets the - * requirements for this centre, - **/ - + *

A collecting parameter (the result variable) is used to filter the global printer queue so + * that it meets the requirements for this centre, + */ public class App { static PrinterQueue printerQueue = PrinterQueue.getInstance(); @@ -75,16 +75,16 @@ public static void main(String[] args) { } /** - * Adds A4 document jobs to the collecting parameter according to some policy that can be whatever the client - * (the print center) wants. + * 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 addValidA4Papers(Queue 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. - */ + 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; @@ -96,9 +96,9 @@ public static void addValidA4Papers(Queue printerItemsCollection) { } /** - * 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. + * 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 */ @@ -106,7 +106,8 @@ public static void addValidA3Papers(Queue 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 + // 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); @@ -116,9 +117,9 @@ public static void addValidA3Papers(Queue printerItemsCollection) { } /** - * 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. + * 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 */ @@ -126,9 +127,10 @@ public static void addValidA2Papers(Queue 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; + // 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); } diff --git a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java index 3f0a24854f02..5669f744dd84 100644 --- a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java +++ b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java @@ -26,18 +26,14 @@ import java.util.Objects; -/** - * This class represents a Print Item, that should be added to the queue. - **/ +/** This class represents a Print Item, that should be added to the queue. */ public class PrinterItem { PaperSizes paperSize; int pageCount; boolean isDoubleSided; boolean isColour; - /** - * The {@link PrinterItem} constructor. - **/ + /** The {@link PrinterItem} constructor. */ public PrinterItem(PaperSizes paperSize, int pageCount, boolean isDoubleSided, boolean isColour) { if (!Objects.isNull(paperSize)) { this.paperSize = paperSize; @@ -53,6 +49,5 @@ public PrinterItem(PaperSizes paperSize, int pageCount, boolean isDoubleSided, b this.isColour = isColour; this.isDoubleSided = isDoubleSided; - } -} \ No newline at end of file +} diff --git a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java index 882fc5277f71..8cdfc84107a3 100644 --- a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java +++ b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java @@ -29,15 +29,17 @@ import java.util.Queue; /** - * This class represents a singleton Printer Queue. It contains a queue that can be filled up with {@link PrinterItem}. - **/ + * This class represents a singleton Printer Queue. It contains a queue that can be filled up with + * {@link PrinterItem}. + */ public class PrinterQueue { static PrinterQueue currentInstance = null; private final Queue printerItemQueue; /** - * This class is a singleton. The getInstance method will ensure that only one instance exists at a time. + * This class is a singleton. The getInstance method will ensure that only one instance exists at + * a time. */ public static PrinterQueue getInstance() { if (Objects.isNull(currentInstance)) { @@ -46,16 +48,12 @@ public static PrinterQueue getInstance() { return currentInstance; } - /** - * Empty the printer queue. - */ + /** Empty the printer queue. */ public void emptyQueue() { currentInstance.getPrinterQueue().clear(); } - /** - * Private constructor prevents instantiation, unless using the getInstance() method. - */ + /** Private constructor prevents instantiation, unless using the getInstance() method. */ private PrinterQueue() { printerItemQueue = new LinkedList<>(); } @@ -72,5 +70,4 @@ public Queue getPrinterQueue() { public void addPrinterItem(PrinterItem printerItem) { currentInstance.getPrinterQueue().add(printerItem); } - } diff --git a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java index 57f9037778ae..8597c4d6f2cb 100644 --- a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java +++ b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java @@ -24,16 +24,14 @@ */ package com.iluwatar.collectingparameter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { - /** - * Checks whether {@link App} executes without throwing exception - */ + /** Checks whether {@link App} executes without throwing exception */ @Test void executesWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java index 9818dbb1809e..c53c5ac2c0ed 100644 --- a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java +++ b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java @@ -24,11 +24,11 @@ */ package com.iluwatar.collectingparameter; +import java.util.LinkedList; +import java.util.Queue; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import java.util.LinkedList; -import java.util.Queue; class CollectingParameterTest { diff --git a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java index 8cbe071a5410..8c03eea90c95 100644 --- a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java +++ b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.collectingparameter; +import static org.junit.jupiter.api.Assertions.*; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import java.util.LinkedList; -import java.util.Queue; - -import static org.junit.jupiter.api.Assertions.*; class PrinterQueueTest { @@ -45,13 +43,14 @@ void singletonTest() { @Test() @Timeout(1000) void negativePageCount() throws IllegalArgumentException { - Assertions.assertThrows(IllegalArgumentException.class, () -> new PrinterItem(PaperSizes.A4, -1, true, true)); + Assertions.assertThrows( + IllegalArgumentException.class, () -> new PrinterItem(PaperSizes.A4, -1, true, true)); } @Test() @Timeout(1000) void nullPageSize() throws IllegalArgumentException { - Assertions.assertThrows(IllegalArgumentException.class, () -> new PrinterItem(null, 1, true, true)); + Assertions.assertThrows( + IllegalArgumentException.class, () -> new PrinterItem(null, 1, true, true)); } - -} \ No newline at end of file +} diff --git a/collection-pipeline/README.md b/collection-pipeline/README.md index 3ae285accb69..8f8764be2b28 100644 --- a/collection-pipeline/README.md +++ b/collection-pipeline/README.md @@ -1,27 +1,122 @@ --- -title: Collection Pipeline +title: "Collection Pipeline Pattern in Java: Streamlining Data Manipulation" +shortTitle: Collection Pipeline +description: "Learn how the Collection Pipeline design pattern in Java enhances data processing by chaining operations in a sequence. This pattern promotes a declarative approach, improving code readability, maintainability, and performance." category: Functional language: en tag: - - Reactive + - Functional decomposition + - Data processing + - Data transformation + - Reactive --- -## Intent -Collection Pipeline introduces Function Composition and Collection Pipeline, two functional-style patterns that you can combine to iterate collections in your code. -In functional programming, it's common to sequence complex operations through a series of smaller modular functions or operations. The series is called a composition of functions, or a function composition. When a collection of data flows through a function composition, it becomes a collection pipeline. Function Composition and Collection Pipeline are two design patterns frequently used in functional-style programming. +## Intent of Collection Pipeline Design Pattern -## Class diagram -![alt text](./etc/collection-pipeline.png "Collection Pipeline") +The Collection Pipeline design pattern in Java processes collections of data by chaining operations in a sequence. Utilizing the Java Stream API, it transforms data declaratively, focusing on what should be done rather than how. -## Applicability -Use the Collection Pipeline pattern when +## Detailed Explanation of Collection Pipeline Pattern with Real-World Examples -* When you want to perform a sequence of operations where one operation's collected output is fed into the next -* When you use a lot of statements in your code -* When you use a lot of loops in your code +Real-world example -## Credits +> Imagine a real-world example of a factory assembly line for manufacturing cars. In this assembly line, each station performs a specific task on the car chassis, such as installing the engine, painting the body, attaching the wheels, and inspecting the final product. Each station takes the output from the previous station and adds its own processing step. This sequence of operations is analogous to the Collection Pipeline design pattern, where each step in the pipeline transforms the data and passes it on to the next step, ensuring an efficient and organized workflow. -* [Function composition and the Collection Pipeline pattern](https://www.ibm.com/developerworks/library/j-java8idioms2/index.html) -* [Martin Fowler](https://martinfowler.com/articles/collection-pipeline/) -* [Java8 Streams](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) +In plain words + +> The Collection Pipeline pattern in Java involves processing data through a series of operations using the Stream API. Each operation transforms the data in sequence, akin to an assembly line in a factory, promoting functional programming principles. + +Wikipedia says + +> In software engineering, a pipeline consists of a chain of processing elements (processes, threads, coroutines, functions, etc.), arranged so that the output of each element is the input of the next; the name is by analogy to a physical pipeline. Usually some amount of buffering is provided between consecutive elements. The information that flows in these pipelines is often a stream of records, bytes, or bits, and the elements of a pipeline may be called filters; this is also called the pipe(s) and filters design pattern. Connecting elements into a pipeline is analogous to function composition. + +## Programmatic Example of Collection Pipeline Pattern in Java + +The Collection Pipeline is a programming pattern where you organize some computation as a sequence of operations which compose by taking a collection as output of one operation and feeding it into the next. + +Here's a programmatic example of the Collection Pipeline design pattern: + +**Step 1: Filtering** + +We start with a list of `Car` objects and we want to filter out those that were manufactured after the year 2000. This is done using the `stream()` method to create a stream from the list, the `filter()` method to filter out the cars we want, and the `collect()` method to collect the results into a new list. + +```java +public static List getModelsAfter2000(List cars){ + return cars.stream() + .filter(car -> car.getYear() > 2000) // Filter cars manufactured after 2000 + .sorted(comparing(Car::getYear)) // Sort the cars by year + .map(Car::getModel) // Get the model of each car + .collect(toList()); // Collect the results into a new list +} +``` + +**Step 2: Grouping** + +Next, we want to group the cars by their category. This is done using the `groupingBy` collector. + +```java +public static Map> getGroupingOfCarsByCategory(List cars){ + return cars.stream() + .collect(groupingBy(Car::getCategory)); // Group cars by category +} +``` + +**Step 3: Filtering, Sorting and Transforming** + +Finally, we want to filter the cars owned by a person to only include sedans, sort them by date, and then transform the sorted cars into a list of `Car` objects. + +```java +public static List getSedanCarsOwnedSortedByDate(List persons){ + return persons.stream() + .flatMap(person -> person.getCars().stream()) // Flatten the list of cars owned by each person + .filter(car -> Category.SEDAN.equals(car.getCategory())) // Filter to only include sedans + .sorted(comparing(Car::getDate)) // Sort the cars by date + .collect(toList()); // Collect the results into a new list +} +``` + +In each of these methods, the Collection Pipeline pattern is used to perform a series of operations on the collection of cars in a declarative manner, which improves readability and maintainability. + +## When to Use the Collection Pipeline Pattern in Java + +The Collection Pipeline pattern is ideal for Java developers handling bulk data operations, including filtering, mapping, sorting, and reducing collections, particularly with Java 8+ Stream API. + +Use the Collection Pipeline pattern: + +* When you need to perform a series of transformations on a collection of data. +* When you want to improve readability and maintainability of complex data processing code. +* When working with large datasets where intermediate results should not be stored in memory. + +## Real-World Applications of Collection Pipeline Pattern in Java + +* LINQ in .NET +* Stream API in Java 8+ +* Collections in modern functional languages (e.g., Haskell, Scala) +* Database query builders and ORM frameworks + +## Benefits and Trade-offs of Collection Pipeline Pattern + +Benefits: + +* Readability: The code is more readable and declarative, making it easier to understand the sequence of operations. +* Maintainability: Easier to modify or extend the pipeline with additional operations. +* Reusability: Common operations can be abstracted into reusable functions. +* Lazy Evaluation: Some implementations allow for operations to be lazily evaluated, improving performance. + +Trade-offs: + +* Performance Overhead: Chaining multiple operations can introduce overhead compared to traditional loops, especially for short pipelines or very large collections. +* Debugging Difficulty: Debugging a chain of operations might be more challenging due to the lack of intermediate variables. +* Limited to Collections: Primarily focused on collections, and its utility might be limited outside of collection processing. + +## Related Java Design Patterns + +* [Builder](https://java-design-patterns.com/patterns/builder/): Similar fluent interface style but used for object construction. +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Conceptually similar in chaining handlers, but applied to object requests rather than data collection processing. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Can be used within a pipeline stage to encapsulate different algorithms that can be selected at runtime. + +## References and Credits + +* [Functional Programming in Scala](https://amzn.to/4cEo6K2) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3THp4wy) +* [Refactoring: Improving the Design of Existing Code](https://amzn.to/3VDMWDO) +* [Collection Pipeline (Martin Fowler)](https://martinfowler.com/articles/collection-pipeline/) diff --git a/collection-pipeline/etc/collection-pipeline.png b/collection-pipeline/etc/collection-pipeline.png deleted file mode 100644 index 67d52629cb86..000000000000 Binary files a/collection-pipeline/etc/collection-pipeline.png and /dev/null differ diff --git a/collection-pipeline/etc/collection-pipeline.ucls b/collection-pipeline/etc/collection-pipeline.ucls deleted file mode 100644 index 6373db62f67a..000000000000 --- a/collection-pipeline/etc/collection-pipeline.ucls +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/collection-pipeline/etc/collection-pipeline.urm.png b/collection-pipeline/etc/collection-pipeline.urm.png new file mode 100644 index 000000000000..074dffe7b14e Binary files /dev/null and b/collection-pipeline/etc/collection-pipeline.urm.png differ diff --git a/collection-pipeline/pom.xml b/collection-pipeline/pom.xml index 63de6d75f523..b3c36cf06ef6 100644 --- a/collection-pipeline/pom.xml +++ b/collection-pipeline/pom.xml @@ -34,6 +34,14 @@ collection-pipeline + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java index 97e13231b24f..7b4537800437 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java @@ -22,22 +22,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.collectionpipeline; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -/** - * A Car class that has the properties of make, model, year and category. - */ -@Getter -@EqualsAndHashCode -@RequiredArgsConstructor -public class Car { - private final String make; - private final String model; - private final int year; - private final Category category; +package com.iluwatar.collectionpipeline; -} \ No newline at end of file +/** A Car class that has the properties of make, model, year and category. */ +public record Car(String make, String model, int year, Category category) {} diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java index b96af01dbe53..2bfaa676060e 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java @@ -26,12 +26,9 @@ import java.util.List; -/** - * A factory class to create a collection of {@link Car} instances. - */ +/** A factory class to create a collection of {@link Car} instances. */ public class CarFactory { - private CarFactory() { - } + private CarFactory() {} /** * Factory method to create a {@link List} of {@link Car} instances. @@ -39,11 +36,12 @@ private CarFactory() { * @return {@link List} of {@link Car} */ public static List createCars() { - return List.of(new Car("Jeep", "Wrangler", 2011, Category.JEEP), + return List.of( + new Car("Jeep", "Wrangler", 2011, Category.JEEP), new Car("Jeep", "Comanche", 1990, Category.JEEP), new Car("Dodge", "Avenger", 2010, Category.SEDAN), new Car("Buick", "Cascada", 2016, Category.CONVERTIBLE), new Car("Ford", "Focus", 2012, Category.SEDAN), new Car("Chevrolet", "Geo Metro", 1992, Category.CONVERTIBLE)); } -} \ No newline at end of file +} diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java index c7b3dfcdccd3..9de7e4da3b50 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java @@ -24,9 +24,7 @@ */ package com.iluwatar.collectionpipeline; -/** - * Enum for the category of car. - */ +/** Enum for the category of car. */ public enum Category { JEEP, SEDAN, diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java index adf0fda4bda2..dfe2d9ea5697 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.collectionpipeline; import java.util.Comparator; @@ -32,20 +33,19 @@ /** * Iterating and sorting with a collection pipeline * - *

In functional programming, it's common to sequence complex operations through - * a series of smaller modular functions or operations. The series is called a composition of - * functions, or a function composition. When a collection of data flows through a function - * composition, it becomes a collection pipeline. Function Composition and Collection Pipeline are - * two design patterns frequently used in functional-style programming. + *

In functional programming, it's common to sequence complex operations through a series of + * smaller modular functions or operations. The series is called a composition of functions, or a + * function composition. When a collection of data flows through a function composition, it becomes + * a collection pipeline. Function Composition and Collection Pipeline are two design patterns + * frequently used in functional-style programming. * - *

Instead of passing a lambda expression to the map method, we passed the - * method reference Car::getModel. Likewise, instead of passing the lambda expression car -> - * car.getYear() to the comparing method, we passed the method reference Car::getYear. Method - * references are short, concise, and expressive. It is best to use them wherever possible. + *

Instead of passing a lambda expression to the map method, we passed the method reference + * Car::getModel. Likewise, instead of passing the lambda expression car -> car.getYear() to the + * comparing method, we passed the method reference Car::getYear. Method references are short, + * concise, and expressive. It is best to use them wherever possible. */ public class FunctionalProgramming { - private FunctionalProgramming() { - } + private FunctionalProgramming() {} /** * Method to get models using for collection pipeline. @@ -54,9 +54,11 @@ private FunctionalProgramming() { * @return {@link List} of {@link String} representing models built after year 2000 */ public static List getModelsAfter2000(List cars) { - return cars.stream().filter(car -> car.getYear() > 2000) - .sorted(Comparator.comparing(Car::getYear)) - .map(Car::getModel).toList(); + return cars.stream() + .filter(car -> car.year() > 2000) + .sorted(Comparator.comparing(Car::year)) + .map(Car::model) + .toList(); } /** @@ -66,7 +68,7 @@ public static List getModelsAfter2000(List cars) { * @return {@link Map} with category as key and cars belonging to that category as value */ public static Map> getGroupingOfCarsByCategory(List cars) { - return cars.stream().collect(Collectors.groupingBy(Car::getCategory)); + return cars.stream().collect(Collectors.groupingBy(Car::category)); } /** @@ -76,8 +78,11 @@ public static Map> getGroupingOfCarsByCategory(List car * @return {@link List} of {@link Car} to belonging to the group */ public static List getSedanCarsOwnedSortedByDate(List persons) { - return persons.stream().map(Person::getCars).flatMap(List::stream) - .filter(car -> Category.SEDAN.equals(car.getCategory())) - .sorted(Comparator.comparing(Car::getYear)).toList(); + return persons.stream() + .map(Person::cars) + .flatMap(List::stream) + .filter(car -> Category.SEDAN.equals(car.category())) + .sorted(Comparator.comparing(Car::year)) + .toList(); } } diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java index e3359a389b0d..24c0b965e4da 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java @@ -35,21 +35,20 @@ * Imperative-style programming to iterate over the list and get the names of cars made later than * the year 2000. We then sort the models in ascending order by year. * - *

As you can see, there's a lot of looping in this code. First, the - * getModelsAfter2000UsingFor method takes a list of cars as its parameter. It extracts or filters - * out cars made after the year 2000, putting them into a new list named carsSortedByYear. Next, it - * sorts that list in ascending order by year-of-make. Finally, it loops through the list - * carsSortedByYear to get the model names and returns them in a list. + *

As you can see, there's a lot of looping in this code. First, the getModelsAfter2000UsingFor + * method takes a list of cars as its parameter. It extracts or filters out cars made after the year + * 2000, putting them into a new list named carsSortedByYear. Next, it sorts that list in ascending + * order by year-of-make. Finally, it loops through the list carsSortedByYear to get the model names + * and returns them in a list. * - *

This short example demonstrates what I call the effect of statements. While - * functions and methods in general can be used as expressions, the {@link Collections} sort method - * doesn't return a result. Because it is used as a statement, it mutates the list given as - * argument. Both of the for loops also mutate lists as they iterate. Being statements, that's just - * how these elements work. As a result, the code contains unnecessary garbage variables + *

This short example demonstrates what I call the effect of statements. While functions and + * methods in general can be used as expressions, the {@link Collections} sort method doesn't return + * a result. Because it is used as a statement, it mutates the list given as argument. Both of the + * for loops also mutate lists as they iterate. Being statements, that's just how these elements + * work. As a result, the code contains unnecessary garbage variables */ public class ImperativeProgramming { - private ImperativeProgramming() { - } + private ImperativeProgramming() {} /** * Method to return the car models built after year 2000 using for loops. @@ -61,21 +60,23 @@ public static List getModelsAfter2000(List cars) { List carsSortedByYear = new ArrayList<>(); for (Car car : cars) { - if (car.getYear() > 2000) { + if (car.year() > 2000) { carsSortedByYear.add(car); } } - Collections.sort(carsSortedByYear, new Comparator() { - @Override - public int compare(Car car1, Car car2) { - return car1.getYear() - car2.getYear(); - } - }); + Collections.sort( + carsSortedByYear, + new Comparator() { + @Override + public int compare(Car car1, Car car2) { + return car1.year() - car2.year(); + } + }); List models = new ArrayList<>(); for (Car car : carsSortedByYear) { - models.add(car.getModel()); + models.add(car.model()); } return models; @@ -90,12 +91,12 @@ public int compare(Car car1, Car car2) { public static Map> getGroupingOfCarsByCategory(List cars) { Map> groupingByCategory = new HashMap<>(); for (Car car : cars) { - if (groupingByCategory.containsKey(car.getCategory())) { - groupingByCategory.get(car.getCategory()).add(car); + if (groupingByCategory.containsKey(car.category())) { + groupingByCategory.get(car.category()).add(car); } else { List categoryCars = new ArrayList<>(); categoryCars.add(car); - groupingByCategory.put(car.getCategory(), categoryCars); + groupingByCategory.put(car.category(), categoryCars); } } return groupingByCategory; @@ -111,22 +112,23 @@ public static Map> getGroupingOfCarsByCategory(List car public static List getSedanCarsOwnedSortedByDate(List persons) { List cars = new ArrayList<>(); for (Person person : persons) { - cars.addAll(person.getCars()); + cars.addAll(person.cars()); } List sedanCars = new ArrayList<>(); for (Car car : cars) { - if (Category.SEDAN.equals(car.getCategory())) { + if (Category.SEDAN.equals(car.category())) { sedanCars.add(car); } } - sedanCars.sort(new Comparator() { - @Override - public int compare(Car o1, Car o2) { - return o1.getYear() - o2.getYear(); - } - }); + sedanCars.sort( + new Comparator() { + @Override + public int compare(Car o1, Car o2) { + return o1.year() - o2.year(); + } + }); return sedanCars; } diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java index 80b6a8bf0303..a84dc7f72a31 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java @@ -25,16 +25,6 @@ package com.iluwatar.collectionpipeline; import java.util.List; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -/** - * A Person class that has the list of cars that the person owns and use. - */ -@Getter -@RequiredArgsConstructor -public class Person { - - private final List cars; - -} \ No newline at end of file +/** A Person class that has the list of cars that the person owns and use. */ +public record Person(List cars) {} diff --git a/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java b/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java index 959d0c6b5b83..9a990b09ec15 100644 --- a/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java +++ b/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java @@ -31,9 +31,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; -/** - * Tests that Collection Pipeline methods work as expected. - */ +/** Tests that Collection Pipeline methods work as expected. */ @Slf4j class AppTest { @@ -53,19 +51,20 @@ void testGetModelsAfter2000UsingPipeline() { @Test void testGetGroupingOfCarsByCategory() { - var modelsExpected = Map.of( - Category.CONVERTIBLE, List.of( - new Car("Buick", "Cascada", 2016, Category.CONVERTIBLE), - new Car("Chevrolet", "Geo Metro", 1992, Category.CONVERTIBLE) - ), - Category.SEDAN, List.of( - new Car("Dodge", "Avenger", 2010, Category.SEDAN), - new Car("Ford", "Focus", 2012, Category.SEDAN) - ), - Category.JEEP, List.of( - new Car("Jeep", "Wrangler", 2011, Category.JEEP), - new Car("Jeep", "Comanche", 1990, Category.JEEP)) - ); + var modelsExpected = + Map.of( + Category.CONVERTIBLE, + List.of( + new Car("Buick", "Cascada", 2016, Category.CONVERTIBLE), + new Car("Chevrolet", "Geo Metro", 1992, Category.CONVERTIBLE)), + Category.SEDAN, + List.of( + new Car("Dodge", "Avenger", 2010, Category.SEDAN), + new Car("Ford", "Focus", 2012, Category.SEDAN)), + Category.JEEP, + List.of( + new Car("Jeep", "Wrangler", 2011, Category.JEEP), + new Car("Jeep", "Comanche", 1990, Category.JEEP))); var modelsFunctional = FunctionalProgramming.getGroupingOfCarsByCategory(cars); var modelsImperative = ImperativeProgramming.getGroupingOfCarsByCategory(cars); LOGGER.info("Category " + modelsFunctional); @@ -76,10 +75,10 @@ void testGetGroupingOfCarsByCategory() { @Test void testGetSedanCarsOwnedSortedByDate() { var john = new Person(cars); - var modelsExpected = List.of( - new Car("Dodge", "Avenger", 2010, Category.SEDAN), - new Car("Ford", "Focus", 2012, Category.SEDAN) - ); + var modelsExpected = + List.of( + new Car("Dodge", "Avenger", 2010, Category.SEDAN), + new Car("Ford", "Focus", 2012, Category.SEDAN)); var modelsFunctional = FunctionalProgramming.getSedanCarsOwnedSortedByDate(List.of(john)); var modelsImperative = ImperativeProgramming.getSedanCarsOwnedSortedByDate(List.of(john)); assertEquals(modelsExpected, modelsFunctional); diff --git a/combinator/README.md b/combinator/README.md index 15ec1ea59019..0ebe7deea67a 100644 --- a/combinator/README.md +++ b/combinator/README.md @@ -1,83 +1,88 @@ --- -title: Combinator -category: Idiom +title: "Combinator Pattern in Java: Crafting Flexible Code Compositions" +shortTitle: Combinator +description: "Learn how to use the Combinator pattern in Java with real-world examples and comprehensive explanations. Enhance your Java design skills with this detailed guide." +category: Functional language: en tag: - - Reactive + - Abstraction + - Code simplification + - Functional decomposition + - Idiom + - Reactive --- ## Also known as -Composition pattern +* Function Composition +* Functional Combinator -## Intent +## Intent of Combinator Design Pattern -The functional pattern representing a style of organizing libraries centered around the idea of combining functions. -Putting it simply, there is some type T, some functions for constructing “primitive” values of type T, and some “combinators” which can combine values of type T in various ways to build up more complex values of type T. +The Combinator pattern, a functional programming technique widely used in Java, is essential for combining functions to build complex behaviors. This pattern allows developers to combine multiple smaller functions or operations into a single, more complex operation, promoting flexible and reusable code. By leveraging higher-order functions, the Combinator pattern enhances code reuse and maintainability in Java applications, making it a valuable tool in software design. This approach fosters the creation of modular, scalable solutions in Java development. -## Explanation +## Detailed Explanation of Combinator Pattern with Real-World Examples -Real world example +Real-world example -> In computer science, combinatory logic is used as a simplified model of computation, used in computability theory and proof theory. Despite its simplicity, combinatory logic captures many essential features of computation. -> +> In the real world, the combinator design pattern can be likened to a meal preparation process in a kitchen. Imagine a chef who has a set of simple operations: chopping vegetables, boiling water, cooking rice, grilling chicken, and mixing ingredients. Each of these operations is a standalone function. The chef can combine these operations in various sequences to create different dishes. For example, to prepare a chicken rice bowl, the chef can combine the operations of cooking rice, grilling chicken, and mixing them with vegetables. By reusing these basic operations and combining them in different ways, the chef can efficiently prepare a wide variety of meals. Similarly, in software, the combinator pattern allows developers to combine simple, reusable functions to create more complex behaviors. In plain words -> The combinator allows you to create new "things" from previously defined "things". -> +> The combinator design pattern combines simple, reusable functions to create more complex and flexible operations. Wikipedia says -> A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments. -> +> A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments. -**Programmatic Example** +## Programmatic Example of Combinator Pattern in Java -Translating the combinator example above. First of all, we have a interface consist of several methods `contains`, `not`, `or`, `and` . +In software design, combinatory logic is pivotal for creating reusable and modular code components. By leveraging higher-order functions, the Combinator pattern promotes code reuse and maintainability in Java applications. + +In this Java example, we demonstrate the implementation of combinators such as `contains`, `not`, `or`, and `and` to create complex finders. ```java // Functional interface to find lines in text. public interface Finder { - // The function to find lines in text. - List find(String text); - - // Simple implementation of function {@link #find(String)}. - static Finder contains(String word) { - return txt -> Stream.of(txt.split("\n")) - .filter(line -> line.toLowerCase().contains(word.toLowerCase())) - .collect(Collectors.toList()); - } - - // combinator not. - default Finder not(Finder notFinder) { - return txt -> { - List res = this.find(txt); - res.removeAll(notFinder.find(txt)); - return res; - }; - } - - // combinator or. - default Finder or(Finder orFinder) { - return txt -> { - List res = this.find(txt); - res.addAll(orFinder.find(txt)); - return res; - }; - } - - // combinator and. - default Finder and(Finder andFinder) { - return - txt -> this - .find(txt) - .stream() - .flatMap(line -> andFinder.find(line).stream()) - .collect(Collectors.toList()); - } - ... + // The function to find lines in text. + List find(String text); + + // Simple implementation of function {@link #find(String)}. + static Finder contains(String word) { + return txt -> Stream.of(txt.split("\n")) + .filter(line -> line.toLowerCase().contains(word.toLowerCase())) + .collect(Collectors.toList()); + } + + // combinator not. + default Finder not(Finder notFinder) { + return txt -> { + List res = this.find(txt); + res.removeAll(notFinder.find(txt)); + return res; + }; + } + + // combinator or. + default Finder or(Finder orFinder) { + return txt -> { + List res = this.find(txt); + res.addAll(orFinder.find(txt)); + return res; + }; + } + + // combinator and. + default Finder and(Finder andFinder) { + return + txt -> this + .find(txt) + .stream() + .flatMap(line -> andFinder.find(line).stream()) + .collect(Collectors.toList()); + } + // Other properties and methods... } ``` @@ -87,122 +92,147 @@ Then we have also another combinator for some complex finders `advancedFinder`, // Complex finders consisting of simple finder. public class Finders { - private Finders() { - } - - // Finder to find a complex query. - public static Finder advancedFinder(String query, String orQuery, String notQuery) { - return - Finder.contains(query) - .or(Finder.contains(orQuery)) - .not(Finder.contains(notQuery)); - } - - // Filtered finder looking a query with excluded queries as well. - public static Finder filteredFinder(String query, String... excludeQueries) { - var finder = Finder.contains(query); - - for (String q : excludeQueries) { - finder = finder.not(Finder.contains(q)); - } - return finder; - } - - // Specialized query. Every next query is looked in previous result. - public static Finder specializedFinder(String... queries) { - var finder = identMult(); - - for (String query : queries) { - finder = finder.and(Finder.contains(query)); - } - return finder; - } - - // Expanded query. Looking for alternatives. - public static Finder expandedFinder(String... queries) { - var finder = identSum(); - - for (String query : queries) { - finder = finder.or(Finder.contains(query)); - } - return finder; - } - ... + private Finders() { + } + + // Finder to find a complex query. + public static Finder advancedFinder(String query, String orQuery, String notQuery) { + return + Finder.contains(query) + .or(Finder.contains(orQuery)) + .not(Finder.contains(notQuery)); + } + + // Filtered finder looking a query with excluded queries as well. + public static Finder filteredFinder(String query, String... excludeQueries) { + var finder = Finder.contains(query); + + for (String q : excludeQueries) { + finder = finder.not(Finder.contains(q)); + } + return finder; + } + + // Specialized query. Every next query is looked in previous result. + public static Finder specializedFinder(String... queries) { + var finder = identMult(); + + for (String query : queries) { + finder = finder.and(Finder.contains(query)); + } + return finder; + } + + // Expanded query. Looking for alternatives. + public static Finder expandedFinder(String... queries) { + var finder = identSum(); + + for (String query : queries) { + finder = finder.or(Finder.contains(query)); + } + return finder; + } + // Other properties and methods... } ``` -Now we have created the interface and methods for combinators. Now we have an application working on these combinators. +Now that we have created the interface and methods for combinators, let's see how an application works with them. ```java -var queriesOr = new String[]{"many", "Annabel"}; -var finder = Finders.expandedFinder(queriesOr); -var res = finder.find(text()); -LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res); - -var queriesAnd = new String[]{"Annabel", "my"}; -finder = Finders.specializedFinder(queriesAnd); -res = finder.find(text()); -LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res); - -finder = Finders.advancedFinder("it was", "kingdom", "sea"); -res = finder.find(text()); -LOGGER.info("the result of advanced query is {}", res); - -res = Finders.filteredFinder(" was ", "many", "child").find(text()); -LOGGER.info("the result of filtered query is {}", res); - -private static String text() { - return - "It was many and many a year ago,\n" - + "In a kingdom by the sea,\n" - + "That a maiden there lived whom you may know\n" - + "By the name of ANNABEL LEE;\n" - + "And this maiden she lived with no other thought\n" - + "Than to love and be loved by me.\n" - + "I was a child and she was a child,\n" - + "In this kingdom by the sea;\n" - + "But we loved with a love that was more than love-\n" - + "I and my Annabel Lee;\n" - + "With a love that the winged seraphs of heaven\n" - + "Coveted her and me."; - } +public class CombinatorApp { + + private static final String TEXT = """ + It was many and many a year ago, + In a kingdom by the sea, + That a maiden there lived whom you may know + By the name of ANNABEL LEE; + And this maiden she lived with no other thought + Than to love and be loved by me. + I was a child and she was a child, + In this kingdom by the sea; + But we loved with a love that was more than love- + I and my Annabel Lee; + With a love that the winged seraphs of heaven + Coveted her and me."""; + + public static void main(String[] args) { + var queriesOr = new String[]{"many", "Annabel"}; + var finder = Finders.expandedFinder(queriesOr); + var res = finder.find(text()); + LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res); + + var queriesAnd = new String[]{"Annabel", "my"}; + finder = Finders.specializedFinder(queriesAnd); + res = finder.find(text()); + LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res); + + finder = Finders.advancedFinder("it was", "kingdom", "sea"); + res = finder.find(text()); + LOGGER.info("the result of advanced query is {}", res); + + res = Finders.filteredFinder(" was ", "many", "child").find(text()); + LOGGER.info("the result of filtered query is {}", res); + } + + private static String text() { + return TEXT; + } +} ``` -**Program output:** +Program output: -```java -the result of expanded(or) query[[many, Annabel]] is [It was many and many a year ago,, By the name of ANNABEL LEE;, I and my Annabel Lee;] -the result of specialized(and) query[[Annabel, my]] is [I and my Annabel Lee;] -the result of advanced query is [It was many and many a year ago,] -the result of filtered query is [But we loved with a love that was more than love-] +``` +20:03:52.746 [main] INFO com.iluwatar.combinator.CombinatorApp -- the result of expanded(or) query[[many, Annabel]] is [It was many and many a year ago,, By the name of ANNABEL LEE;, I and my Annabel Lee;] +20:03:52.749 [main] INFO com.iluwatar.combinator.CombinatorApp -- the result of specialized(and) query[[Annabel, my]] is [I and my Annabel Lee;] +20:03:52.750 [main] INFO com.iluwatar.combinator.CombinatorApp -- the result of advanced query is [It was many and many a year ago,] +20:03:52.750 [main] INFO com.iluwatar.combinator.CombinatorApp -- the result of filtered query is [But we loved with a love that was more than love-] ``` Now we can design our app to with the queries finding feature `expandedFinder`, `specializedFinder`, `advancedFinder`, `filteredFinder` which are all derived from `contains`, `or`, `not`, `and`. +## When to Use the Combinator Pattern in Java + +The Combinator pattern is particularly useful in functional programming where complex values are built from simpler, reusable components. + +The applicable scenarios include: + +* The solution to a problem can be constructed from simple, reusable components. +* There is a need for high modularity and reusability of functions. +* The programming environment supports first-class functions and higher-order functions. + +## Real-World Applications of Combinator Pattern in Java -## Class diagram -![alt text](./etc/combinator.urm.png "Combinator class diagram") +* Functional programming languages like Haskell and Scala extensively use combinators for tasks ranging from parsing to UI construction. +* In domain-specific languages, particularly those involved in parsing, such as parsing expression grammars. +* In libraries for functional programming in languages like JavaScript, Python, and Ruby. +* java.util.function.Function#compose +* java.util.function.Function#andThen -## Applicability -Use the combinator pattern when: +## Benefits and Trade-offs of Combinator Pattern -- You are able to create a more complex value from more plain values but having the same type(a combination of them) +Benefits: -## Benefits +* Enhances developer productivity by using domain-specific terms and facilitates parallel execution in Java applications. +* Enhances modularity and reusability by breaking down complex tasks into simpler, composable functions. +* Promotes readability and maintainability by using a declarative style of programming. +* Facilitates lazy evaluation and potentially more efficient execution through function composition. -- From a developers perspective the API is made of terms from the domain. -- There is a clear distinction between combining and application phase. -- One first constructs an instance and then executes it. -- This makes the pattern applicable in a parallel environment. +Trade-offs: +* Can lead to a steep learning curve for those unfamiliar with functional programming principles. +* May result in performance overhead due to the creation of intermediate functions. +* Debugging can be challenging due to the abstract nature of function compositions. -## Real world examples +## Related Java Design Patterns -- java.util.function.Function#compose -- java.util.function.Function#andThen +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Relies on chaining objects, whereas Combinator chains functions. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar to Combinator in enhancing functionality, but Decorator focuses on object augmentation. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Both involve selecting an algorithm at runtime, but Combinator uses composition of functions. -## Credits +## References and Credits -- [Example for java](https://gtrefs.github.io/code/combinator-pattern/) -- [Combinator pattern](https://wiki.haskell.org/Combinator_pattern) -- [Combinatory logic](https://wiki.haskell.org/Combinatory_logic) +* [Functional Programming in Scala](https://amzn.to/4cEo6K2) +* [Haskell: The Craft of Functional Programming](https://amzn.to/4axxtcF) +* [Structure and Interpretation of Computer Programs](https://amzn.to/3PJwVsf) +* [Combinator Pattern with Java 8 (Gregor Trefs)](https://gtrefs.github.io/code/combinator-pattern/) diff --git a/combinator/pom.xml b/combinator/pom.xml index 0e2f41962a5b..d366b383a187 100644 --- a/combinator/pom.xml +++ b/combinator/pom.xml @@ -34,10 +34,37 @@ combinator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.combinator.CombinatorApp + + + + + + + + diff --git a/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java b/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java index 2ca6e16d774e..bf1ec4a017de 100644 --- a/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java +++ b/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java @@ -26,25 +26,20 @@ import lombok.extern.slf4j.Slf4j; - /** - * The functional pattern representing a style of organizing libraries - * centered around the idea of combining functions. - * Putting it simply, there is some type T, some functions - * for constructing "primitive" values of type T, - * and some "combinators" which can combine values of type T - * in various ways to build up more complex values of type T. - * The class {@link Finder} defines a simple function {@link Finder#find(String)} - * and connected functions - * {@link Finder#or(Finder)}, - * {@link Finder#not(Finder)}, - * {@link Finder#and(Finder)} - * Using them the became possible to get more complex functions {@link Finders} + * The functional pattern representing a style of organizing libraries centered around the idea of + * combining functions. Putting it simply, there is some type T, some functions for constructing + * "primitive" values of type T, and some "combinators" which can combine values of type T in + * various ways to build up more complex values of type T. The class {@link Finder} defines a simple + * function {@link Finder#find(String)} and connected functions {@link Finder#or(Finder)}, {@link + * Finder#not(Finder)}, {@link Finder#and(Finder)} Using them the became possible to get more + * complex functions {@link Finders} */ @Slf4j public class CombinatorApp { - private static final String TEXT = """ + private static final String TEXT = + """ It was many and many a year ago, In a kingdom by the sea, That a maiden there lived whom you may know @@ -60,15 +55,16 @@ public class CombinatorApp { /** * main. + * * @param args args */ public static void main(String[] args) { - var queriesOr = new String[]{"many", "Annabel"}; + var queriesOr = new String[] {"many", "Annabel"}; var finder = Finders.expandedFinder(queriesOr); var res = finder.find(text()); LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res); - var queriesAnd = new String[]{"Annabel", "my"}; + var queriesAnd = new String[] {"Annabel", "my"}; finder = Finders.specializedFinder(queriesAnd); res = finder.find(text()); LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res); @@ -79,12 +75,10 @@ public static void main(String[] args) { res = Finders.filteredFinder(" was ", "many", "child").find(text()); LOGGER.info("the result of filtered query is {}", res); - } private static String text() { return TEXT; } - } diff --git a/combinator/src/main/java/com/iluwatar/combinator/Finder.java b/combinator/src/main/java/com/iluwatar/combinator/Finder.java index 1189367dedb1..cc2d5ce84918 100644 --- a/combinator/src/main/java/com/iluwatar/combinator/Finder.java +++ b/combinator/src/main/java/com/iluwatar/combinator/Finder.java @@ -28,13 +28,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -/** - * Functional interface to find lines in text. - */ +/** Functional interface to find lines in text. */ public interface Finder { /** * The function to find lines in text. + * * @param text full tet * @return result of searching */ @@ -42,17 +41,20 @@ public interface Finder { /** * Simple implementation of function {@link #find(String)}. + * * @param word for searching * @return this */ static Finder contains(String word) { - return txt -> Stream.of(txt.split("\n")) - .filter(line -> line.toLowerCase().contains(word.toLowerCase())) - .collect(Collectors.toList()); + return txt -> + Stream.of(txt.split("\n")) + .filter(line -> line.toLowerCase().contains(word.toLowerCase())) + .collect(Collectors.toList()); } /** * combinator not. + * * @param notFinder finder to combine * @return new finder including previous finders */ @@ -66,6 +68,7 @@ default Finder not(Finder notFinder) { /** * combinator or. + * * @param orFinder finder to combine * @return new finder including previous finders */ @@ -79,16 +82,14 @@ default Finder or(Finder orFinder) { /** * combinator and. + * * @param andFinder finder to combine * @return new finder including previous finders */ default Finder and(Finder andFinder) { - return - txt -> this - .find(txt) - .stream() + return txt -> + this.find(txt).stream() .flatMap(line -> andFinder.find(line).stream()) .collect(Collectors.toList()); } - } diff --git a/combinator/src/main/java/com/iluwatar/combinator/Finders.java b/combinator/src/main/java/com/iluwatar/combinator/Finders.java index 1920c85d4260..a6f8dd7cac79 100644 --- a/combinator/src/main/java/com/iluwatar/combinator/Finders.java +++ b/combinator/src/main/java/com/iluwatar/combinator/Finders.java @@ -28,30 +28,25 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -/** - * Complex finders consisting of simple finder. - */ +/** Complex finders consisting of simple finder. */ public class Finders { - private Finders() { - } - + private Finders() {} /** * Finder to find a complex query. + * * @param query to find * @param orQuery alternative to find * @param notQuery exclude from search * @return new finder */ public static Finder advancedFinder(String query, String orQuery, String notQuery) { - return - Finder.contains(query) - .or(Finder.contains(orQuery)) - .not(Finder.contains(notQuery)); + return Finder.contains(query).or(Finder.contains(orQuery)).not(Finder.contains(notQuery)); } /** * Filtered finder looking a query with excluded queries as well. + * * @param query to find * @param excludeQueries to exclude * @return new finder @@ -63,11 +58,11 @@ public static Finder filteredFinder(String query, String... excludeQueries) { finder = finder.not(Finder.contains(q)); } return finder; - } /** * Specialized query. Every next query is looked in previous result. + * * @param queries array with queries * @return new finder */ @@ -82,6 +77,7 @@ public static Finder specializedFinder(String... queries) { /** * Expanded query. Looking for alternatives. + * * @param queries array with queries. * @return new finder */ diff --git a/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java index 02f8581df62a..4a7a6fff7a4e 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java @@ -32,12 +32,12 @@ class CombinatorAppTest { /** * Issue: Add at least one assertion to this test case. - *

- * Solution: Inserted assertion to check whether the execution of the main method in {@link CombinatorApp#main(String[])} - * throws an exception. + * + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * CombinatorApp#main(String[])} throws an exception. */ @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> CombinatorApp.main(new String[]{})); + assertDoesNotThrow(() -> CombinatorApp.main(new String[] {})); } } diff --git a/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java b/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java index 4f0d42258d62..c4a505df075d 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.combinator; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -33,12 +34,12 @@ class FinderTest { @Test void contains() { var example = """ - the first one - the second one\s - """; + the first one + the second one\s + """; var result = Finder.contains("second").find(example); assertEquals(1, result.size()); - assertEquals( "the second one ", result.get(0)); + assertEquals("the second one ", result.get(0)); } } diff --git a/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java index bf34af1704f7..f8a23927c33e 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.combinator; import static com.iluwatar.combinator.Finders.advancedFinder; @@ -38,48 +39,45 @@ class FindersTest { void advancedFinderTest() { var res = advancedFinder("it was", "kingdom", "sea").find(text()); assertEquals(1, res.size()); - assertEquals( "It was many and many a year ago,", res.get(0)); + assertEquals("It was many and many a year ago,", res.get(0)); } @Test void filteredFinderTest() { var res = filteredFinder(" was ", "many", "child").find(text()); assertEquals(1, res.size()); - assertEquals( "But we loved with a love that was more than love-", res.get(0)); + assertEquals("But we loved with a love that was more than love-", res.get(0)); } @Test void specializedFinderTest() { var res = specializedFinder("love", "heaven").find(text()); assertEquals(1, res.size()); - assertEquals( "With a love that the winged seraphs of heaven", res.get(0)); + assertEquals("With a love that the winged seraphs of heaven", res.get(0)); } @Test void expandedFinderTest() { var res = expandedFinder("It was", "kingdom").find(text()); assertEquals(3, res.size()); - assertEquals( "It was many and many a year ago,", res.get(0)); - assertEquals( "In a kingdom by the sea,", res.get(1)); - assertEquals( "In this kingdom by the sea;", res.get(2)); + assertEquals("It was many and many a year ago,", res.get(0)); + assertEquals("In a kingdom by the sea,", res.get(1)); + assertEquals("In this kingdom by the sea;", res.get(2)); } - private String text() { - return - """ - It was many and many a year ago, - In a kingdom by the sea, - That a maiden there lived whom you may know - By the name of ANNABEL LEE; - And this maiden she lived with no other thought - Than to love and be loved by me. - I was a child and she was a child, - In this kingdom by the sea; - But we loved with a love that was more than love- - I and my Annabel Lee; - With a love that the winged seraphs of heaven - Coveted her and me."""; + return """ + It was many and many a year ago, + In a kingdom by the sea, + That a maiden there lived whom you may know + By the name of ANNABEL LEE; + And this maiden she lived with no other thought + Than to love and be loved by me. + I was a child and she was a child, + In this kingdom by the sea; + But we loved with a love that was more than love- + I and my Annabel Lee; + With a love that the winged seraphs of heaven + Coveted her and me."""; } - } diff --git a/command-query-responsibility-segregation/README.md b/command-query-responsibility-segregation/README.md new file mode 100644 index 000000000000..bc1de83fc681 --- /dev/null +++ b/command-query-responsibility-segregation/README.md @@ -0,0 +1,143 @@ +--- +title: "Command Query Responsibility Segregation in Java: Optimizing Data Interaction for Scalability" +shortTitle: Command Query Responsibility Segregation (CQRS) +description: "Learn about the Command Query Responsibility Segregation (CQRS) pattern in Java. Discover how segregating commands and queries can enhance the scalability, performance, and maintainability of your software systems." +category: Architectural +language: en +tag: + - Event-driven + - Performance + - Scalability +--- + +## Also known as + +* CQRS + +## Intent of Command Query Responsibility Segregation Design Pattern + +Command Query Responsibility Segregation (CQRS) aims to segregate the operations that modify the state of an application (commands) from the operations that read the state (queries). This separation enhances scalability, performance, and maintainability in complex software systems. + +## Detailed Explanation of Command Query Responsibility Segregation Pattern with Real-World Examples + +Real-world example + +> Imagine a modern library where the tasks of borrowing and returning books (commands) are handled at the front desk, while the task of searching for books and reading them (queries) happens in the reading area. The front desk optimizes for transaction efficiency and record-keeping, ensuring books are properly checked in and out. Meanwhile, the reading area is optimized for comfort and accessibility, making it easy for readers to find and engage with the books. This separation improves the library's overall efficiency and user experience, much like the CQRS pattern enhances a software system's performance and maintainability. + +In plain words + +> The CQRS design pattern separates the actions of modifying data (commands) from the actions of retrieving data (queries) to enhance performance, scalability, and maintainability in software systems. By implementing CQRS, you can optimize your system's read and write operations independently, allowing for more efficient data handling and improved overall system performance. + +Microsoft's documentation says + +> CQRS separates reads and writes into different models, using commands to update data, and queries to read data. + +Architecture diagram + +![CQRS Architecture Diagram](./etc/cqrs-architecture-diagram.png) + +## Programmatic Example of CQRS Pattern in Java + +One way to implement the Command Query Responsibility Segregation (CQRS) pattern is to separate the read and write operations into different services. + +Let's see the code implementation first and explain how it works afterward. + +```java +public static void main(String[] args) { + + // Create Authors and Books using CommandService + var commands = new CommandServiceImpl(); + + commands.authorCreated(AppConstants.E_EVANS, "Eric Evans", "evans@email.com"); + commands.authorCreated(AppConstants.J_BLOCH, "Joshua Bloch", "jBloch@email.com"); + commands.authorCreated(AppConstants.M_FOWLER, "Martin Fowler", "mFowler@email.com"); + + commands.bookAddedToAuthor("Domain-Driven Design", 60.08, AppConstants.E_EVANS); + commands.bookAddedToAuthor("Effective Java", 40.54, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Java Puzzlers", 39.99, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Patterns of Enterprise" + + " Application Architecture", 54.01, AppConstants.M_FOWLER); + commands.bookAddedToAuthor("Domain Specific Languages", 48.89, AppConstants.M_FOWLER); + commands.authorNameUpdated(AppConstants.E_EVANS, "Eric J. Evans"); + + // Query the database using QueryService + var queries = new QueryServiceImpl(); + + var nullAuthor = queries.getAuthorByUsername("username"); + var evans = queries.getAuthorByUsername(AppConstants.E_EVANS); + var blochBooksCount = queries.getAuthorBooksCount(AppConstants.J_BLOCH); + var authorsCount = queries.getAuthorsCount(); + var dddBook = queries.getBook("Domain-Driven Design"); + var blochBooks = queries.getAuthorBooks(AppConstants.J_BLOCH); + + LOGGER.info("Author username : {}", nullAuthor); + LOGGER.info("Author evans : {}", evans); + LOGGER.info("jBloch number of books : {}", blochBooksCount); + LOGGER.info("Number of authors : {}", authorsCount); + LOGGER.info("DDD book : {}", dddBook); + LOGGER.info("jBloch books : {}", blochBooks); + + HibernateUtil.getSessionFactory().close(); +} +``` + +1. Command Service: The `CommandServiceImpl` class is used for write operations. It provides methods to create authors and books, and to add books to authors. + +2. Query Service: The `QueryServiceImpl` class is used for read operations. It provides methods to get author and book details. + +This separation of concerns allows for flexibility in how the application handles data access and manipulation, and is a key aspect of the CQRS pattern. + +Program output: + +``` +17:37:56.040 [main] INFO com.iluwatar.cqrs.app.App - Author username : null +17:37:56.040 [main] INFO com.iluwatar.cqrs.app.App - Author evans : Author(name=Eric J. Evans, email=evans@email.com, username=eEvans) +17:37:56.041 [main] INFO com.iluwatar.cqrs.app.App - jBloch number of books : 3 +17:37:56.041 [main] INFO com.iluwatar.cqrs.app.App - Number of authors : 3 +17:37:56.041 [main] INFO com.iluwatar.cqrs.app.App - DDD book : Book(title=Domain-Driven Design, price=60.08) +17:37:56.042 [main] INFO com.iluwatar.cqrs.app.App - jBloch books : [Book(title=Effective Java, price=40.54), Book(title=Java Puzzlers, price=39.99), Book(title=Java Concurrency in Practice, price=29.4)] +``` + +## When to Use the Command Query Responsibility Segregation Pattern in Java + +* Systems requiring distinct models for read and write operations for scalability and maintainability, such as e-commerce platforms and high-traffic websites. +* Complex domain models, like financial services or healthcare applications, where the task of updating objects differs significantly from the task of reading object data. +* Scenarios where performance optimization for read operations is crucial, and the system can benefit from different data models or databases for reads and writes, enhancing data retrieval speed and accuracy. + +## Real-World Applications of CQRS Pattern in Java + +* Distributed Systems and Microservices Architecture, where different services manage read and write responsibilities. +* Event-Sourced Systems, where changes to the application state are stored as a sequence of events. +* High-Performance Web Applications, segregating read and write databases to optimize load handling. + +## Benefits and Trade-offs of Command Query Responsibility Segregation Pattern + +Benefits: + +* Scalability: By separating read and write models, each can be scaled independently according to their specific demands. +* Optimization: Allows for the optimization of read models for query efficiency and write models for transactional integrity. +* Maintainability: Reduces complexity by separating the concerns, leading to cleaner, more maintainable code. +* Flexibility: Offers the flexibility to choose different technologies for the read and write sides according to their requirements. + +Trade-Offs: + +* Complexity: Introduces complexity due to synchronization between read and write models, especially in consistency maintenance. +* Overhead: Might be an overkill for simple systems where the benefits do not outweigh the additional complexity. +* Learning Curve: Requires a deeper understanding and careful design to implement effectively, increasing the initial learning curve. + +## Related Java Design Patterns + +* [Event Sourcing](https://java-design-patterns.com/patterns/event-sourcing/): Often used in conjunction with CQRS, where changes to the application state are stored as a sequence of events. +* Domain-Driven Design (DDD): CQRS fits well within the DDD context, providing clear boundaries and separation of concerns. +* [Repository](https://java-design-patterns.com/patterns/repository/): Can be used to abstract the data layer, providing a more seamless integration between the command and query sides. + +## References and Credits + +* [Implementing Domain-Driven Design](https://amzn.to/3TJN2HH) +* [Microsoft .NET: Architecting Applications for the Enterprise](https://amzn.to/4aktRes) +* [Patterns, Principles, and Practices of Domain-Driven Design](https://amzn.to/3vNV4Hm) +* [CQRS, Task Based UIs, Event Sourcing agh! (Greg Young)](http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/) +* [CQRS (Martin Fowler)](https://martinfowler.com/bliki/CQRS.html) +* [CQRS for Great Good (Oliver Wolf)](https://www.youtube.com/watch?v=Ge53swja9Dw) +* [CQRS pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs) diff --git a/command-query-responsibility-segregation/etc/command-query-responsibility-segregation.urm.puml b/command-query-responsibility-segregation/etc/command-query-responsibility-segregation.urm.puml new file mode 100644 index 000000000000..aef8eef5c1d2 --- /dev/null +++ b/command-query-responsibility-segregation/etc/command-query-responsibility-segregation.urm.puml @@ -0,0 +1,136 @@ +@startuml +package com.iluwatar.cqrs.util { + class HibernateUtil { + - LOGGER : Logger {static} + - SESSIONFACTORY : SessionFactory {static} + + HibernateUtil() + - buildSessionFactory() : SessionFactory {static} + + getSessionFactory() : SessionFactory {static} + } +} +package com.iluwatar.cqrs.app { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.cqrs.dto { + class Author { + - email : String + - name : String + - username : String + + Author() + + Author(name : String, email : String, username : String) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getEmail() : String + + getName() : String + + getUsername() : String + + hashCode() : int + + toString() : String + } + class Book { + - price : double + - title : String + + Book() + + Book(title : String, price : double) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getPrice() : double + + getTitle() : String + + hashCode() : int + + toString() : String + } +} +package com.iluwatar.cqrs.commandes { + interface CommandService { + + authorCreated(String, String, String) {abstract} + + authorEmailUpdated(String, String) {abstract} + + authorNameUpdated(String, String) {abstract} + + authorUsernameUpdated(String, String) {abstract} + + bookAddedToAuthor(String, double, String) {abstract} + + bookPriceUpdated(String, double) {abstract} + + bookTitleUpdated(String, String) {abstract} + } + class CommandServiceImpl { + - sessionFactory : SessionFactory + + CommandServiceImpl() + + authorCreated(username : String, name : String, email : String) + + authorEmailUpdated(username : String, email : String) + + authorNameUpdated(username : String, name : String) + + authorUsernameUpdated(oldUsername : String, newUsername : String) + + bookAddedToAuthor(title : String, price : double, username : String) + + bookPriceUpdated(title : String, price : double) + + bookTitleUpdated(oldTitle : String, newTitle : String) + - getAuthorByUsername(username : String) : Author + - getBookByTitle(title : String) : Book + } +} +package com.iluwatar.cqrs.queries { + interface QueryService { + + getAuthorBooks(String) : List {abstract} + + getAuthorBooksCount(String) : BigInteger {abstract} + + getAuthorByUsername(String) : Author {abstract} + + getAuthorsCount() : BigInteger {abstract} + + getBook(String) : Book {abstract} + } + class QueryServiceImpl { + - sessionFactory : SessionFactory + + QueryServiceImpl() + + getAuthorBooks(username : String) : List + + getAuthorBooksCount(username : String) : BigInteger + + getAuthorByUsername(username : String) : Author + + getAuthorsCount() : BigInteger + + getBook(title : String) : Book + } +} +package com.iluwatar.cqrs.constants { + class AppConstants { + + E_EVANS : String {static} + + J_BLOCH : String {static} + + M_FOWLER : String {static} + + USER_NAME : String {static} + + AppConstants() + } +} +package com.iluwatar.cqrs.domain.model { + class Author { + - email : String + - id : long + - name : String + - username : String + # Author() + + Author(username : String, name : String, email : String) + + getEmail() : String + + getId() : long + + getName() : String + + getUsername() : String + + setEmail(email : String) + + setId(id : long) + + setName(name : String) + + setUsername(username : String) + + toString() : String + } + class Book { + - author : Author + - id : long + - price : double + - title : String + # Book() + + Book(title : String, price : double, author : Author) + + getAuthor() : Author + + getId() : long + + getPrice() : double + + getTitle() : String + + setAuthor(author : Author) + + setId(id : long) + + setPrice(price : double) + + setTitle(title : String) + + toString() : String + } +} +Book --> "-author" Author +CommandServiceImpl ..|> CommandService +QueryServiceImpl ..|> QueryService +@enduml \ No newline at end of file diff --git a/command-query-responsibility-segregation/etc/cqrs-architecture-diagram.png b/command-query-responsibility-segregation/etc/cqrs-architecture-diagram.png new file mode 100644 index 000000000000..7391582c245c Binary files /dev/null and b/command-query-responsibility-segregation/etc/cqrs-architecture-diagram.png differ diff --git a/cqrs/etc/cqrs.png b/command-query-responsibility-segregation/etc/cqrs.png similarity index 100% rename from cqrs/etc/cqrs.png rename to command-query-responsibility-segregation/etc/cqrs.png diff --git a/cqrs/etc/cqrs.ucls b/command-query-responsibility-segregation/etc/cqrs.ucls similarity index 100% rename from cqrs/etc/cqrs.ucls rename to command-query-responsibility-segregation/etc/cqrs.ucls diff --git a/cqrs/etc/cqrs.urm.puml b/command-query-responsibility-segregation/etc/cqrs.urm.puml similarity index 100% rename from cqrs/etc/cqrs.urm.puml rename to command-query-responsibility-segregation/etc/cqrs.urm.puml diff --git a/command-query-responsibility-segregation/pom.xml b/command-query-responsibility-segregation/pom.xml new file mode 100644 index 000000000000..c3c277dd0b80 --- /dev/null +++ b/command-query-responsibility-segregation/pom.xml @@ -0,0 +1,89 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + command-query-responsibility-segregation + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + com.h2database + h2 + + + org.hibernate + hibernate-core + 5.6.15.Final + + + org.glassfish.jaxb + jaxb-runtime + 2.3.3 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.cqrs.app.App + + + + + + + + + diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/app/App.java similarity index 94% rename from cqrs/src/main/java/com/iluwatar/cqrs/app/App.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/app/App.java index 6e73318afffb..62317638955c 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/app/App.java @@ -32,7 +32,7 @@ /** * CQRS : Command Query Responsibility Segregation. A pattern used to separate query services from - * commands or writes services. The pattern is very simple but it has many consequences. For + * commands or writes services. The pattern is very simple, but it has many consequences. For * example, it can be used to tackle down a complex domain, or to use other architectures that were * hard to implement with the classical way. * @@ -50,9 +50,10 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var commands = new CommandServiceImpl(); // Create Authors and Books using CommandService + var commands = new CommandServiceImpl(); + commands.authorCreated(AppConstants.E_EVANS, "Eric Evans", "evans@email.com"); commands.authorCreated(AppConstants.J_BLOCH, "Joshua Bloch", "jBloch@email.com"); commands.authorCreated(AppConstants.M_FOWLER, "Martin Fowler", "mFowler@email.com"); @@ -61,14 +62,14 @@ public static void main(String[] args) { commands.bookAddedToAuthor("Effective Java", 40.54, AppConstants.J_BLOCH); commands.bookAddedToAuthor("Java Puzzlers", 39.99, AppConstants.J_BLOCH); commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, AppConstants.J_BLOCH); - commands.bookAddedToAuthor("Patterns of Enterprise" - + " Application Architecture", 54.01, AppConstants.M_FOWLER); + commands.bookAddedToAuthor( + "Patterns of Enterprise" + " Application Architecture", 54.01, AppConstants.M_FOWLER); commands.bookAddedToAuthor("Domain Specific Languages", 48.89, AppConstants.M_FOWLER); commands.authorNameUpdated(AppConstants.E_EVANS, "Eric J. Evans"); + // Query the database using QueryService var queries = new QueryServiceImpl(); - // Query the database using QueryService var nullAuthor = queries.getAuthorByUsername("username"); var evans = queries.getAuthorByUsername(AppConstants.E_EVANS); var blochBooksCount = queries.getAuthorBooksCount(AppConstants.J_BLOCH); @@ -85,5 +86,4 @@ public static void main(String[] args) { HibernateUtil.getSessionFactory().close(); } - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java similarity index 96% rename from cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java index 114280afb65f..9cf8c52cbb09 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.cqrs.commandes; -/** - * This interface represents the commands of the CQRS pattern. - */ +/** This interface represents the commands of the CQRS pattern. */ public interface CommandService { void authorCreated(String username, String name, String email); @@ -42,5 +40,4 @@ public interface CommandService { void bookTitleUpdated(String oldTitle, String newTitle); void bookPriceUpdated(String title, double price); - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java similarity index 99% rename from cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java index 52b32dc45f85..b4a368c98319 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java @@ -140,5 +140,4 @@ public void bookPriceUpdated(String title, double price) { session.getTransaction().commit(); } } - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java similarity index 97% rename from cqrs/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java index 5753f8c2bd3a..71d266f43c35 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java @@ -24,14 +24,11 @@ */ package com.iluwatar.cqrs.constants; -/** - * Class to define the constants. - */ +/** Class to define the constants. */ public class AppConstants { public static final String E_EVANS = "eEvans"; public static final String J_BLOCH = "jBloch"; public static final String M_FOWLER = "mFowler"; public static final String USER_NAME = "username"; - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Author.java similarity index 91% rename from cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Author.java index ff9192717e8e..03155d67a30b 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Author.java @@ -32,9 +32,7 @@ import lombok.Setter; import lombok.ToString; -/** - * This is an Author entity. It is used by Hibernate for persistence. - */ +/** This is an Author entity. It is used by Hibernate for persistence. */ @ToString @Getter @Setter @@ -43,6 +41,7 @@ public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + private String username; private String name; private String email; @@ -51,8 +50,8 @@ public class Author { * Constructor. * * @param username username of the author - * @param name name of the author - * @param email email of the author + * @param name name of the author + * @param email email of the author */ public Author(String username, String name, String email) { this.username = username; @@ -60,7 +59,5 @@ public Author(String username, String name, String email) { this.email = email; } - protected Author() { - } - + protected Author() {} } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Book.java similarity index 93% rename from cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Book.java index 171c91d4da83..2e2c356528e9 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Book.java @@ -45,16 +45,16 @@ public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + private String title; private double price; - @ManyToOne - private Author author; + @ManyToOne private Author author; /** * Constructor. * - * @param title title of the book - * @param price price of the book + * @param title title of the book + * @param price price of the book * @param author author of the book */ public Book(String title, double price, Author author) { @@ -63,7 +63,5 @@ public Book(String title, double price, Author author) { this.author = author; } - protected Book() { - } - + protected Book() {} } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Author.java similarity index 93% rename from cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Author.java index 35a565cfa332..58074e6dafe2 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Author.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; -/** - * This is a DTO (Data Transfer Object) author, contains only useful information to be returned. - */ +/** This is a DTO (Data Transfer Object) author, contains only useful information to be returned. */ @ToString @EqualsAndHashCode @Getter @@ -43,5 +41,4 @@ public class Author { private String name; private String email; private String username; - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Book.java similarity index 93% rename from cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Book.java index a938c65d39a9..72ce5b8c249b 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Book.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; -/** - * This is a DTO (Data Transfer Object) book, contains only useful information to be returned. - */ +/** This is a DTO (Data Transfer Object) book, contains only useful information to be returned. */ @ToString @EqualsAndHashCode @Getter @@ -42,5 +40,4 @@ public class Book { private String title; private double price; - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryService.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryService.java similarity index 95% rename from cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryService.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryService.java index c226c6f213a6..b37c1dad05a9 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryService.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryService.java @@ -29,9 +29,7 @@ import java.math.BigInteger; import java.util.List; -/** - * This interface represents the query methods of the CQRS pattern. - */ +/** This interface represents the query methods of the CQRS pattern. */ public interface QueryService { Author getAuthorByUsername(String username); @@ -43,5 +41,4 @@ public interface QueryService { BigInteger getAuthorBooksCount(String username); BigInteger getAuthorsCount(); - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java similarity index 82% rename from cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java index ed6db0b500cb..60847d1c6db6 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java @@ -45,9 +45,10 @@ public class QueryServiceImpl implements QueryService { public Author getAuthorByUsername(String username) { Author authorDto; try (var session = sessionFactory.openSession()) { - Query sqlQuery = session.createQuery( + Query sqlQuery = + session.createQuery( "select new com.iluwatar.cqrs.dto.Author(a.name, a.email, a.username)" - + " from com.iluwatar.cqrs.domain.model.Author a where a.username=:username"); + + " from com.iluwatar.cqrs.domain.model.Author a where a.username=:username"); sqlQuery.setParameter(AppConstants.USER_NAME, username); authorDto = sqlQuery.uniqueResult(); } @@ -58,9 +59,10 @@ public Author getAuthorByUsername(String username) { public Book getBook(String title) { Book bookDto; try (var session = sessionFactory.openSession()) { - Query sqlQuery = session.createQuery( + Query sqlQuery = + session.createQuery( "select new com.iluwatar.cqrs.dto.Book(b.title, b.price)" - + " from com.iluwatar.cqrs.domain.model.Book b where b.title=:title"); + + " from com.iluwatar.cqrs.domain.model.Book b where b.title=:title"); sqlQuery.setParameter("title", title); bookDto = sqlQuery.uniqueResult(); } @@ -71,10 +73,11 @@ public Book getBook(String title) { public List getAuthorBooks(String username) { List bookDtos; try (var session = sessionFactory.openSession()) { - Query sqlQuery = session.createQuery( + Query sqlQuery = + session.createQuery( "select new com.iluwatar.cqrs.dto.Book(b.title, b.price)" - + " from com.iluwatar.cqrs.domain.model.Author a, com.iluwatar.cqrs.domain.model.Book b " - + "where b.author.id = a.id and a.username=:username"); + + " from com.iluwatar.cqrs.domain.model.Author a, com.iluwatar.cqrs.domain.model.Book b " + + "where b.author.id = a.id and a.username=:username"); sqlQuery.setParameter(AppConstants.USER_NAME, username); bookDtos = sqlQuery.list(); } @@ -85,9 +88,11 @@ public List getAuthorBooks(String username) { public BigInteger getAuthorBooksCount(String username) { BigInteger bookcount; try (var session = sessionFactory.openSession()) { - var sqlQuery = session.createNativeQuery( - "SELECT count(b.title)" + " FROM Book b, Author a" - + " where b.author_id = a.id and a.username=:username"); + var sqlQuery = + session.createNativeQuery( + "SELECT count(b.title)" + + " FROM Book b, Author a" + + " where b.author_id = a.id and a.username=:username"); sqlQuery.setParameter(AppConstants.USER_NAME, username); bookcount = (BigInteger) sqlQuery.uniqueResult(); } @@ -103,5 +108,4 @@ public BigInteger getAuthorsCount() { } return authorcount; } - } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java similarity index 99% rename from cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java rename to command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java index b7f6e76f3faa..0b777fc59b1f 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java @@ -54,5 +54,4 @@ private static SessionFactory buildSessionFactory() { public static SessionFactory getSessionFactory() { return SESSIONFACTORY; } - } diff --git a/cqrs/src/main/resources/hibernate.cfg.xml b/command-query-responsibility-segregation/src/main/resources/hibernate.cfg.xml similarity index 100% rename from cqrs/src/main/resources/hibernate.cfg.xml rename to command-query-responsibility-segregation/src/main/resources/hibernate.cfg.xml diff --git a/cqrs/src/main/resources/logback.xml b/command-query-responsibility-segregation/src/main/resources/logback.xml similarity index 100% rename from cqrs/src/main/resources/logback.xml rename to command-query-responsibility-segregation/src/main/resources/logback.xml diff --git a/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java b/command-query-responsibility-segregation/src/test/java/com/iluwatar/cqrs/IntegrationTest.java similarity index 98% rename from cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java rename to command-query-responsibility-segregation/src/test/java/com/iluwatar/cqrs/IntegrationTest.java index d7df072aa05f..a4fb9ed3bb7a 100644 --- a/cqrs/src/test/java/com/iluwatar/cqrs/IntegrationTest.java +++ b/command-query-responsibility-segregation/src/test/java/com/iluwatar/cqrs/IntegrationTest.java @@ -36,9 +36,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -/** - * Integration test of IQueryService and ICommandService with h2 data - */ +/** Integration test of IQueryService and ICommandService with h2 data */ class IntegrationTest { private static QueryService queryService; @@ -64,7 +62,6 @@ static void initializeAndPopulateDatabase() { commandService.bookAddedToAuthor("title2", 20, "username1"); commandService.bookPriceUpdated("title2", 30); commandService.bookTitleUpdated("title2", "new_title2"); - } @Test @@ -80,7 +77,6 @@ void testGetUpdatedAuthorByUsername() { var author = queryService.getAuthorByUsername("new_username2"); var expectedAuthor = new Author("new_name2", "new_email2", "new_username2"); assertEquals(expectedAuthor, author); - } @Test @@ -109,5 +105,4 @@ void testGetAuthorsCount() { var authorCount = queryService.getAuthorsCount(); assertEquals(new BigInteger("2"), authorCount); } - } diff --git a/cqrs/src/test/resources/hibernate.cfg.xml b/command-query-responsibility-segregation/src/test/resources/hibernate.cfg.xml similarity index 100% rename from cqrs/src/test/resources/hibernate.cfg.xml rename to command-query-responsibility-segregation/src/test/resources/hibernate.cfg.xml diff --git a/cqrs/src/test/resources/logback.xml b/command-query-responsibility-segregation/src/test/resources/logback.xml similarity index 100% rename from cqrs/src/test/resources/logback.xml rename to command-query-responsibility-segregation/src/test/resources/logback.xml diff --git a/command/README.md b/command/README.md index a3b863b78cbe..8877f722e4b9 100644 --- a/command/README.md +++ b/command/README.md @@ -1,26 +1,30 @@ --- -title: Command +title: "Command Pattern in Java: Empowering Flexible Command Execution" +shortTitle: Command +description: "Learn about the Command design pattern in Java with real-world examples, detailed explanations, and practical use cases. Understand how this pattern encapsulates requests as objects to support undo operations and more." category: Behavioral language: en tag: - - Gang of Four + - Decoupling + - Extensibility + - Gang of Four + - Undo --- ## Also known as -Action, Transaction +* Action +* Transaction -## Intent +## Intent of Command Design Pattern -Encapsulate a request as an object, thereby letting you parameterize clients with different -requests, queue or log requests, and support undoable operations. +The Command design pattern is a behavioral pattern used in Java programming. It encapsulates a request as an object, allowing for parameterization of clients with queues, requests, and operations. This pattern also supports undoable operations, enhancing flexibility in managing and executing commands. + +## Detailed Explanation of Command Pattern with Real-World Examples -## Explanation Real-world example -> There is a wizard casting spells on a goblin. The spells are executed on the goblin one by one. -> The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses -> the spells one by one. Each spell here is a command object that can be undone. +> Imagine a smart home system where you can control devices such as lights, thermostats, and security cameras through a central application. Each command to operate these devices is encapsulated as an object, enabling the system to queue, execute sequentially, and undo commands if necessary. This approach decouples control logic from device implementation, allowing easy addition of new devices or features without altering the core application. This flexibility and functionality illustrate the practical application of the Command design pattern in Java programming. In plain words @@ -28,209 +32,188 @@ In plain words Wikipedia says -> In object-oriented programming, the command pattern is a behavioral design pattern in which an -> object is used to encapsulate all information needed to perform an action or trigger an event at -> a later time. +> In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. + +## Programmatic Example of Command Pattern in Java + +In the Command pattern, objects are used to encapsulate all information needed to perform an action or trigger an event at a later time. This pattern is particularly useful for implementing undo functionality in applications. -**Programmatic Example** +In our example, a `Wizard` casts spells on a `Goblin`. Each spell is a command object that can be executed and undone, demonstrating the core principles of the Command pattern in Java. The spells are executed on the goblin one by one. The first spell shrinks the goblin and the second makes him invisible. Then the wizard reverses the spells one by one. Each spell here is a command object that can be undone. -Here's the sample code with wizard and goblin. Let's start from the `Wizard` class. +Let's start from the `Wizard` class. ```java + @Slf4j public class Wizard { - private final Deque undoStack = new LinkedList<>(); - private final Deque redoStack = new LinkedList<>(); + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); - public Wizard() {} + public Wizard() { + } - public void castSpell(Runnable runnable) { - runnable.run(); - undoStack.offerLast(runnable); - } + public void castSpell(Runnable runnable) { + runnable.run(); + undoStack.offerLast(runnable); + } - public void undoLastSpell() { - if (!undoStack.isEmpty()) { - var previousSpell = undoStack.pollLast(); - redoStack.offerLast(previousSpell); - previousSpell.run(); + public void undoLastSpell() { + if (!undoStack.isEmpty()) { + var previousSpell = undoStack.pollLast(); + redoStack.offerLast(previousSpell); + previousSpell.run(); + } } - } - public void redoLastSpell() { - if (!redoStack.isEmpty()) { - var previousSpell = redoStack.pollLast(); - undoStack.offerLast(previousSpell); - previousSpell.run(); + public void redoLastSpell() { + if (!redoStack.isEmpty()) { + var previousSpell = redoStack.pollLast(); + undoStack.offerLast(previousSpell); + previousSpell.run(); + } } - } - @Override - public String toString() { - return "Wizard"; - } + @Override + public String toString() { + return "Wizard"; + } } ``` -Next, we have the goblin who's the target of the spells. +Next, we have the `Goblin` who's the `Target` of the spells. ```java @Slf4j +@Getter +@Setter public abstract class Target { - private Size size; - - private Visibility visibility; - - public Size getSize() { - return size; - } + private Size size; - public void setSize(Size size) { - this.size = size; - } + private Visibility visibility; - public Visibility getVisibility() { - return visibility; - } - - public void setVisibility(Visibility visibility) { - this.visibility = visibility; - } + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } - @Override - public abstract String toString(); + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } - public void printStatus() { - LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); - } + public void changeVisibility() { + var visible = getVisibility() == Visibility.INVISIBLE + ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } } +``` +```java public class Goblin extends Target { - public Goblin() { - setSize(Size.NORMAL); - setVisibility(Visibility.VISIBLE); - } - - @Override - public String toString() { - return "Goblin"; - } - - public void changeSize() { - var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; - setSize(oldSize); - } - - public void changeVisibility() { - var visible = getVisibility() == Visibility.INVISIBLE - ? Visibility.VISIBLE : Visibility.INVISIBLE; - setVisibility(visible); - } + public Goblin() { + setSize(Size.NORMAL); + setVisibility(Visibility.VISIBLE); + } + + @Override + public String toString() { + return "Goblin"; + } } ``` -Finally, we have the wizard in the main function casting spells. +Finally, we can show the full example of `Wizard` casting spells. ```java public static void main(String[] args) { - var wizard = new Wizard(); - var goblin = new Goblin(); + var wizard = new Wizard(); + var goblin = new Goblin(); - // casts shrink/unshrink spell - wizard.castSpell(goblin::changeSize); + goblin.printStatus(); - // casts visible/invisible spell - wizard.castSpell(goblin::changeVisibility); + wizard.castSpell(goblin::changeSize); + goblin.printStatus(); - // undo and redo casts - wizard.undoLastSpell(); - wizard.redoLastSpell(); -``` - -Here's the whole example in action. - -```java -var wizard = new Wizard(); -var goblin = new Goblin(); - -goblin.printStatus(); -wizard.castSpell(goblin::changeSize); -goblin.printStatus(); + wizard.castSpell(goblin::changeVisibility); + goblin.printStatus(); -wizard.castSpell(goblin::changeVisibility); -goblin.printStatus(); + wizard.undoLastSpell(); + goblin.printStatus(); -wizard.undoLastSpell(); -goblin.printStatus(); + wizard.undoLastSpell(); + goblin.printStatus(); -wizard.undoLastSpell(); -goblin.printStatus(); + wizard.redoLastSpell(); + goblin.printStatus(); -wizard.redoLastSpell(); -goblin.printStatus(); - -wizard.redoLastSpell(); -goblin.printStatus(); + wizard.redoLastSpell(); + goblin.printStatus(); +} ``` Here's the program output: -```java -Goblin, [size=normal] [visibility=visible] -Goblin, [size=small] [visibility=visible] -Goblin, [size=small] [visibility=invisible] -Goblin, [size=small] [visibility=visible] -Goblin, [size=normal] [visibility=visible] -Goblin, [size=small] [visibility=visible] -Goblin, [size=small] [visibility=invisible] +``` +20:13:38.406 [main] INFO com.iluwatar.command.Target -- Goblin, [size=normal] [visibility=visible] +20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=visible] +20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=invisible] +20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=visible] +20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=normal] [visibility=visible] +20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=visible] +20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=invisible] ``` -## Class diagram - -![alt text](./etc/command.png "Command") +## When to Use the Command Pattern in Java -## Applicability +The Command design pattern is applicable when you need to parameterize objects with actions, support undo operations, or structure a system around high-level operations built on primitive ones. It is commonly used in GUI buttons, database transactions, and macro recording. Use the Command pattern when you want to: -* Parameterize objects by an action to perform. You can express such parameterization in a -procedural language with a callback function, that is, a function that's registered somewhere to be -called at a later point. Commands are an object-oriented replacement for callbacks. -* Specify, queue, and execute requests at different times. A Command object can have a life -independent of the original request. If the receiver of a request can be represented in an address -space-independent way, then you can transfer a command object for the request to a different process -and fulfill the request there. -* Support undo. The Command's execute operation can store state for reversing its effects in the -command itself. The Command interface must have an added un-execute operation that reverses the -effects of a previous call to execute. The executed commands are stored in a history list. -Unlimited-level undo and redo functionality is achieved by traversing this list backward and forward - calling un-execute and execute, respectively. -* Support logging changes so that they can be reapplied in case of a system crash. By augmenting the -Command interface with load and store operations, you can keep a persistent log of changes. -Recovering from a crash involves reloading logged commands from the disk and re-executing them with -the execute operation. -* Structure a system around high-level operations build on primitive operations. Such a structure is -common in information systems that support transactions. A transaction encapsulates a set of data -changes. The Command pattern offers a way to model transactions. Commands have a common interface, -letting you invoke all transactions the same way. The pattern also makes it easy to extend the -system with new transactions. +* Parameterize objects with actions to perform, offering an object-oriented alternative to callbacks found in procedural languages. Commands can be registered and executed later. +* Specify, queue, and execute requests at different times, allowing commands to exist independently of the original request and even be transferred across processes. +* Support undo functionality, where the Command’s execute operation stores state and includes an un-execute operation to reverse previous actions. This allows for unlimited undo and redo capabilities by maintaining a history list. +* Log changes to reapply them after a system crash. By adding load and store operations to the Command interface, you can maintain a persistent log of changes and recover by reloading and re-executing commands from this log. +* Structure a system around high-level operations built on primitive operations, which is common in transaction-based systems. The Command pattern models transactions by providing a common interface for invoking and extending operations. * Keep a history of requests. * Implement callback functionality. -* Implement the undo functionality. +* Implement undo functionality. -## Known uses +## Real-World Applications of Command Pattern in Java +* GUI Buttons and menu items in desktop applications. +* Operations in database systems and transactional systems that support rollback. +* Macro recording in applications like text editors and spreadsheets. * [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) * [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java) * [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) * [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) -## Credits +## Benefits and Trade-offs of Command Pattern + +Benefits: + +* Decouples the object that invokes the operation from the one that knows how to perform it. +* It's easy to add new Commands, because you don't have to change existing classes. +* You can assemble a set of commands into a composite command. + +Trade-offs: + +* Increases the number of classes for each individual command. +* Can complicate the design by adding multiple layers between senders and receivers. + +## Related Java Design Patterns + +* [Composite](https://java-design-patterns.com/patterns/composite/): Commands can be composed using the Composite pattern to create macro commands. +* [Memento](https://java-design-patterns.com/patterns/memento/): Can be used for implementing undo mechanisms. +* [Observer](https://java-design-patterns.com/patterns/observer/): The pattern can be observed for changes that trigger commands. + +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PFUqSY) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/command/pom.xml b/command/pom.xml index d6532a8db700..83fb68cbf8e3 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -34,6 +34,14 @@ command + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/command/src/main/java/com/iluwatar/command/App.java b/command/src/main/java/com/iluwatar/command/App.java index 18dd6eee3596..a8e0edcdf45c 100644 --- a/command/src/main/java/com/iluwatar/command/App.java +++ b/command/src/main/java/com/iluwatar/command/App.java @@ -32,9 +32,9 @@ *

Four terms always associated with the command pattern are command, receiver, invoker and * client. A command object (spell) knows about the receiver (target) and invokes a method of the * receiver. An invoker object (wizard) receives a reference to the command to be executed and - * optionally does bookkeeping about the command execution. The invoker does not know anything - * about how the command is executed. The client decides which commands to execute at which - * points. To execute a command, it passes a reference of the function to the invoker object. + * optionally does bookkeeping about the command execution. The invoker does not know anything about + * how the command is executed. The client decides which commands to execute at which points. To + * execute a command, it passes a reference of the function to the invoker object. * *

In other words, in this example the wizard casts spells on the goblin. The wizard keeps track * of the previous spells cast, so it is easy to undo them. In addition, the wizard keeps track of diff --git a/command/src/main/java/com/iluwatar/command/Goblin.java b/command/src/main/java/com/iluwatar/command/Goblin.java index ed9ee49b85ea..911983c7b71d 100644 --- a/command/src/main/java/com/iluwatar/command/Goblin.java +++ b/command/src/main/java/com/iluwatar/command/Goblin.java @@ -24,9 +24,7 @@ */ package com.iluwatar.command; -/** - * Goblin is the target of the spells. - */ +/** Goblin is the target of the spells. */ public class Goblin extends Target { public Goblin() { diff --git a/command/src/main/java/com/iluwatar/command/Size.java b/command/src/main/java/com/iluwatar/command/Size.java index d16149c1d81e..203f190a89c4 100644 --- a/command/src/main/java/com/iluwatar/command/Size.java +++ b/command/src/main/java/com/iluwatar/command/Size.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Enumeration for target size. - */ +/** Enumeration for target size. */ @RequiredArgsConstructor public enum Size { - SMALL("small"), NORMAL("normal"); diff --git a/command/src/main/java/com/iluwatar/command/Target.java b/command/src/main/java/com/iluwatar/command/Target.java index 365f6e446f6e..5885e0c4a9a6 100644 --- a/command/src/main/java/com/iluwatar/command/Target.java +++ b/command/src/main/java/com/iluwatar/command/Target.java @@ -1,66 +1,58 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.command; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -/** - * Base class for spell targets. - */ -@Setter -@Slf4j -@Getter -public abstract class Target { - - private Size size; - - private Visibility visibility; - - /** - * Print status. - */ - public void printStatus() { - LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); - } - - /** - * Changes the size of the target. - */ - public void changeSize() { - var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; - setSize(oldSize); - } - - /** - * Changes the visibility of the target. - */ - public void changeVisibility() { - var visible = getVisibility() == Visibility.INVISIBLE - ? Visibility.VISIBLE : Visibility.INVISIBLE; - setVisibility(visible); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.command; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** Base class for spell targets. */ +@Slf4j +@Getter +@Setter +public abstract class Target { + + private Size size; + + private Visibility visibility; + + /** Print status. */ + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } + + /** Changes the size of the target. */ + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } + + /** Changes the visibility of the target. */ + public void changeVisibility() { + var visible = + getVisibility() == Visibility.INVISIBLE ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } +} diff --git a/command/src/main/java/com/iluwatar/command/Visibility.java b/command/src/main/java/com/iluwatar/command/Visibility.java index d1539482e0e8..a07e7876b609 100644 --- a/command/src/main/java/com/iluwatar/command/Visibility.java +++ b/command/src/main/java/com/iluwatar/command/Visibility.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Enumeration for target visibility. - */ +/** Enumeration for target visibility. */ @RequiredArgsConstructor public enum Visibility { - VISIBLE("visible"), INVISIBLE("invisible"); diff --git a/command/src/main/java/com/iluwatar/command/Wizard.java b/command/src/main/java/com/iluwatar/command/Wizard.java index 93d23cd68eff..797c41ec04ad 100644 --- a/command/src/main/java/com/iluwatar/command/Wizard.java +++ b/command/src/main/java/com/iluwatar/command/Wizard.java @@ -28,26 +28,20 @@ import java.util.LinkedList; import lombok.extern.slf4j.Slf4j; -/** - * Wizard is the invoker of the commands. - */ +/** Wizard is the invoker of the commands. */ @Slf4j public class Wizard { private final Deque undoStack = new LinkedList<>(); private final Deque redoStack = new LinkedList<>(); - /** - * Cast spell. - */ + /** Cast spell. */ public void castSpell(Runnable runnable) { runnable.run(); undoStack.offerLast(runnable); } - /** - * Undo last spell. - */ + /** Undo last spell. */ public void undoLastSpell() { if (!undoStack.isEmpty()) { var previousSpell = undoStack.pollLast(); @@ -56,9 +50,7 @@ public void undoLastSpell() { } } - /** - * Redo last spell. - */ + /** Redo last spell. */ public void redoLastSpell() { if (!redoStack.isEmpty()) { var previousSpell = redoStack.pollLast(); diff --git a/command/src/test/java/com/iluwatar/command/AppTest.java b/command/src/test/java/com/iluwatar/command/AppTest.java index 830fee8fb4dc..874bc3609d91 100644 --- a/command/src/test/java/com/iluwatar/command/AppTest.java +++ b/command/src/test/java/com/iluwatar/command/AppTest.java @@ -24,23 +24,21 @@ */ package com.iluwatar.command; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Command example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Command example runs without errors. */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/command/src/test/java/com/iluwatar/command/CommandTest.java b/command/src/test/java/com/iluwatar/command/CommandTest.java index 766cede95b43..5e41c884697b 100644 --- a/command/src/test/java/com/iluwatar/command/CommandTest.java +++ b/command/src/test/java/com/iluwatar/command/CommandTest.java @@ -80,17 +80,18 @@ void testCommand() { * This method asserts that the passed goblin object has the name as expectedName, size as * expectedSize and visibility as expectedVisibility. * - * @param goblin a goblin object whose state is to be verified against other - * parameters - * @param expectedName expectedName of the goblin - * @param expectedSize expected size of the goblin + * @param goblin a goblin object whose state is to be verified against other parameters + * @param expectedName expectedName of the goblin + * @param expectedSize expected size of the goblin * @param expectedVisibility expected visibility of the goblin */ - private void verifyGoblin(Goblin goblin, String expectedName, Size expectedSize, - Visibility expectedVisibility) { + private void verifyGoblin( + Goblin goblin, String expectedName, Size expectedSize, Visibility expectedVisibility) { assertEquals(expectedName, goblin.toString(), "Goblin's name must be same as expectedName"); assertEquals(expectedSize, goblin.getSize(), "Goblin's size must be same as expectedSize"); - assertEquals(expectedVisibility, goblin.getVisibility(), + assertEquals( + expectedVisibility, + goblin.getVisibility(), "Goblin's visibility must be same as expectedVisibility"); } } diff --git a/commander/README.md b/commander/README.md index 472370f4bbe1..902c6af1bcad 100644 --- a/commander/README.md +++ b/commander/README.md @@ -1,25 +1,119 @@ --- -title: Commander -category: Concurrency +title: "Commander Pattern in Java: Orchestrating Complex Commands with Ease" +shortTitle: Commander +description: "Learn about the Commander design pattern in Java, a powerful approach for managing distributed transactions across multiple services. Ensure data consistency and reliability in your microservices architecture with practical examples and use cases." +category: Behavioral language: en tag: - - Cloud distributed + - Cloud distributed + - Microservices + - Transactions +head: + - - meta + - name: keywords + content: --- -## Intent +## Also known as -> Used to handle all problems that can be encountered when doing distributed transactions. +* Distributed Transaction Commander +* Transaction Coordinator -## Class diagram -![alt text](./etc/commander.urm.png "Commander class diagram") +## Intent of Commander Design Pattern -## Applicability -This pattern can be used when we need to make commits into 2 (or more) databases to complete transaction, which cannot be done atomically and can thereby create problems. +The intent of the Commander pattern in Java, especially in the context of distributed transactions, is to manage and coordinate complex transactions across multiple distributed components or services. This pattern ensures data consistency and integrity in distributed systems, making it crucial for microservices architecture. It encapsulates transaction commands and coordination logic, facilitating the implementation of distributed transaction protocols like two-phase commit or Saga. -## Explanation -Handling distributed transactions can be tricky, but if we choose to not handle it carefully, there could be unwanted consequences. Say, we have an e-commerce website which has a Payment microservice and a Shipping microservice. If the shipping is available currently but payment service is not up, or vice versa, how would we deal with it after having already received the order from the user? -We need a mechanism in place which can handle these kinds of situations. We have to direct the order to either one of the services (in this example, shipping) and then add the order into the database of the other service (in this example, payment), since two databases cannot be updated atomically. If currently unable to do it, there should be a queue where this request can be queued, and there has to be a mechanism which allows for a failure in the queueing as well. All this needs to be done by constant retries while ensuring idempotence (even if the request is made several times, the change should only be applied once) by a commander class, to reach a state of eventual consistency. +## Detailed Explanation of Commander Pattern with Real-World Examples -## Credits +Real-world example -* [Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/) +> Imagine organizing a large international music festival where various bands from around the world are scheduled to perform. Each band's arrival, soundcheck, and performance are akin to individual transactions in a distributed system. This scenario mirrors the Commander pattern in Java, where the "Commander" coordinates distributed transactions to maintain overall consistency and reliability. The festival organizer acts as the "Commander," coordinating these transactions to ensure that if a band's flight is delayed (akin to a transaction failure), there's a backup plan, such as rescheduling or swapping time slots with another band (compensating actions), to keep the overall schedule intact. This setup mirrors the Commander pattern in distributed transactions, where various components must be coordinated to achieve a successful outcome despite individual failures. + +In plain words + +> The Commander pattern turns a request into a stand-alone object, allowing for the parameterization of commands, queueing of actions, and the implementation of undo operations. + +## Programmatic Example of Commander Pattern in Java + +Managing transactions across different services in a distributed system, such as an e-commerce platform with separate `Payment` and `Shipping` microservices, requires careful coordination. Using the Commander pattern in Java for transaction coordination helps ensure data consistency and reliability, even when services experience partial failures. + +A strategy to address this involves using a `Commander` component that orchestrates the process. Initially, the order is processed by the available service (`Shipping` in this case). The `Commander` then attempts to synchronize the order with the currently unavailable service (`Payment`) by storing the order details in a database or queueing it for future processing. This queueing system must also account for possible failures in adding requests to the queue. + +The `Commander` repeatedly tries to process the queued orders to ensure both services eventually reflect the same transaction data. This process involves ensuring idempotence, meaning that even if the same order synchronization request is made multiple times, it will only be executed once, preventing duplicate transactions. The goal is to achieve eventual consistency across services, where all systems are synchronized over time despite initial failures or delays. + +Here's a simplified example of how the `Commander` class is used in the `AppAllCases` class: + +```java +public class AppAllCases { + // ... other methods ... + + // Shipping Database Fail Cases + void itemUnavailableCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + // Create a Commander instance + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + // Use the Commander instance to place an order + c.placeOrder(order); + } + + // ... other methods ... +} +``` + +In the `itemUnavailableCase` method, a `Commander` instance is created with the respective services and their databases. Then, a `User` and an `Order` are created, and the `placeOrder` method of the `Commander` instance is called with the order. This triggers the process of placing the order and handling any failures according to the Commander pattern. + +The `Commander` class encapsulates the logic for handling the order placement and any potential failures. This separation of concerns makes the code easier to understand and maintain, and it allows for the reuse of the `Commander` class in different parts of the application. In a real-world application, the `Commander` class would be more complex and would include additional logic for handling different types of failures, retrying failed operations, and coordinating transactions across multiple services. + +Here is the output from executing the `itemUnavailableCase`: + +``` +09:10:13.894 [main] DEBUG com.iluwatar.commander.Commander -- Order YN3V8B7IL2PI: Error in creating shipping request.. +09:10:13.896 [main] INFO com.iluwatar.commander.Commander -- This item is currently unavailable. We will inform you as soon as the item becomes available again. +09:10:13.896 [main] INFO com.iluwatar.commander.Commander -- Order YN3V8B7IL2PI: Item book unavailable, trying to add problem to employee handle.. +09:10:13.897 [Thread-0] INFO com.iluwatar.commander.Commander -- Order YN3V8B7IL2PI: Added order to employee database +``` + +## When to Use the Commander Pattern in Java + +Use the Commander pattern in Java for distributed transactions when: + +* You need to ensure data consistency across distributed services in the event of partial system failures. +* Transactions span multiple microservices or distributed components requiring coordinated commit or rollback. +* You are implementing long-lived transactions requiring compensating actions for rollback. + +## Real-World Applications of Commander Pattern in Java + +* Two-Phase Commit (2PC) Protocols: Coordinating commit or rollback across distributed databases or services. +* Saga Pattern Implementations: Managing long-lived business processes that span multiple microservices, with each step having a compensating action for rollback. +* Distributed Transactions in Microservices Architecture: Coordinating complex operations across microservices while maintaining data integrity and consistency. + +## Benefits and Trade-offs of Commander Pattern + +Benefits: + +* Provides a clear mechanism for managing complex distributed transactions, enhancing system reliability. +* Enables the implementation of compensating transactions, which are crucial for maintaining consistency in long-lived transactions. +* Facilitates the integration of heterogeneous systems within a transactional context. + +Trade-offs: + +* Increases complexity, especially in failure scenarios, due to the need for coordinated rollback mechanisms. +* Potentially impacts performance due to the overhead of coordination and consistency checks. +* Saga-based implementations can lead to increased complexity in understanding the overall business process flow. + +## Related Java Design Patterns + +* [Saga Pattern](https://java-design-patterns.com/patterns/saga/): Often discussed in tandem with the Commander pattern for distributed transactions, focusing on long-lived transactions with compensating actions. + +## References and Credits + +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/4aATcRe) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/4axHwOV) +* [Microservices Patterns: With examples in Java](https://amzn.to/4axjnYW) +* [Distributed Transactions: The Icebergs of Microservices (Graham Lea)](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/) diff --git a/commander/pom.xml b/commander/pom.xml index 5de5e562ae43..1b5897da6586 100644 --- a/commander/pom.xml +++ b/commander/pom.xml @@ -33,76 +33,43 @@ 1.26.0-SNAPSHOT commander + + 2.0.17 + 1.5.6 + org.junit.jupiter junit-jupiter-engine test + + org.slf4j + slf4j-api + ${slf4j.version} + - - - - org.apache.maven.plugins - maven-assembly-plugin - - - Employee - - - - com.iluwatar.commander.AppEmployeeDbFailCases - - - ${project.artifactId}-EmployeeDBFailCase - - - - Message - - - - com.iluwatar.commander.AppMessagingFailCases - - - ${project.artifactId}-MessagingFailCase - - - - Payment - - - - com.iluwatar.commander.AppPaymentFailCases - - - ${project.artifactId}-PaymentFailCase - - - - Queue - - - - com.iluwatar.commander.AppQueueFailCases - - - ${project.artifactId}-QueueFailCase - - - - Shipping - - - - com.iluwatar.commander.AppShippingFailCases - - - ${project.artifactId}-ShippingFailCase - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + allCases + + + + com.iluwatar.commander.AppAllCases + + + ${project.artifactId}-AllCases + + + + + + + diff --git a/commander/src/main/java/com/iluwatar/commander/AppAllCases.java b/commander/src/main/java/com/iluwatar/commander/AppAllCases.java new file mode 100644 index 000000000000..51fa1fed4f34 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/AppAllCases.java @@ -0,0 +1,517 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.commander; + +import com.iluwatar.commander.employeehandle.EmployeeDatabase; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.ItemUnavailableException; +import com.iluwatar.commander.exceptions.PaymentDetailsErrorException; +import com.iluwatar.commander.messagingservice.MessagingDatabase; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentDatabase; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.shippingservice.ShippingDatabase; +import com.iluwatar.commander.shippingservice.ShippingService; + +/** + * The {@code AppAllCases} class tests various scenarios for the microservices involved in the order + * placement process. This class consolidates previously separated cases into a single class to + * manage different success and failure scenarios for each service. + * + *

The application consists of abstract classes {@link Database} and {@link Service} which are + * extended by all the databases and services. Each service has a corresponding database to be + * updated and receives requests from an external user through the {@link Commander} class. There + * are 5 microservices: + * + *

    + *
  • {@link ShippingService} + *
  • {@link PaymentService} + *
  • {@link MessagingService} + *
  • {@link EmployeeHandle} + *
  • {@link QueueDatabase} + *
+ * + *

Retries are managed using the {@link Retry} class, ensuring idempotence by performing checks + * before making requests to services and updating the {@link Order} class fields upon request + * success or definitive failure. + * + *

This class tests the following scenarios: + * + *

    + *
  • Employee database availability and unavailability + *
  • Payment service success and failures + *
  • Messaging service database availability and unavailability + *
  • Queue database availability and unavailability + *
  • Shipping service success and failures + *
+ * + *

Each scenario is encapsulated in a corresponding method that sets up the service conditions + * and tests the order placement process. + * + *

The main method executes all success and failure cases to verify the application's behavior + * under different conditions. + * + *

Usage: + * + *

{@code
+ * public static void main(String[] args) {
+ *     AppAllCases app = new AppAllCases();
+ *     app.testAllScenarios();
+ * }
+ * }
+ */ +public class AppAllCases { + private static final RetryParams retryParams = RetryParams.DEFAULT; + private static final TimeLimits timeLimits = TimeLimits.DEFAULT; + + // Employee Database Fail Case + void employeeDatabaseUnavailableCase() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Employee Database Success Case + void employeeDbSuccessCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Messaging Database Fail Cases + void messagingDatabaseUnavailableCasePaymentSuccess() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void messagingDatabaseUnavailableCasePaymentError() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void messagingDatabaseUnavailableCasePaymentFailure() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Messaging Database Success Case + void messagingSuccessCase() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Payment Database Fail Cases + void paymentNotPossibleCase() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new PaymentDetailsErrorException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void paymentDatabaseUnavailableCase() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Payment Database Success Case + void paymentSuccessCase() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Queue Database Fail Cases + void queuePaymentTaskDatabaseUnavailableCase() { + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void queueMessageTaskDatabaseUnavailableCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void queueEmployeeDbTaskDatabaseUnavailableCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Queue Database Success Cases + void queueSuccessCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Shipping Database Fail Cases + void itemUnavailableCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void shippingDatabaseUnavailableCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = + new ShippingService( + new ShippingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void shippingItemNotPossibleCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + // Shipping Database Success Cases + void shippingSuccessCase() { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + /** + * Program entry point. + * + * @param args command line arguments + */ + public static void main(String[] args) { + AppAllCases app = new AppAllCases(); + + // Employee Database cases + app.employeeDatabaseUnavailableCase(); + app.employeeDbSuccessCase(); + + // Messaging Database cases + app.messagingDatabaseUnavailableCasePaymentSuccess(); + app.messagingDatabaseUnavailableCasePaymentError(); + app.messagingDatabaseUnavailableCasePaymentFailure(); + app.messagingSuccessCase(); + + // Payment Database cases + app.paymentNotPossibleCase(); + app.paymentDatabaseUnavailableCase(); + app.paymentSuccessCase(); + + // Queue Database cases + app.queuePaymentTaskDatabaseUnavailableCase(); + app.queueMessageTaskDatabaseUnavailableCase(); + app.queueEmployeeDbTaskDatabaseUnavailableCase(); + app.queueSuccessCase(); + + // Shipping Database cases + app.itemUnavailableCase(); + app.shippingDatabaseUnavailableCase(); + app.shippingItemNotPossibleCase(); + app.shippingSuccessCase(); + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/AppEmployeeDbFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppEmployeeDbFailCases.java deleted file mode 100644 index 8d633a6d3481..000000000000 --- a/commander/src/main/java/com/iluwatar/commander/AppEmployeeDbFailCases.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.commander; - -import com.iluwatar.commander.employeehandle.EmployeeDatabase; -import com.iluwatar.commander.employeehandle.EmployeeHandle; -import com.iluwatar.commander.exceptions.DatabaseUnavailableException; -import com.iluwatar.commander.exceptions.ItemUnavailableException; -import com.iluwatar.commander.messagingservice.MessagingDatabase; -import com.iluwatar.commander.messagingservice.MessagingService; -import com.iluwatar.commander.paymentservice.PaymentDatabase; -import com.iluwatar.commander.paymentservice.PaymentService; -import com.iluwatar.commander.queue.QueueDatabase; -import com.iluwatar.commander.shippingservice.ShippingDatabase; -import com.iluwatar.commander.shippingservice.ShippingService; - -/** - * AppEmployeeDbFailCases class looks at possible cases when Employee handle service is - * available/unavailable. - */ -public class AppEmployeeDbFailCases { - private final int numOfRetries = 3; - private final long retryDuration = 30000; - private final long queueTime = 240000; //4 mins - private final long queueTaskTime = 60000; //1 min - private final long paymentTime = 120000; //2 mins - private final long messageTime = 150000; //2.5 mins - private final long employeeTime = 240000; //4 mins - - void employeeDatabaseUnavailableCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = - new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void employeeDbSuccessCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - /** - * Program entry point. - * - * @param args command line args - */ - - public static void main(String[] args) throws Exception { - var aefc = new AppEmployeeDbFailCases(); - //aefc.employeeDatabaseUnavailableCase(); - aefc.employeeDbSuccessCase(); - } -} diff --git a/commander/src/main/java/com/iluwatar/commander/AppMessagingFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppMessagingFailCases.java deleted file mode 100644 index 69c4cede9bf4..000000000000 --- a/commander/src/main/java/com/iluwatar/commander/AppMessagingFailCases.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.commander; - -import com.iluwatar.commander.employeehandle.EmployeeDatabase; -import com.iluwatar.commander.employeehandle.EmployeeHandle; -import com.iluwatar.commander.exceptions.DatabaseUnavailableException; -import com.iluwatar.commander.messagingservice.MessagingDatabase; -import com.iluwatar.commander.messagingservice.MessagingService; -import com.iluwatar.commander.paymentservice.PaymentDatabase; -import com.iluwatar.commander.paymentservice.PaymentService; -import com.iluwatar.commander.queue.QueueDatabase; -import com.iluwatar.commander.shippingservice.ShippingDatabase; -import com.iluwatar.commander.shippingservice.ShippingService; - -/** - * AppMessagingFailCases class looks at possible cases when Messaging service is - * available/unavailable. - */ - -public class AppMessagingFailCases { - private final int numOfRetries = 3; - private final long retryDuration = 30000; - private final long queueTime = 240000; //4 mins - private final long queueTaskTime = 60000; //1 min - private final long paymentTime = 120000; //2 mins - private final long messageTime = 150000; //2.5 mins - private final long employeeTime = 240000; //4 mins - - void messagingDatabaseUnavailableCasePaymentSuccess() throws Exception { - //rest is successful - var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void messagingDatabaseUnavailableCasePaymentError() throws Exception { - //rest is successful - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void messagingDatabaseUnavailableCasePaymentFailure() throws Exception { - //rest is successful - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = - new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - var c = - new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, queueTime, queueTaskTime, - paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void messagingSuccessCase() throws Exception { - //done here - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - /** - * Program entry point. - * - * @param args command line args - */ - - public static void main(String[] args) throws Exception { - var amfc = new AppMessagingFailCases(); - //amfc.messagingDatabaseUnavailableCasePaymentSuccess(); - //amfc.messagingDatabaseUnavailableCasePaymentError(); - //amfc.messagingDatabaseUnavailableCasePaymentFailure(); - amfc.messagingSuccessCase(); - } -} diff --git a/commander/src/main/java/com/iluwatar/commander/AppPaymentFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppPaymentFailCases.java deleted file mode 100644 index be133ccdc140..000000000000 --- a/commander/src/main/java/com/iluwatar/commander/AppPaymentFailCases.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.commander; - -import com.iluwatar.commander.employeehandle.EmployeeDatabase; -import com.iluwatar.commander.employeehandle.EmployeeHandle; -import com.iluwatar.commander.exceptions.DatabaseUnavailableException; -import com.iluwatar.commander.exceptions.PaymentDetailsErrorException; -import com.iluwatar.commander.messagingservice.MessagingDatabase; -import com.iluwatar.commander.messagingservice.MessagingService; -import com.iluwatar.commander.paymentservice.PaymentDatabase; -import com.iluwatar.commander.paymentservice.PaymentService; -import com.iluwatar.commander.queue.QueueDatabase; -import com.iluwatar.commander.shippingservice.ShippingDatabase; -import com.iluwatar.commander.shippingservice.ShippingService; - -/** - * AppPaymentFailCases class looks at possible cases when Payment service is available/unavailable. - */ - -public class AppPaymentFailCases { - private final int numOfRetries = 3; - private final long retryDuration = 30000; - private final long queueTime = 240000; //4 mins - private final long queueTaskTime = 60000; //1 min - private final long paymentTime = 120000; //2 mins - private final long messageTime = 150000; //2.5 mins - private final long employeeTime = 240000; //4 mins - - void paymentNotPossibleCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new PaymentDetailsErrorException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(new DatabaseUnavailableException()); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void paymentDatabaseUnavailableCase() throws Exception { - //rest is successful - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void paymentSuccessCase() throws Exception { - //goes to message after 2 retries maybe - rest is successful for now - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = - new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(new DatabaseUnavailableException()); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - /** - * Program entry point. - * - * @param args command line args - */ - - public static void main(String[] args) throws Exception { - var apfc = new AppPaymentFailCases(); - //apfc.paymentNotPossibleCase(); - //apfc.paymentDatabaseUnavailableCase(); - apfc.paymentSuccessCase(); - } -} diff --git a/commander/src/main/java/com/iluwatar/commander/AppQueueFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppQueueFailCases.java deleted file mode 100644 index 08f0fc218387..000000000000 --- a/commander/src/main/java/com/iluwatar/commander/AppQueueFailCases.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.commander; - -import com.iluwatar.commander.employeehandle.EmployeeDatabase; -import com.iluwatar.commander.employeehandle.EmployeeHandle; -import com.iluwatar.commander.exceptions.DatabaseUnavailableException; -import com.iluwatar.commander.exceptions.ItemUnavailableException; -import com.iluwatar.commander.messagingservice.MessagingDatabase; -import com.iluwatar.commander.messagingservice.MessagingService; -import com.iluwatar.commander.paymentservice.PaymentDatabase; -import com.iluwatar.commander.paymentservice.PaymentService; -import com.iluwatar.commander.queue.QueueDatabase; -import com.iluwatar.commander.shippingservice.ShippingDatabase; -import com.iluwatar.commander.shippingservice.ShippingService; - -/** - * AppQueueFailCases class looks at possible cases when Queue Database is available/unavailable. - */ - -public class AppQueueFailCases { - private final int numOfRetries = 3; - private final long retryDuration = 30000; - private final long queueTime = 240000; //4 mins - private final long queueTaskTime = 60000; //1 min - private final long paymentTime = 120000; //2 mins - private final long messageTime = 150000; //2.5 mins - private final long employeeTime = 240000; //4 mins - - void queuePaymentTaskDatabaseUnavailableCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = - new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void queueMessageTaskDatabaseUnavailableCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = - new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void queueEmployeeDbTaskDatabaseUnavailableCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = - new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void queueSuccessCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase()); - var ms = - new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = - new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException()); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - /** - * Program entry point. - * - * @param args command line args - */ - - public static void main(String[] args) throws Exception { - var aqfc = new AppQueueFailCases(); - //aqfc.queuePaymentTaskDatabaseUnavailableCase(); - //aqfc.queueMessageTaskDatabaseUnavailableCase(); - //aqfc.queueEmployeeDbTaskDatabaseUnavailableCase(); - aqfc.queueSuccessCase(); - } -} diff --git a/commander/src/main/java/com/iluwatar/commander/AppShippingFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppShippingFailCases.java deleted file mode 100644 index fdba598e659d..000000000000 --- a/commander/src/main/java/com/iluwatar/commander/AppShippingFailCases.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.commander; - -import com.iluwatar.commander.employeehandle.EmployeeDatabase; -import com.iluwatar.commander.employeehandle.EmployeeHandle; -import com.iluwatar.commander.exceptions.DatabaseUnavailableException; -import com.iluwatar.commander.exceptions.ItemUnavailableException; -import com.iluwatar.commander.exceptions.ShippingNotPossibleException; -import com.iluwatar.commander.messagingservice.MessagingDatabase; -import com.iluwatar.commander.messagingservice.MessagingService; -import com.iluwatar.commander.paymentservice.PaymentDatabase; -import com.iluwatar.commander.paymentservice.PaymentService; -import com.iluwatar.commander.queue.QueueDatabase; -import com.iluwatar.commander.shippingservice.ShippingDatabase; -import com.iluwatar.commander.shippingservice.ShippingService; - -/** - * AppShippingFailCases class looks at possible cases when Shipping service is - * available/unavailable. - */ - -public class AppShippingFailCases { - private final int numOfRetries = 3; - private final long retryDuration = 30000; - private final long queueTime = 240000; //4 mins - private final long queueTaskTime = 60000; //1 min - private final long paymentTime = 120000; //2 mins - private final long messageTime = 150000; //2.5 mins - private final long employeeTime = 240000; //4 mins - - void itemUnavailableCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void shippingNotPossibleCase() throws Exception { - var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase(), new ShippingNotPossibleException()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void shippingDatabaseUnavailableCase() throws Exception { - //rest is successful - var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - void shippingSuccessCase() throws Exception { - //goes to payment after 2 retries maybe - rest is successful for now - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException()); - var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(); - var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); - var user = new User("Jim", "ABCD"); - var order = new Order(user, "book", 10f); - c.placeOrder(order); - } - - /** - * Program entry point. - * - * @param args command line args - */ - - public static void main(String[] args) throws Exception { - var asfc = new AppShippingFailCases(); - //asfc.itemUnavailableCase(); - //asfc.shippingNotPossibleCase(); - //asfc.shippingDatabaseUnavailableCase(); - asfc.shippingSuccessCase(); - } -} diff --git a/commander/src/main/java/com/iluwatar/commander/Commander.java b/commander/src/main/java/com/iluwatar/commander/Commander.java index 6b6c90a03f8d..b25e1c128c6d 100644 --- a/commander/src/main/java/com/iluwatar/commander/Commander.java +++ b/commander/src/main/java/com/iluwatar/commander/Commander.java @@ -28,6 +28,7 @@ import com.iluwatar.commander.Order.PaymentStatus; import com.iluwatar.commander.employeehandle.EmployeeHandle; import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.IsEmptyException; import com.iluwatar.commander.exceptions.ItemUnavailableException; import com.iluwatar.commander.exceptions.PaymentDetailsErrorException; import com.iluwatar.commander.exceptions.ShippingNotPossibleException; @@ -41,37 +42,34 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** - *

Commander pattern is used to handle all issues that can come up while making a - * distributed transaction. The idea is to have a commander, which coordinates the execution of all - * instructions and ensures proper completion using retries and taking care of idempotence. By - * queueing instructions while they haven't been done, we can ensure a state of 'eventual - * consistency'.

- *

In our example, we have an e-commerce application. When the user places an order, - * the shipping service is intimated first. If the service does not respond for some reason, the - * order is not placed. If response is received, the commander then calls for the payment service to - * be intimated. If this fails, the shipping still takes place (order converted to COD) and the item - * is queued. If the queue is also found to be unavailable, the payment is noted to be not done and + * Commander pattern is used to handle all issues that can come up while making a distributed + * transaction. The idea is to have a commander, which coordinates the execution of all instructions + * and ensures proper completion using retries and taking care of idempotence. By queueing + * instructions while they haven't been done, we can ensure a state of 'eventual consistency'. + * + *

In our example, we have an e-commerce application. When the user places an order, the shipping + * service is intimated first. If the service does not respond for some reason, the order is not + * placed. If response is received, the commander then calls for the payment service to be + * intimated. If this fails, the shipping still takes place (order converted to COD) and the item is + * queued. If the queue is also found to be unavailable, the payment is noted to be not done and * this is added to an employee database. Three types of messages are sent to the user - one, if * payment succeeds; two, if payment fails definitively; and three, if payment fails in the first * attempt. If the message is not sent, this is also queued and is added to employee db. We also * have a time limit for each instruction to be completed, after which, the instruction is not * executed, thereby ensuring that resources are not held for too long. In the rare occasion in - * which everything fails, an individual would have to step in to figure out how to solve the - * issue.

- *

We have abstract classes {@link Database} and {@link Service} which are extended - * by all the databases and services. Each service has a database to be updated, and receives - * request from an outside user (the {@link Commander} class here). There are 5 microservices - - * {@link ShippingService}, {@link PaymentService}, {@link MessagingService}, {@link EmployeeHandle} - * and a {@link QueueDatabase}. We use retries to execute any instruction using {@link Retry} class, - * and idempotence is ensured by going through some checks before making requests to services and - * making change in {@link Order} class fields if request succeeds or definitively fails. There are - * 5 classes - {@link AppShippingFailCases}, {@link AppPaymentFailCases}, {@link - * AppMessagingFailCases}, {@link AppQueueFailCases} and {@link AppEmployeeDbFailCases}, which look - * at the different scenarios that may be encountered during the placing of an order.

+ * which everything fails, an individual would have to step in to figure out how to solve the issue. + * + *

We have abstract classes {@link Database} and {@link Service} which are extended by all the + * databases and services. Each service has a database to be updated, and receives request from an + * outside user (the {@link Commander} class here). There are 5 microservices - {@link + * ShippingService}, {@link PaymentService}, {@link MessagingService}, {@link EmployeeHandle} and a + * {@link QueueDatabase}. We use retries to execute any instruction using {@link Retry} class, and + * idempotence is ensured by going through some checks before making requests to services and making + * change in {@link Order} class fields if request succeeds or definitively fails. There is a single + * class {@link AppAllCases} that looks at the different scenarios that may be encountered during + * the placing of an order, including both success and failure cases for each service. */ - public class Commander { private final QueueDatabase queue; @@ -79,7 +77,8 @@ public class Commander { private final PaymentService paymentService; private final ShippingService shippingService; private final MessagingService messagingService; - private int queueItems = 0; //keeping track here only so don't need access to queue db to get this + private int queueItems = + 0; // keeping track here only so don't need access to queue db to get this private final int numOfRetries; private final long retryDuration; private final long queueTime; @@ -88,82 +87,109 @@ public class Commander { private final long messageTime; private final long employeeTime; private boolean finalSiteMsgShown; + private static final Logger LOG = LoggerFactory.getLogger(Commander.class); - //we could also have another db where it stores all orders + // we could also have another db where it stores all orders private static final String ORDER_ID = "Order {}"; private static final String REQUEST_ID = " request Id: {}"; private static final String ERROR_CONNECTING_MSG_SVC = - ": Error in connecting to messaging service "; - private static final String TRY_CONNECTING_MSG_SVC = - ": Trying to connect to messaging service.."; - - Commander(EmployeeHandle empDb, PaymentService paymentService, ShippingService shippingService, - MessagingService messagingService, QueueDatabase qdb, int numOfRetries, - long retryDuration, long queueTime, long queueTaskTime, long paymentTime, - long messageTime, long employeeTime) { + ": Error in connecting to messaging service "; + private static final String TRY_CONNECTING_MSG_SVC = ": Trying to connect to messaging service.."; + + private static final String DEFAULT_EXCEPTION_MESSAGE = "An exception occurred"; + + Commander( + EmployeeHandle empDb, + PaymentService paymentService, + ShippingService shippingService, + MessagingService messagingService, + QueueDatabase qdb, + RetryParams retryParams, + TimeLimits timeLimits) { this.paymentService = paymentService; this.shippingService = shippingService; this.messagingService = messagingService; this.employeeDb = empDb; this.queue = qdb; - this.numOfRetries = numOfRetries; - this.retryDuration = retryDuration; - this.queueTime = queueTime; - this.queueTaskTime = queueTaskTime; - this.paymentTime = paymentTime; - this.messageTime = messageTime; - this.employeeTime = employeeTime; + this.numOfRetries = retryParams.numOfRetries(); + this.retryDuration = retryParams.retryDuration(); + this.queueTime = timeLimits.queueTime(); + this.queueTaskTime = timeLimits.queueTaskTime(); + this.paymentTime = timeLimits.paymentTime(); + this.messageTime = timeLimits.messageTime(); + this.employeeTime = timeLimits.employeeTime(); this.finalSiteMsgShown = false; } - void placeOrder(Order order) throws Exception { + void placeOrder(Order order) { sendShippingRequest(order); } - private void sendShippingRequest(Order order) throws Exception { + private void sendShippingRequest(Order order) { var list = shippingService.exceptionsList; - Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ": Error in connecting to shipping service, " - + "trying again..", order.id); - } else { - LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id); - } - throw l.remove(0); - } - String transactionId = shippingService.receiveRequest(order.item, order.user.address); - //could save this transaction id in a db too - LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}", - order.id, transactionId); - LOG.info("Order has been placed and will be shipped to you. Please wait while we make your" - + " payment... "); - sendPaymentRequest(order); - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - if (ShippingNotPossibleException.class.isAssignableFrom(err.getClass())) { - LOG.info("Shipping is currently not possible to your address. We are working on the problem" - + " and will get back to you asap."); - finalSiteMsgShown = true; - LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem " - + "to employee db..", order.id); - employeeHandleIssue(o); - } else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) { - LOG.info("This item is currently unavailable. We will inform you as soon as the item " - + "becomes available again."); - finalSiteMsgShown = true; - LOG.info(ORDER_ID + ": Item {}" + " unavailable, trying to add " - + "problem to employee handle..", order.id, order.item); - employeeHandleIssue(o); - } else { - LOG.info("Sorry, there was a problem in creating your order. Please try later."); - LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id); - finalSiteMsgShown = true; - } - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + Retry.Operation op = + l -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug( + ORDER_ID + ": Error in connecting to shipping service, " + "trying again..", + order.id); + } else { + LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id); + } + throw l.remove(0); + } + String transactionId = shippingService.receiveRequest(order.item, order.user.address); + // could save this transaction id in a db too + LOG.info( + ORDER_ID + ": Shipping placed successfully, transaction id: {}", + order.id, + transactionId); + LOG.info( + "Order has been placed and will be shipped to you. Please wait while we make your" + + " payment... "); + sendPaymentRequest(order); + }; + Retry.HandleErrorIssue handleError = + (o, err) -> { + if (ShippingNotPossibleException.class.isAssignableFrom(err.getClass())) { + LOG.info( + "Shipping is currently not possible to your address. We are working on the problem" + + " and will get back to you asap."); + finalSiteMsgShown = true; + LOG.info( + ORDER_ID + + ": Shipping not possible to address, trying to add problem " + + "to employee db..", + order.id); + employeeHandleIssue(o); + } else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) { + LOG.info( + "This item is currently unavailable. We will inform you as soon as the item " + + "becomes available again."); + finalSiteMsgShown = true; + LOG.info( + ORDER_ID + + ": Item {}" + + " unavailable, trying to add " + + "problem to employee handle..", + order.id, + order.item); + employeeHandleIssue(o); + } else { + LOG.info("Sorry, there was a problem in creating your order. Please try later."); + LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id); + finalSiteMsgShown = true; + } + }; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); r.perform(list, order); } @@ -173,70 +199,97 @@ private void sendPaymentRequest(Order order) { order.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(order); LOG.error(ORDER_ID + ": Payment time for order over, failed and returning..", order.id); - } //if succeeded or failed, would have been dequeued, no attempt to make payment + } // if succeeded or failed, would have been dequeued, no attempt to make payment return; } var list = paymentService.exceptionsList; - var t = new Thread(() -> { - Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ": Error in connecting to payment service," - + " trying again..", order.id); - } else { - LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id); - } - throw l.remove(0); + var t = + new Thread( + () -> { + Retry.Operation op = getRetryOperation(order); + + Retry.HandleErrorIssue handleError = getRetryHandleErrorIssue(order); + + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); + + t.start(); + } + + private Retry.HandleErrorIssue getRetryHandleErrorIssue(Order order) { + return (o, err) -> { + if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) { + handlePaymentDetailsError(order.id, o); + } else { + if (o.messageSent.equals(MessageSent.NONE_SENT)) { + handlePaymentError(order.id, o); } - if (order.paid.equals(PaymentStatus.TRYING)) { - var transactionId = paymentService.receiveRequest(order.price); - order.paid = PaymentStatus.DONE; - LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}", - order.id, transactionId); - if (!finalSiteMsgShown) { - LOG.info("Payment made successfully, thank you for shopping with us!!"); - finalSiteMsgShown = true; - } - sendSuccessMessage(order); + if (o.paid.equals(PaymentStatus.TRYING) + && System.currentTimeMillis() - o.createdTime < paymentTime) { + var qt = new QueueTask(o, TaskType.PAYMENT, -1); + updateQueue(qt); } - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) { - if (!finalSiteMsgShown) { - LOG.info("There was an error in payment. Your account/card details " - + "may have been incorrect. " - + "Meanwhile, your order has been converted to COD and will be shipped."); - finalSiteMsgShown = true; - } - LOG.error(ORDER_ID + ": Payment details incorrect, failed..", order.id); - o.paid = PaymentStatus.NOT_DONE; - sendPaymentFailureMessage(o); + } + }; + } + + private void handlePaymentError(String orderId, Order o) { + if (!finalSiteMsgShown) { + LOG.info( + "There was an error in payment. We are on it, and will get back to you " + + "asap. Don't worry, your order has been placed and will be shipped."); + finalSiteMsgShown = true; + } + LOG.warn(ORDER_ID + ": Payment error, going to queue..", orderId); + sendPaymentPossibleErrorMsg(o); + } + + private void handlePaymentDetailsError(String orderId, Order o) { + if (!finalSiteMsgShown) { + LOG.info( + "There was an error in payment. Your account/card details " + + "may have been incorrect. " + + "Meanwhile, your order has been converted to COD and will be shipped."); + finalSiteMsgShown = true; + } + LOG.error(ORDER_ID + ": Payment details incorrect, failed..", orderId); + o.paid = PaymentStatus.NOT_DONE; + sendPaymentFailureMessage(o); + } + + private Retry.Operation getRetryOperation(Order order) { + return l -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug( + ORDER_ID + ": Error in connecting to payment service," + " trying again..", order.id); } else { - if (o.messageSent.equals(MessageSent.NONE_SENT)) { - if (!finalSiteMsgShown) { - LOG.info("There was an error in payment. We are on it, and will get back to you " - + "asap. Don't worry, your order has been placed and will be shipped."); - finalSiteMsgShown = true; - } - LOG.warn(ORDER_ID + ": Payment error, going to queue..", order.id); - sendPaymentPossibleErrorMsg(o); - } - if (o.paid.equals(PaymentStatus.TRYING) && System - .currentTimeMillis() - o.createdTime < paymentTime) { - var qt = new QueueTask(o, TaskType.PAYMENT, -1); - updateQueue(qt); - } + LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id); } - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - e1.printStackTrace(); + throw l.remove(0); } - }); - t.start(); + if (order.paid.equals(PaymentStatus.TRYING)) { + var transactionId = paymentService.receiveRequest(order.price); + order.paid = PaymentStatus.DONE; + LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}", order.id, transactionId); + if (!finalSiteMsgShown) { + LOG.info("Payment made successfully, thank you for shopping with us!!"); + finalSiteMsgShown = true; + } + sendSuccessMessage(order); + } + }; } private void updateQueue(QueueTask qt) { @@ -246,92 +299,122 @@ private void updateQueue(QueueTask qt) { LOG.trace(ORDER_ID + ": Queue time for order over, failed..", qt.order.id); return; } else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING) - || qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1 - && !qt.order.messageSent.equals(MessageSent.NONE_SENT) - || qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) - || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) + || qt.taskType.equals(TaskType.MESSAGING) + && (qt.messageType == 1 && !qt.order.messageSent.equals(MessageSent.NONE_SENT) + || qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) + || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) || qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) { LOG.trace(ORDER_ID + ": Not queueing task since task already done..", qt.order.id); return; } var list = queue.exceptionsList; - Thread t = new Thread(() -> { - Retry.Operation op = (list1) -> { - if (!list1.isEmpty()) { - LOG.warn(ORDER_ID + ": Error in connecting to queue db, trying again..", qt.order.id); - throw list1.remove(0); - } - queue.add(qt); - queueItems++; - LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType()); - tryDoingTasksInQueue(); - }; - Retry.HandleErrorIssue handleError = (qt1, err) -> { - if (qt1.taskType.equals(TaskType.PAYMENT)) { - qt1.order.paid = PaymentStatus.NOT_DONE; - sendPaymentFailureMessage(qt1.order); - LOG.error(ORDER_ID + ": Unable to enqueue payment task," - + " payment failed..", qt1.order.id); - } - LOG.error(ORDER_ID + ": Unable to enqueue task of type {}" - + ", trying to add to employee handle..", qt1.order.id, qt1.getType()); - employeeHandleIssue(qt1.order); - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, qt); - } catch (Exception e1) { - e1.printStackTrace(); - } - }); + Thread t = + new Thread( + () -> { + Retry.Operation op = + list1 -> { + if (!list1.isEmpty()) { + LOG.warn( + ORDER_ID + ": Error in connecting to queue db, trying again..", + qt.order.id); + throw list1.remove(0); + } + queue.add(qt); + queueItems++; + LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType()); + tryDoingTasksInQueue(); + }; + Retry.HandleErrorIssue handleError = + (qt1, err) -> { + if (qt1.taskType.equals(TaskType.PAYMENT)) { + qt1.order.paid = PaymentStatus.NOT_DONE; + sendPaymentFailureMessage(qt1.order); + LOG.error( + ORDER_ID + ": Unable to enqueue payment task," + " payment failed..", + qt1.order.id); + } + LOG.error( + ORDER_ID + + ": Unable to enqueue task of type {}" + + ", trying to add to employee handle..", + qt1.order.id, + qt1.getType()); + employeeHandleIssue(qt1.order); + }; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, qt); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } - private void tryDoingTasksInQueue() { //commander controls operations done to queue + private void tryDoingTasksInQueue() { // commander controls operations done to queue var list = queue.exceptionsList; - var t2 = new Thread(() -> { - Retry.Operation op = (list1) -> { - if (!list1.isEmpty()) { - LOG.warn("Error in accessing queue db to do tasks, trying again.."); - throw list1.remove(0); - } - doTasksInQueue(); - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, null); - } catch (Exception e1) { - e1.printStackTrace(); - } - }); + var t2 = + new Thread( + () -> { + Retry.Operation op = + list1 -> { + if (!list1.isEmpty()) { + LOG.warn("Error in accessing queue db to do tasks, trying again.."); + throw list1.remove(0); + } + doTasksInQueue(); + }; + Retry.HandleErrorIssue handleError = (o, err) -> {}; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, null); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t2.start(); } private void tryDequeue() { var list = queue.exceptionsList; - var t3 = new Thread(() -> { - Retry.Operation op = (list1) -> { - if (!list1.isEmpty()) { - LOG.warn("Error in accessing queue db to dequeue task, trying again.."); - throw list1.remove(0); - } - queue.dequeue(); - queueItems--; - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - }; - var r = new Retry(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, null); - } catch (Exception e1) { - e1.printStackTrace(); - } - }); + var t3 = + new Thread( + () -> { + Retry.Operation op = + list1 -> { + if (!list1.isEmpty()) { + LOG.warn("Error in accessing queue db to dequeue task, trying again.."); + throw list1.remove(0); + } + queue.dequeue(); + queueItems--; + }; + Retry.HandleErrorIssue handleError = (o, err) -> {}; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, null); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t3.start(); } @@ -341,43 +424,53 @@ private void sendSuccessMessage(Order order) { return; } var list = messagingService.exceptionsList; - Thread t = new Thread(() -> { - Retry.Operation op = handleSuccessMessageRetryOperation(order); - Retry.HandleErrorIssue handleError = (o, err) -> { - handleSuccessMessageErrorIssue(order, o); - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - e1.printStackTrace(); - } - }); + Thread t = + new Thread( + () -> { + Retry.Operation op = handleSuccessMessageRetryOperation(order); + Retry.HandleErrorIssue handleError = + (o, err) -> handleSuccessMessageErrorIssue(order, o); + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } private void handleSuccessMessageErrorIssue(Order order, Order o) { - if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent - .equals(MessageSent.PAYMENT_TRYING)) + if ((o.messageSent.equals(MessageSent.NONE_SENT) + || o.messageSent.equals(MessageSent.PAYMENT_TRYING)) && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 2); updateQueue(qt); - LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to" - + " queue task and add to employee handle..", order.id); + LOG.info( + ORDER_ID + + ": Error in sending Payment Success message, trying to" + + " queue task and add to employee handle..", + order.id); employeeHandleIssue(order); } } private Retry.Operation handleSuccessMessageRetryOperation(Order order) { - return (l) -> { + return l -> { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC - + "(Payment Success msg), trying again..", order.id); + LOG.debug( + ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Success msg), trying again..", + order.id); } else { - LOG.debug(ORDER_ID + ": Error in creating Payment Success" - + " messaging request..", order.id); + LOG.debug( + ORDER_ID + ": Error in creating Payment Success" + " messaging request..", order.id); } throw l.remove(0); } @@ -385,8 +478,7 @@ private Retry.Operation handleSuccessMessageRetryOperation(Order order) { && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { var requestId = messagingService.receiveRequest(2); order.messageSent = MessageSent.PAYMENT_SUCCESSFUL; - LOG.info(ORDER_ID + ": Payment Success message sent," - + REQUEST_ID, order.id, requestId); + LOG.info(ORDER_ID + ": Payment Success message sent," + REQUEST_ID, order.id, requestId); } }; } @@ -397,53 +489,64 @@ private void sendPaymentFailureMessage(Order order) { return; } var list = messagingService.exceptionsList; - var t = new Thread(() -> { - Retry.Operation op = (l) -> { - handlePaymentFailureRetryOperation(order, l); - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - handlePaymentErrorIssue(order, o); - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - e1.printStackTrace(); - } - }); + var t = + new Thread( + () -> { + Retry.Operation op = l -> handlePaymentFailureRetryOperation(order, l); + Retry.HandleErrorIssue handleError = + (o, err) -> handlePaymentErrorIssue(order, o); + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } private void handlePaymentErrorIssue(Order order, Order o) { - if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent - .equals(MessageSent.PAYMENT_TRYING)) + if ((o.messageSent.equals(MessageSent.NONE_SENT) + || o.messageSent.equals(MessageSent.PAYMENT_TRYING)) && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 0); updateQueue(qt); - LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, " - + "trying to queue task and add to employee handle..", order.id); + LOG.warn( + ORDER_ID + + ": Error in sending Payment Failure message, " + + "trying to queue task and add to employee handle..", + order.id); employeeHandleIssue(o); } } - private void handlePaymentFailureRetryOperation(Order order, List l) throws Exception { + private void handlePaymentFailureRetryOperation(Order order, List l) + throws IndexOutOfBoundsException, DatabaseUnavailableException { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC - + "(Payment Failure msg), trying again..", order.id); + LOG.debug( + ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Failure msg), trying again..", + order.id); } else { - LOG.debug(ORDER_ID + ": Error in creating Payment Failure" - + " message request..", order.id); + LOG.debug( + ORDER_ID + ": Error in creating Payment Failure" + " message request..", order.id); } - throw l.remove(0); + throw new IndexOutOfBoundsException(); } if (!order.messageSent.equals(MessageSent.PAYMENT_FAIL) && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { var requestId = messagingService.receiveRequest(0); order.messageSent = MessageSent.PAYMENT_FAIL; - LOG.info(ORDER_ID + ": Payment Failure message sent successfully," - + REQUEST_ID, order.id, requestId); + LOG.info( + ORDER_ID + ": Payment Failure message sent successfully," + REQUEST_ID, + order.id, + requestId); } } @@ -453,54 +556,61 @@ private void sendPaymentPossibleErrorMsg(Order order) { return; } var list = messagingService.exceptionsList; - var t = new Thread(() -> { - Retry.Operation op = (l) -> { - handlePaymentPossibleErrorMsgRetryOperation(order, l); - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - handlePaymentPossibleErrorMsgErrorIssue(order, o); - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - e1.printStackTrace(); - } - }); + var t = + new Thread( + () -> { + Retry.Operation op = l -> handlePaymentPossibleErrorMsgRetryOperation(order, l); + Retry.HandleErrorIssue handleError = + (o, err) -> handlePaymentPossibleErrorMsgErrorIssue(order, o); + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } private void handlePaymentPossibleErrorMsgErrorIssue(Order order, Order o) { - if (o.messageSent.equals(MessageSent.NONE_SENT) && order.paid - .equals(PaymentStatus.TRYING) + if (o.messageSent.equals(MessageSent.NONE_SENT) + && order.paid.equals(PaymentStatus.TRYING) && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 1); updateQueue(qt); - LOG.warn("Order {}: Error in sending Payment Error message, trying to queue task and add to employee handle..", - order.id); + LOG.warn( + "Order {}: Error in sending Payment Error message, trying to queue task and add to employee handle..", + order.id); employeeHandleIssue(o); } } private void handlePaymentPossibleErrorMsgRetryOperation(Order order, List l) - throws Exception { + throws IndexOutOfBoundsException, DatabaseUnavailableException { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC - + "(Payment Error msg), trying again..", order.id); + LOG.debug( + ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Error msg), trying again..", order.id); } else { - LOG.debug(ORDER_ID + ": Error in creating Payment Error" - + " messaging request..", order.id); + LOG.debug( + ORDER_ID + ": Error in creating Payment Error" + " messaging request..", order.id); } - throw l.remove(0); + throw new IndexOutOfBoundsException(); } - if (order.paid.equals(PaymentStatus.TRYING) && order.messageSent - .equals(MessageSent.NONE_SENT)) { + if (order.paid.equals(PaymentStatus.TRYING) + && order.messageSent.equals(MessageSent.NONE_SENT)) { var requestId = messagingService.receiveRequest(1); order.messageSent = MessageSent.PAYMENT_TRYING; - LOG.info(ORDER_ID + ": Payment Error message sent successfully," - + REQUEST_ID, order.id, requestId); + LOG.info( + ORDER_ID + ": Payment Error message sent successfully," + REQUEST_ID, + order.id, + requestId); } } @@ -510,89 +620,76 @@ private void employeeHandleIssue(Order order) { return; } var list = employeeDb.exceptionsList; - var t = new Thread(() -> { - Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - LOG.warn(ORDER_ID + ": Error in connecting to employee handle," - + " trying again..", order.id); - throw l.remove(0); - } - if (!order.addedToEmployeeHandle) { - employeeDb.receiveRequest(order); - order.addedToEmployeeHandle = true; - LOG.info(ORDER_ID + ": Added order to employee database", order.id); - } - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - if (!o.addedToEmployeeHandle && System - .currentTimeMillis() - order.createdTime < employeeTime) { - var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1); - updateQueue(qt); - LOG.warn(ORDER_ID + ": Error in adding to employee db," - + " trying to queue task..", order.id); - } - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - e1.printStackTrace(); - } - }); + var t = + new Thread( + () -> { + Retry.Operation op = + l -> { + if (!l.isEmpty()) { + LOG.warn( + ORDER_ID + + ": Error in connecting to employee handle," + + " trying again..", + order.id); + throw l.remove(0); + } + if (!order.addedToEmployeeHandle) { + employeeDb.receiveRequest(order); + order.addedToEmployeeHandle = true; + LOG.info(ORDER_ID + ": Added order to employee database", order.id); + } + }; + Retry.HandleErrorIssue handleError = + (o, err) -> { + if (!o.addedToEmployeeHandle + && System.currentTimeMillis() - order.createdTime < employeeTime) { + var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1); + updateQueue(qt); + LOG.warn( + ORDER_ID + + ": Error in adding to employee db," + + " trying to queue task..", + order.id); + } + }; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } - private void doTasksInQueue() throws Exception { + private void doTasksInQueue() throws IsEmptyException, InterruptedException { if (queueItems != 0) { - var qt = queue.peek(); //this should probably be cloned here - //this is why we have retry for doTasksInQueue + var qt = queue.peek(); // this should probably be cloned here + // this is why we have retry for doTasksInQueue LOG.trace(ORDER_ID + ": Started doing task of type {}", qt.order.id, qt.getType()); - if (qt.getFirstAttemptTime() == -1) { + if (qt.isFirstAttempt()) { qt.setFirstAttemptTime(System.currentTimeMillis()); } if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) { tryDequeue(); - LOG.trace(ORDER_ID + ": This queue task of type {}" - + " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType()); + LOG.trace( + ORDER_ID + + ": This queue task of type {}" + + " does not need to be done anymore (timeout), dequeue..", + qt.order.id, + qt.getType()); } else { - if (qt.taskType.equals(TaskType.PAYMENT)) { - if (!qt.order.paid.equals(PaymentStatus.TRYING)) { - tryDequeue(); - LOG.trace(ORDER_ID + ": This payment task already done, dequeueing..", qt.order.id); - } else { - sendPaymentRequest(qt.order); - LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id); - } - } else if (qt.taskType.equals(TaskType.MESSAGING)) { - if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) - || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { - tryDequeue(); - LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id); - } else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT) - || !qt.order.paid.equals(PaymentStatus.TRYING))) { - tryDequeue(); - LOG.trace(ORDER_ID + ": This messaging task does not need to be done," - + " dequeue..", qt.order.id); - } else if (qt.messageType == 0) { - sendPaymentFailureMessage(qt.order); - LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); - } else if (qt.messageType == 1) { - sendPaymentPossibleErrorMsg(qt.order); - LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); - } else if (qt.messageType == 2) { - sendSuccessMessage(qt.order); - LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); - } - } else if (qt.taskType.equals(TaskType.EMPLOYEE_DB)) { - if (qt.order.addedToEmployeeHandle) { - tryDequeue(); - LOG.trace(ORDER_ID + ": This employee handle task already done," - + " dequeue..", qt.order.id); - } else { - employeeHandleIssue(qt.order); - LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id); - } + switch (qt.taskType) { + case PAYMENT -> doPaymentTask(qt); + case MESSAGING -> doMessagingTask(qt); + case EMPLOYEE_DB -> doEmployeeDbTask(qt); + default -> throw new IllegalArgumentException("Unknown task type"); } } } @@ -604,4 +701,46 @@ private void doTasksInQueue() throws Exception { } } + private void doEmployeeDbTask(QueueTask qt) { + if (qt.order.addedToEmployeeHandle) { + tryDequeue(); + LOG.trace(ORDER_ID + ": This employee handle task already done," + " dequeue..", qt.order.id); + } else { + employeeHandleIssue(qt.order); + LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id); + } + } + + private void doMessagingTask(QueueTask qt) { + if (qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) + || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { + tryDequeue(); + LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id); + } else if (qt.messageType == 1 + && (!qt.order.messageSent.equals(MessageSent.NONE_SENT) + || !qt.order.paid.equals(PaymentStatus.TRYING))) { + tryDequeue(); + LOG.trace( + ORDER_ID + ": This messaging task does not need to be done," + " dequeue..", qt.order.id); + } else if (qt.messageType == 0) { + sendPaymentFailureMessage(qt.order); + LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); + } else if (qt.messageType == 1) { + sendPaymentPossibleErrorMsg(qt.order); + LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); + } else if (qt.messageType == 2) { + sendSuccessMessage(qt.order); + LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); + } + } + + private void doPaymentTask(QueueTask qt) { + if (!qt.order.paid.equals(PaymentStatus.TRYING)) { + tryDequeue(); + LOG.trace(ORDER_ID + ": This payment task already done, dequeueing..", qt.order.id); + } else { + sendPaymentRequest(qt.order); + LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id); + } + } } diff --git a/commander/src/main/java/com/iluwatar/commander/Database.java b/commander/src/main/java/com/iluwatar/commander/Database.java index e118ed3ad894..a3e3da1693a8 100644 --- a/commander/src/main/java/com/iluwatar/commander/Database.java +++ b/commander/src/main/java/com/iluwatar/commander/Database.java @@ -32,7 +32,6 @@ * * @param T is the type of object being held by database. */ - public abstract class Database { public abstract T add(T obj) throws DatabaseUnavailableException; diff --git a/commander/src/main/java/com/iluwatar/commander/Order.java b/commander/src/main/java/com/iluwatar/commander/Order.java index 262aba6fe07a..b998a02974f1 100644 --- a/commander/src/main/java/com/iluwatar/commander/Order.java +++ b/commander/src/main/java/com/iluwatar/commander/Order.java @@ -28,11 +28,8 @@ import java.util.HashMap; import java.util.Map; -/** - * Order class holds details of the order. - */ - -public class Order { //can store all transactions ids also +/** Order class holds details of the order. */ +public class Order { // can store all transactions ids also enum PaymentStatus { NOT_DONE, @@ -56,8 +53,8 @@ enum MessageSent { private static final String ALL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; private static final Map USED_IDS = new HashMap<>(); PaymentStatus paid; - MessageSent messageSent; //to avoid sending error msg on page and text more than once - boolean addedToEmployeeHandle; //to avoid creating more to enqueue + MessageSent messageSent; // to avoid sending error msg on page and text more than once + boolean addedToEmployeeHandle; // to avoid creating more to enqueue Order(User user, String item, float price) { this.createdTime = System.currentTimeMillis(); @@ -85,5 +82,4 @@ private String createUniqueId() { } return random.toString(); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/Retry.java b/commander/src/main/java/com/iluwatar/commander/Retry.java index 45984dfaa910..661f479174a3 100644 --- a/commander/src/main/java/com/iluwatar/commander/Retry.java +++ b/commander/src/main/java/com/iluwatar/commander/Retry.java @@ -36,13 +36,9 @@ * * @param is the type of object passed into HandleErrorIssue as a parameter. */ - public class Retry { - /** - * Operation Interface will define method to be implemented. - */ - + /** Operation Interface will define method to be implemented. */ public interface Operation { void operation(List list) throws Exception; } @@ -52,7 +48,6 @@ public interface Operation { * * @param is the type of object to be passed into the method as parameter. */ - public interface HandleErrorIssue { void handleIssue(T obj, Exception e); } @@ -67,8 +62,12 @@ public interface HandleErrorIssue { private final Predicate test; private final List errors; - Retry(Operation op, HandleErrorIssue handleError, int maxAttempts, - long maxDelay, Predicate... ignoreTests) { + Retry( + Operation op, + HandleErrorIssue handleError, + int maxAttempts, + long maxDelay, + Predicate... ignoreTests) { this.op = op; this.handleError = handleError; this.maxAttempts = maxAttempts; @@ -82,9 +81,8 @@ public interface HandleErrorIssue { * Performing the operation with retries. * * @param list is the exception list - * @param obj is the parameter to be passed into handleIsuue method + * @param obj is the parameter to be passed into handleIsuue method */ - public void perform(List list, T obj) { do { try { @@ -94,7 +92,7 @@ public void perform(List list, T obj) { this.errors.add(e); if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { this.handleError.handleIssue(obj, e); - return; //return here...dont go further + return; // return here... don't go further } try { long testDelay = @@ -102,10 +100,9 @@ public void perform(List list, T obj) { long delay = Math.min(testDelay, this.maxDelay); Thread.sleep(delay); } catch (InterruptedException f) { - //ignore + // ignore } } } while (true); } - } diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java b/commander/src/main/java/com/iluwatar/commander/RetryParams.java similarity index 81% rename from thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java rename to commander/src/main/java/com/iluwatar/commander/RetryParams.java index e8b5e1003fb1..9988af8a42e8 100644 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java +++ b/commander/src/main/java/com/iluwatar/commander/RetryParams.java @@ -22,20 +22,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; +package com.iluwatar.commander; /** - * Date: 12/30/15 - 18:23 PM + * Record to hold the parameters related to retries. * - * @author Jeroen Meulemeester + * @param numOfRetries number of retries + * @param retryDuration retry duration */ -class PotatoPeelingTaskTest extends TaskTest { - - /** - * Create a new test instance - */ - public PotatoPeelingTaskTest() { - super(PotatoPeelingTask::new, 200); - } - -} \ No newline at end of file +public record RetryParams(int numOfRetries, long retryDuration) { + public static final RetryParams DEFAULT = new RetryParams(3, 30000L); +} diff --git a/commander/src/main/java/com/iluwatar/commander/Service.java b/commander/src/main/java/com/iluwatar/commander/Service.java index e3177fc307e0..06aa9a3ce103 100644 --- a/commander/src/main/java/com/iluwatar/commander/Service.java +++ b/commander/src/main/java/com/iluwatar/commander/Service.java @@ -38,7 +38,6 @@ * for the transactions/requests, which are then sent back. These could be stored by the {@link * Commander} class in a separate database for reference (though we are not doing that here). */ - public abstract class Service { protected final Database database; diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java b/commander/src/main/java/com/iluwatar/commander/TimeLimits.java similarity index 68% rename from thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java rename to commander/src/main/java/com/iluwatar/commander/TimeLimits.java index 4b94f07e7840..9051fdf29d1a 100644 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java +++ b/commander/src/main/java/com/iluwatar/commander/TimeLimits.java @@ -22,33 +22,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import org.junit.jupiter.api.Test; +package com.iluwatar.commander; /** - * Date: 12/30/15 - 18:21 PM + * Record to hold parameters related to time limit for various tasks. * - * @author Jeroen Meulemeester + * @param queueTime time limit for queue + * @param queueTaskTime time limit for queuing task + * @param paymentTime time limit for payment error message + * @param messageTime time limit for message time order + * @param employeeTime time limit for employee handle time */ -class WorkerTest { - - /** - * Verify if a worker does the actual job - */ - @Test - void testRun() { - final var task = mock(Task.class); - final var worker = new Worker(task); - verifyNoMoreInteractions(task); - - worker.run(); - verify(task).getTimeMs(); - verifyNoMoreInteractions(task); - } +public record TimeLimits( + long queueTime, long queueTaskTime, long paymentTime, long messageTime, long employeeTime) { -} \ No newline at end of file + public static final TimeLimits DEFAULT = + new TimeLimits(240000L, 60000L, 120000L, 150000L, 240000L); +} diff --git a/commander/src/main/java/com/iluwatar/commander/User.java b/commander/src/main/java/com/iluwatar/commander/User.java index b6989e1d9178..e32d0024af02 100644 --- a/commander/src/main/java/com/iluwatar/commander/User.java +++ b/commander/src/main/java/com/iluwatar/commander/User.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * User class contains details of user who places order. - */ +/** User class contains details of user who places order. */ @AllArgsConstructor public class User { String name; diff --git a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java index c584d2ebd212..a139ab323556 100644 --- a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java @@ -30,10 +30,7 @@ import java.util.HashMap; import java.util.Map; -/** - * The Employee Database is where orders which have encountered some issue(s) are added. - */ - +/** The Employee Database is where orders which have encountered some issue(s) are added. */ public class EmployeeDatabase extends Database { private final Map data = new HashMap<>(); diff --git a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java index ac161fbb5404..745b71d90114 100644 --- a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java +++ b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java @@ -32,7 +32,6 @@ * The EmployeeHandle class is the middle-man between {@link com.iluwatar.commander.Commander} and * {@link EmployeeDatabase}. */ - public class EmployeeHandle extends Service { public EmployeeHandle(EmployeeDatabase db, Exception... exc) { @@ -47,9 +46,8 @@ protected String updateDb(Object... parameters) throws DatabaseUnavailableExcept var o = (Order) parameters[0]; if (database.get(o.id) == null) { database.add(o); - return o.id; //true rcvd - change addedToEmployeeHandle to true else dont do anything + return o.id; // true rcvd - change addedToEmployeeHandle to true else don't do anything } return null; } - } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java index 5d368180ce39..51f27832f481 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java @@ -28,7 +28,6 @@ * DatabaseUnavailableException is thrown when database is unavailable and nothing can be added or * retrieved. */ - public class DatabaseUnavailableException extends Exception { private static final long serialVersionUID = 2459603L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java index 3ab6b22b3387..5cb06214f527 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java @@ -24,10 +24,7 @@ */ package com.iluwatar.commander.exceptions; -/** - * IsEmptyException is thrown when it is attempted to dequeue from an empty queue. - */ - +/** IsEmptyException is thrown when it is attempted to dequeue from an empty queue. */ public class IsEmptyException extends Exception { private static final long serialVersionUID = 123546L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java index 4ff39543ef72..9d6f2849ff77 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java @@ -24,10 +24,7 @@ */ package com.iluwatar.commander.exceptions; -/** - * ItemUnavailableException is thrown when item is not available for shipping. - */ - +/** ItemUnavailableException is thrown when item is not available for shipping. */ public class ItemUnavailableException extends Exception { private static final long serialVersionUID = 575940L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java index 844dab389941..1406b43de2b1 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java @@ -28,7 +28,6 @@ * PaymentDetailsErrorException is thrown when the details entered are incorrect or payment cannot * be made with the details given. */ - public class PaymentDetailsErrorException extends Exception { private static final long serialVersionUID = 867203L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java index ac6267dfc42d..51036aaea3aa 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java @@ -28,7 +28,6 @@ * ShippingNotPossibleException is thrown when the address entered cannot be shipped to by service * currently for some reason. */ - public class ShippingNotPossibleException extends Exception { private static final long serialVersionUID = 342055L; } diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java index 7339a623c1ce..b6af7d7b52d2 100644 --- a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java @@ -29,21 +29,17 @@ import java.util.Hashtable; import java.util.Map; -/** - * The MessagingDatabase is where the MessageRequest is added. - */ - +/** The MessagingDatabase is where the MessageRequest is added. */ public class MessagingDatabase extends Database { private final Map data = new Hashtable<>(); @Override public MessageRequest add(MessageRequest r) { - return data.put(r.reqId, r); + return data.put(r.reqId(), r); } @Override public MessageRequest get(String requestId) { return data.get(requestId); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java index 47d14b970b68..44b58e5e07ba 100644 --- a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java @@ -26,7 +26,6 @@ import com.iluwatar.commander.Service; import com.iluwatar.commander.exceptions.DatabaseUnavailableException; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; /** @@ -34,7 +33,6 @@ * In case an error is encountered in payment and this service is found to be unavailable, the order * is added to the {@link com.iluwatar.commander.employeehandle.EmployeeDatabase}. */ - @Slf4j public class MessagingService extends Service { @@ -44,19 +42,13 @@ enum MessageToSend { PAYMENT_SUCCESSFUL } - @RequiredArgsConstructor - static class MessageRequest { - final String reqId; - final MessageToSend msg; - } + record MessageRequest(String reqId, MessageToSend msg) {} public MessagingService(MessagingDatabase db, Exception... exc) { super(db, exc); } - /** - * Public method which will receive request from {@link com.iluwatar.commander.Commander}. - */ + /** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */ public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { var messageToSend = (int) parameters[0]; var id = generateId(); @@ -65,7 +57,7 @@ public String receiveRequest(Object... parameters) throws DatabaseUnavailableExc msg = MessageToSend.PAYMENT_FAIL; } else if (messageToSend == 1) { msg = MessageToSend.PAYMENT_TRYING; - } else { //messageToSend == 2 + } else { // messageToSend == 2 msg = MessageToSend.PAYMENT_SUCCESSFUL; } var req = new MessageRequest(id, msg); @@ -74,8 +66,8 @@ public String receiveRequest(Object... parameters) throws DatabaseUnavailableExc protected String updateDb(Object... parameters) throws DatabaseUnavailableException { var req = (MessageRequest) parameters[0]; - if (this.database.get(req.reqId) == null) { //idempotence, in case db fails here - database.add(req); //if successful: + if (this.database.get(req.reqId) == null) { // idempotence, in case db fails here + database.add(req); // if successful: LOGGER.info(sendMessage(req.msg)); return req.reqId; } diff --git a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java index 3c2a6129ba8c..354c1c958cae 100644 --- a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java @@ -29,12 +29,10 @@ import java.util.Hashtable; import java.util.Map; -/** - * PaymentDatabase is where the PaymentRequest is added, along with details. - */ +/** PaymentDatabase is where the PaymentRequest is added, along with details. */ public class PaymentDatabase extends Database { - //0-fail, 1-error, 2-success + // 0-fail, 1-error, 2-success private final Map data = new Hashtable<>(); @Override @@ -46,5 +44,4 @@ public PaymentRequest add(PaymentRequest r) { public PaymentRequest get(String requestId) { return data.get(requestId); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java index 0ba0c531001f..953c46165351 100644 --- a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java +++ b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java @@ -32,7 +32,6 @@ * The PaymentService class receives request from the {@link com.iluwatar.commander.Commander} and * adds to the {@link PaymentDatabase}. */ - public class PaymentService extends Service { @RequiredArgsConstructor @@ -46,12 +45,9 @@ public PaymentService(PaymentDatabase db, Exception... exc) { super(db, exc); } - /** - * Public method which will receive request from {@link com.iluwatar.commander.Commander}. - */ - + /** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */ public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { - //it could also be sending a userid, payment details here or something, not added here + // it could also be sending an userid, payment details here or something, not added here var id = generateId(); var req = new PaymentRequest(id, (float) parameters[0]); return updateDb(req); diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java index 5f4f40b5b5df..41b54a276c5c 100644 --- a/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java @@ -25,15 +25,11 @@ package com.iluwatar.commander.queue; import com.iluwatar.commander.Database; -import com.iluwatar.commander.exceptions.DatabaseUnavailableException; import com.iluwatar.commander.exceptions.IsEmptyException; import java.util.ArrayList; import java.util.List; -/** - * QueueDatabase id where the instructions to be implemented are queued. - */ - +/** QueueDatabase id where the instructions to be implemented are queued. */ public class QueueDatabase extends Database { private final Queue data; @@ -48,16 +44,15 @@ public QueueDatabase(Exception... exc) { public QueueTask add(QueueTask t) { data.enqueue(t); return t; - //even if same thing queued twice, it is taken care of in other dbs + // even if same thing queued twice, it is taken care of in other dbs } /** * peek method returns object at front without removing it from queue. * * @return object at front of queue - * @throws IsEmptyException if queue is empty + * @throws IsEmptyException if queue is empty */ - public QueueTask peek() throws IsEmptyException { return this.data.peek(); } @@ -66,9 +61,8 @@ public QueueTask peek() throws IsEmptyException { * dequeue method removes the object at front and returns it. * * @return object at front of queue - * @throws IsEmptyException if queue is empty + * @throws IsEmptyException if queue is empty */ - public QueueTask dequeue() throws IsEmptyException { return this.data.dequeue(); } @@ -77,5 +71,4 @@ public QueueTask dequeue() throws IsEmptyException { public QueueTask get(String taskId) { return null; } - } diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java index 32d205e79f86..aedf55a9e1a3 100644 --- a/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java @@ -29,15 +29,11 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; -/** - * QueueTask object is the object enqueued in queue. - */ +/** QueueTask object is the object enqueued in queue. */ @RequiredArgsConstructor public class QueueTask { - /** - * TaskType is the type of task to be done. - */ + /** TaskType is the type of task to be done. */ public enum TaskType { MESSAGING, PAYMENT, @@ -46,13 +42,11 @@ public enum TaskType { public final Order order; public final TaskType taskType; - public final int messageType; //0-fail, 1-error, 2-success - + public final int messageType; // 0-fail, 1-error, 2-success + /*we could have varargs Object instead to pass in any parameter instead of just message type but keeping it simple here*/ - @Getter - @Setter - private long firstAttemptTime = -1L; //when first time attempt made to do task + @Getter @Setter private long firstAttemptTime = -1L; // when first time attempt made to do task /** * getType method. @@ -72,4 +66,8 @@ public String getType() { } } } + + public boolean isFirstAttempt() { + return this.firstAttemptTime == -1L; + } } diff --git a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java index f1a11d591c69..3906834279b8 100644 --- a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java @@ -29,10 +29,7 @@ import java.util.Hashtable; import java.util.Map; -/** - * ShippingDatabase is where the ShippingRequest objects are added. - */ - +/** ShippingDatabase is where the ShippingRequest objects are added. */ public class ShippingDatabase extends Database { private final Map data = new Hashtable<>(); @@ -45,5 +42,4 @@ public ShippingRequest add(ShippingRequest r) { public ShippingRequest get(String trasnactionId) { return data.get(trasnactionId); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java index 8144fde90810..cc97159b763f 100644 --- a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java +++ b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java @@ -32,7 +32,6 @@ * ShippingService class receives request from {@link com.iluwatar.commander.Commander} class and * adds it to the {@link ShippingDatabase}. */ - public class ShippingService extends Service { @AllArgsConstructor @@ -46,10 +45,7 @@ public ShippingService(ShippingDatabase db, Exception... exc) { super(db, exc); } - /** - * Public method which will receive request from {@link com.iluwatar.commander.Commander}. - */ - + /** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */ public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { var id = generateId(); var item = (String) parameters[0]; diff --git a/commander/src/test/java/com/iluwatar/commander/CommanderTest.java b/commander/src/test/java/com/iluwatar/commander/CommanderTest.java index 42405417c6ac..664268965bf7 100644 --- a/commander/src/test/java/com/iluwatar/commander/CommanderTest.java +++ b/commander/src/test/java/com/iluwatar/commander/CommanderTest.java @@ -24,6 +24,8 @@ */ package com.iluwatar.commander; +import static org.junit.jupiter.api.Assertions.assertFalse; + import com.iluwatar.commander.employeehandle.EmployeeDatabase; import com.iluwatar.commander.employeehandle.EmployeeHandle; import com.iluwatar.commander.exceptions.DatabaseUnavailableException; @@ -37,512 +39,645 @@ import com.iluwatar.commander.queue.QueueDatabase; import com.iluwatar.commander.shippingservice.ShippingDatabase; import com.iluwatar.commander.shippingservice.ShippingService; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.StringUtils; import java.util.ArrayList; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; class CommanderTest { - private final int numOfRetries = 1; - private final long retryDuration = 1_000; - private long queueTime = 1_00; - private long queueTaskTime = 1_000; - private long paymentTime = 6_000; - private long messageTime = 5_000; - private long employeeTime = 2_000; - - private static final List exceptionList = new ArrayList<>(); - - static { - exceptionList.add(new DatabaseUnavailableException()); - exceptionList.add(new ShippingNotPossibleException()); - exceptionList.add(new ItemUnavailableException()); - exceptionList.add(new PaymentDetailsErrorException()); - exceptionList.add(new IllegalStateException()); + private final RetryParams retryParams = new RetryParams(1, 1_000L); + + private final TimeLimits timeLimits = new TimeLimits(1L, 1000L, 6000L, 5000L, 2000L); + + private static final List exceptionList = new ArrayList<>(); + + private static final AppAllCases appAllCases = new AppAllCases(); + + static { + exceptionList.add(new DatabaseUnavailableException()); + exceptionList.add(new ShippingNotPossibleException()); + exceptionList.add(new ItemUnavailableException()); + exceptionList.add(new PaymentDetailsErrorException()); + exceptionList.add(new IllegalStateException()); + } + + private Commander buildCommanderObject() { + return buildCommanderObject(false); + } + + private Commander buildCommanderObject(boolean nonPaymentException) { + PaymentService paymentService = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + + ShippingService shippingService; + MessagingService messagingService; + if (nonPaymentException) { + shippingService = + new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); + messagingService = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + + } else { + shippingService = + new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); + messagingService = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); } - - private Commander buildCommanderObject() { - return buildCommanderObject(false); + var employeeHandle = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectVanilla() { + PaymentService paymentService = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectUnknownException() { + PaymentService paymentService = + new PaymentService(new PaymentDatabase(), new IllegalStateException()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectNoPaymentException1() { + PaymentService paymentService = new PaymentService(new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectNoPaymentException2() { + PaymentService paymentService = new PaymentService(new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = + new MessagingService(new MessagingDatabase(), new IllegalStateException()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectNoPaymentException3() { + PaymentService paymentService = new PaymentService(new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectWithDB() { + return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + private Commander buildCommanderObjectWithDB( + boolean includeException, boolean includeDBException, Exception e) { + var l = includeDBException ? new DatabaseUnavailableException() : e; + PaymentService paymentService; + ShippingService shippingService; + MessagingService messagingService; + EmployeeHandle employeeHandle; + if (includeException) { + paymentService = new PaymentService(new PaymentDatabase(), l); + shippingService = new ShippingService(new ShippingDatabase(), l); + messagingService = new MessagingService(new MessagingDatabase(), l); + employeeHandle = new EmployeeHandle(new EmployeeDatabase(), l); + } else { + paymentService = new PaymentService(null); + shippingService = new ShippingService(null); + messagingService = new MessagingService(null); + employeeHandle = new EmployeeHandle(null); } - private Commander buildCommanderObject(boolean nonPaymentException) { - PaymentService paymentService = new PaymentService - (new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - - ShippingService shippingService; - MessagingService messagingService; - if (nonPaymentException) { - shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); - messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - - } else { - shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); - messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - - } - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + null, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectWithoutDB() { + return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + private Commander buildCommanderObjectWithoutDB( + boolean includeException, boolean includeDBException, Exception e) { + var l = includeDBException ? new DatabaseUnavailableException() : e; + PaymentService paymentService; + ShippingService shippingService; + MessagingService messagingService; + EmployeeHandle employeeHandle; + if (includeException) { + paymentService = new PaymentService(null, l); + shippingService = new ShippingService(null, l); + messagingService = new MessagingService(null, l); + employeeHandle = new EmployeeHandle(null, l); + } else { + paymentService = new PaymentService(null); + shippingService = new ShippingService(null); + messagingService = new MessagingService(null); + employeeHandle = new EmployeeHandle(null); } - private Commander buildCommanderObjectVanilla() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + null, + retryParams, + timeLimits); + } + + @Test + void testPlaceOrderVanilla() { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectVanilla(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectUnknownException() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase(), new IllegalStateException()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + @Test + void testPlaceOrder() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObject(true); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectNoPaymentException1() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + @Test + void testPlaceOrder2() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObject(false); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectNoPaymentException2() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase(), new IllegalStateException()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + @Test + void testPlaceOrderNoException1() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException1(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectNoPaymentException3() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + @Test + void testPlaceOrderNoException2() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException2(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithDB() { - return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + @Test + void testPlaceOrderNoException3() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException3(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithDB(boolean includeException, boolean includeDBException, Exception e) { - var l = includeDBException ? new DatabaseUnavailableException() : e; - PaymentService paymentService; - ShippingService shippingService; - MessagingService messagingService; - EmployeeHandle employeeHandle; - if (includeException) { - paymentService = new PaymentService - (new PaymentDatabase(), l); - shippingService = new ShippingService(new ShippingDatabase(), l); - messagingService = new MessagingService(new MessagingDatabase(), l); - employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), l); - } else { - paymentService = new PaymentService - (null); - shippingService = new ShippingService(null); - messagingService = new MessagingService(null); - employeeHandle = new EmployeeHandle - (null); - } - - - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, null, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + @Test + void testPlaceOrderNoException4() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException3(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + c.placeOrder(order); + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithoutDB() { - return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + @Test + void testPlaceOrderUnknownException() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectUnknownException(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithoutDB(boolean includeException, boolean includeDBException, Exception e) { - var l = includeDBException ? new DatabaseUnavailableException() : e; - PaymentService paymentService; - ShippingService shippingService; - MessagingService messagingService; - EmployeeHandle employeeHandle; - if (includeException) { - paymentService = new PaymentService - (null, l); - shippingService = new ShippingService(null, l); - messagingService = new MessagingService(null, l); - employeeHandle = new EmployeeHandle - (null, l); - } else { - paymentService = new PaymentService - (null); - shippingService = new ShippingService(null); - messagingService = new MessagingService(null); - employeeHandle = new EmployeeHandle - (null); - } - - - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, null, numOfRetries, retryDuration, - queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + } + + @Test + void testPlaceOrderShortDuration() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObject(true); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderVanilla() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectVanilla(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderShortDuration2() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObject(false); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrder() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObject(true); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderNoExceptionShortMsgDuration() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectNoPaymentException1(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrder2() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObject(false); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderNoExceptionShortQueueDuration() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectUnknownException(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderNoException1() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException1(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderWithDatabase() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectWithDB(); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderNoException2() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException2(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + } + + @Test + void testPlaceOrderWithDatabaseAndExceptions() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + + for (Exception e : exceptionList) { + + Commander c = buildCommanderObjectWithDB(true, true, e); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderNoException3() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException3(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithDB(true, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderNoException4() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException3(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - c.placeOrder(order); - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithDB(false, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderUnknownException() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectUnknownException(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithDB(false, true, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } + } } - - @Test - void testPlaceOrderShortDuration() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObject(true); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderWithoutDatabase() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectWithoutDB(); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderShortDuration2() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObject(false); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + } + + @Test + void testPlaceOrderWithoutDatabaseAndExceptions() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + + for (Exception e : exceptionList) { + + Commander c = buildCommanderObjectWithoutDB(true, true, e); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderNoExceptionShortMsgDuration() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectNoPaymentException1(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithoutDB(true, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderNoExceptionShortQueueDuration() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectUnknownException(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithoutDB(false, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderWithDatabase() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectWithDB(); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithoutDB(false, true, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } + } } - - @Test - void testPlaceOrderWithDatabaseAndExceptions() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - - for (Exception e : exceptionList) { - - Commander c = buildCommanderObjectWithDB(true, true, e); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithDB(true, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithDB(false, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithDB(false, true, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } - } - } - - @Test - void testPlaceOrderWithoutDatabase() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectWithoutDB(); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } - } - - @Test - void testPlaceOrderWithoutDatabaseAndExceptions() throws Exception { - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - - for (Exception e : exceptionList) { - - Commander c = buildCommanderObjectWithoutDB(true, true, e); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithoutDB(true, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithoutDB(false, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithoutDB(false, true, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } - } - } - + } + + @Test + void testAllSuccessCases() throws Exception { + appAllCases.employeeDbSuccessCase(); + appAllCases.messagingSuccessCase(); + appAllCases.paymentSuccessCase(); + appAllCases.queueSuccessCase(); + appAllCases.shippingSuccessCase(); + } + + @Test + void testAllUnavailableCase() throws Exception { + appAllCases.employeeDatabaseUnavailableCase(); + appAllCases.messagingDatabaseUnavailableCasePaymentSuccess(); + appAllCases.messagingDatabaseUnavailableCasePaymentError(); + appAllCases.messagingDatabaseUnavailableCasePaymentFailure(); + appAllCases.paymentDatabaseUnavailableCase(); + appAllCases.queuePaymentTaskDatabaseUnavailableCase(); + appAllCases.queueMessageTaskDatabaseUnavailableCase(); + appAllCases.queueEmployeeDbTaskDatabaseUnavailableCase(); + appAllCases.itemUnavailableCase(); + appAllCases.shippingDatabaseUnavailableCase(); + } + + @Test + void testAllNotPossibleCase() throws Exception { + appAllCases.paymentNotPossibleCase(); + appAllCases.shippingItemNotPossibleCase(); + } } diff --git a/commander/src/test/java/com/iluwatar/commander/RetryTest.java b/commander/src/test/java/com/iluwatar/commander/RetryTest.java index 2276b340efc7..389a45c2a52f 100644 --- a/commander/src/test/java/com/iluwatar/commander/RetryTest.java +++ b/commander/src/test/java/com/iluwatar/commander/RetryTest.java @@ -31,38 +31,56 @@ import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class RetryTest { + private static final Logger LOG = LoggerFactory.getLogger(RetryTest.class); + @Test void performTest() { - Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - throw l.remove(0); - } - }; - Retry.HandleErrorIssue handleError = (o, e) -> { - }; - var r1 = new Retry<>(op, handleError, 3, 30000, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - var r2 = new Retry<>(op, handleError, 3, 30000, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + Retry.Operation op = + (l) -> { + if (!l.isEmpty()) { + throw l.remove(0); + } + }; + Retry.HandleErrorIssue handleError = (o, e) -> {}; + var r1 = + new Retry<>( + op, + handleError, + 3, + 30000, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + var r2 = + new Retry<>( + op, + handleError, + 3, + 30000, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); var user = new User("Jim", "ABCD"); var order = new Order(user, "book", 10f); - var arr1 = new ArrayList<>(List.of(new ItemUnavailableException(), new DatabaseUnavailableException())); + var arr1 = + new ArrayList<>( + List.of(new ItemUnavailableException(), new DatabaseUnavailableException())); try { r1.perform(arr1, order); } catch (Exception e1) { - e1.printStackTrace(); + LOG.error("An exception occurred", e1); } - var arr2 = new ArrayList<>(List.of(new DatabaseUnavailableException(), new ItemUnavailableException())); + var arr2 = + new ArrayList<>( + List.of(new DatabaseUnavailableException(), new ItemUnavailableException())); try { r2.perform(arr2, order); } catch (Exception e1) { - e1.printStackTrace(); + LOG.error("An exception occurred", e1); } - //r1 stops at ItemUnavailableException, r2 retries because it encounters DatabaseUnavailableException - assertTrue(arr1.size() == 1 && arr2.size() == 0); + // r1 stops at ItemUnavailableException, r2 retries because it encounters + // DatabaseUnavailableException + assertTrue(arr1.size() == 1 && arr2.isEmpty()); } - } diff --git a/component/README.md b/component/README.md index 7864945fc3e1..638133265aa3 100644 --- a/component/README.md +++ b/component/README.md @@ -1,54 +1,46 @@ --- -title: Component -categories: Behavioral +title: "Component Pattern in Java: Simplifying Complex Systems with Reusable Components" +shortTitle: Component +description: "Learn about the Component Design Pattern in Java, including ECS architecture, modularity, and decoupling. Explore examples, class diagrams, and real-world applications in game development for flexible and maintainable code." +categories: Structural language: en tag: -- Game programming -- Domain + - Game programming + - Decoupling + - Modularity --- -## Intent +## Also known as -The component design pattern enables developers to decouple attributes of an objects. Essentially allowing a single -component to be inheritable by multiple domains/objects without linking the objects to each other. In addition to this -benefit, the component design pattern allows developer to write maintainable and comprehensible code which is less -likely to result in monolithic classes. +* Entity-Component-System (ECS) +* Component-Entity-System (CES) +* Component-Based Architecture (CBA) -![Intent](./etc/component.duplication.png "Component Design Pattern") +## Intent of Component Design Pattern -## Explanation +The Component design pattern organizes code into reusable, interchangeable components, promoting flexibility, modularity, and ease of maintenance. This pattern is especially useful in game development, enabling entities to be configured with diverse behaviors dynamically. -Real world example -> Suppose your video game consists of a graphics component and a sound component. Including the methods and attributes of both of these features in a single java class can be problematic due to many reasons. Firstly, the graphics and sound code can create an extremely long java class which can be hard to maintain. Furthermore, graphics components may be written and implemented by a separate team as to the sound contents. If both parties work simultaneously on the same java class, this may cause conflicts and major delay. Using the component design pattern, the development team is able to create individual component classes for graphics and sound whilst providing the domain/object the reach to both of these attributes. +## Detailed Explanation of Component Pattern with Real-World Examples +Real-world example + +> Consider a video game with a graphics component and a sound component. Including both in a single Java class can create maintenance challenges due to lengthy code and potential conflicts from different teams working on the same class. The Component design pattern resolves this by creating individual component classes for graphics and sound, allowing flexible and independent development. This modular approach enhances maintainability and scalability. In plain words -> The component design pattern provides a single attribute to be accessible by numerous objects without requiring the -> existence of a relationship between the objects themselves. -Key drawback -> With the implementation of the component design pattern, it can be very difficult to create a relationship -> between components. For example, suppose we require the sound component to be aware of the current animation in order -> create a certain sound based upon the animation; this can be quite tricky as the component design pattern makes -> components 'unaware' of other components' existence due to its decoupling nature. +> The component design pattern provides a single attribute to be accessible by numerous objects without requiring the existence of a relationship between the objects themselves. -**Programmatic Example** +## Programmatic Example of Component Pattern in Java -The App class creates a demonstration of the use of the component pattern by creating two different objects which -inherit a small collection of individual components that are modifiable. +The `App` class creates a demonstration of the use of the component pattern by creating two different objects which inherit a small collection of individual components that are modifiable. ```java public final class App { - /** - * Program entry point. - * - * @param args args command line args. - */ + public static void main(String[] args) { final var player = GameObject.createPlayer(); final var npc = GameObject.createNpc(); - LOGGER.info("Player Update:"); player.update(KeyEvent.KEY_LOCATION_LEFT); LOGGER.info("NPC Update:"); @@ -57,71 +49,61 @@ public final class App { } ``` -Much of the program exists within the GameObject class, within this class, the player and NPC object create methods are -set up. Additionally, this class also consists of the method calls used to update/alter information of the object's -components. +Much of the program exists within the `GameObject` class, within this class, the player and NPC object create methods are set up. Additionally, this class also consists of the method calls used to update/alter information of the object's components. ```java public class GameObject { - private final InputComponent inputComponent; - private final PhysicComponent physicComponent; - private final GraphicComponent graphicComponent; - - public String name; - public int velocity = 0; - public int coordinate = 0; - - public static GameObject createPlayer() { - return new GameObject(new PlayerInputComponent(), - new ObjectPhysicComponent(), - new ObjectGraphicComponent(), - "player"); - } - - public static GameObject createNpc() { - return new GameObject( - new DemoInputComponent(), - new ObjectPhysicComponent(), - new ObjectGraphicComponent(), - "npc"); - } - - public void demoUpdate() { - inputComponent.update(this); - physicComponent.update(this); - graphicComponent.update(this); - } - - public void update(int e) { - inputComponent.update(this, e); - physicComponent.update(this); - graphicComponent.update(this); - } - - public void updateVelocity(int acceleration) { - this.velocity += acceleration; - } - - public void updateCoordinate() { - this.coordinate += this.velocity; - } + private final InputComponent inputComponent; + private final PhysicComponent physicComponent; + private final GraphicComponent graphicComponent; + + public String name; + public int velocity = 0; + public int coordinate = 0; + + public static GameObject createPlayer() { + return new GameObject(new PlayerInputComponent(), + new ObjectPhysicComponent(), + new ObjectGraphicComponent(), + "player"); + } + + public static GameObject createNpc() { + return new GameObject( + new DemoInputComponent(), + new ObjectPhysicComponent(), + new ObjectGraphicComponent(), + "npc"); + } + + public void demoUpdate() { + inputComponent.update(this); + physicComponent.update(this); + graphicComponent.update(this); + } + + public void update(int e) { + inputComponent.update(this, e); + physicComponent.update(this); + graphicComponent.update(this); + } + + public void updateVelocity(int acceleration) { + this.velocity += acceleration; + } + + public void updateCoordinate() { + this.coordinate += this.velocity; + } } ``` -Upon opening the component package, the collection of components are revealed. These components provide the interface -for objects to inherit these domains. The PlayerInputComponent class shown below updates the object's velocity -characteristic based on user's key event input. +Upon opening the component package, the collection of components are revealed. These components provide the interface for objects to inherit these domains. The `PlayerInputComponent` class shown below updates the object's velocity characteristic based on user's key event input. ```java public class PlayerInputComponent implements InputComponent { private static final int walkAcceleration = 1; - /** - * The update method to change the velocity based on the input key event. - * - * @param gameObject the gameObject instance - * @param e key event instance - */ @Override public void update(GameObject gameObject, int e) { switch (e) { @@ -142,19 +124,38 @@ public class PlayerInputComponent implements InputComponent { } ``` -## Class diagram +## When to Use the Component Pattern in Java + +* Used in game development and simulations where game entities (e.g., characters, items) can have a dynamic set of abilities or states. +* Suitable for systems requiring high modularity and systems where entities might need to change behavior at runtime without inheritance hierarchies. + +## Real-World Applications of Component Pattern in Java + +The Component pattern is ideal for game development and simulations where entities like characters and items have dynamic abilities or states. It suits systems requiring high modularity and scenarios where entities need to change behavior at runtime without relying on inheritance hierarchies, enhancing flexibility and maintainability. + +## Benefits and Trade-offs of Component Pattern + +Benefits: + +* Flexibility and Reusability: Components can be reused across different entities, making it easier to add new features or modify existing ones. +* Decoupling: Reduces dependencies between game entity states and behaviors, facilitating easier changes and maintenance. +* Dynamic Composition: Entities can alter their behavior at runtime by adding or removing components, providing significant flexibility in game design. -![UML](./etc/component.uml.png "The UML for Component Design Pattern") +Trade-offs: -## Applicability +* Complexity: Can introduce additional complexity in system architecture, particularly in managing dependencies and communications between components. +* Performance Considerations: Depending on implementation, may incur a performance overhead due to indirection and dynamic behavior, especially critical in high-performance game loops. -Use the component design pattern when +## Related Java Design Patterns -- you have a class which access multiple features which you would like to keep separate. -- you want to reduce the length of a class. -- you require a variety of objects to share a collection of components but the use of inheritance isn't specific enough. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar concept of adding responsibilities dynamically, but without the focus on game entities. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Can be used in conjunction with the Component pattern to share component instances among many entities to save memory. +* [Observer](https://java-design-patterns.com/patterns/observer/): Often used in Component systems to communicate state changes between components. -## Credits +## References and Credits -- [Component Design Pattern] (https://gameprogrammingpatterns.com/component.html) -- [Component pattern - game programming series - Tutemic] (https://www.youtube.com/watch?v=n92GBp2WMkg&ab_channel=Tutemic) \ No newline at end of file +* [Game Programming Patterns](https://amzn.to/4cDRWhV) +* [Procedural Content Generation for Unity Game Development](https://amzn.to/3vBKCTp) +* [Unity in Action: Multiplatform Game Development in C#](https://amzn.to/3THO6vw) +* [Component (Game Programming Patterns)](https://gameprogrammingpatterns.com/component.html) +* [Component pattern - game programming series (Tutemic)](https://www.youtube.com/watch?v=n92GBp2WMkg&ab_channel=Tutemic) diff --git a/component/etc/component.duplication.png b/component/etc/component.duplication.png deleted file mode 100644 index 1e5f376f3d69..000000000000 Binary files a/component/etc/component.duplication.png and /dev/null differ diff --git a/component/pom.xml b/component/pom.xml index d416ae4f53df..e666e283489b 100644 --- a/component/pom.xml +++ b/component/pom.xml @@ -1,4 +1,30 @@ + @@ -11,9 +37,17 @@ component + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine test diff --git a/component/src/main/java/com/iluwatar/component/App.java b/component/src/main/java/com/iluwatar/component/App.java index 12f19a691245..9401483e92e2 100644 --- a/component/src/main/java/com/iluwatar/component/App.java +++ b/component/src/main/java/com/iluwatar/component/App.java @@ -1,22 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component; import java.awt.event.KeyEvent; import lombok.extern.slf4j.Slf4j; /** - * The component design pattern is a common game design structure. This pattern is often - * used to reduce duplication of code as well as to improve maintainability. - * In this implementation, component design pattern has been used to provide two game - * objects with varying component interfaces (features). As opposed to copying and - * pasting same code for the two game objects, the component interfaces allow game - * objects to inherit these components from the component classes. + * The component design pattern is a common game design structure. This pattern is often used to + * reduce duplication of code as well as to improve maintainability. In this implementation, + * component design pattern has been used to provide two game objects with varying component + * interfaces (features). As opposed to copying and pasting same code for the two game objects, the + * component interfaces allow game objects to inherit these components from the component classes. * - *

The implementation has decoupled graphic, physics and input components from - * the player and NPC objects. As a result, it avoids the creation of monolithic java classes. + *

The implementation has decoupled graphic, physics and input components from the player and NPC + * objects. As a result, it avoids the creation of monolithic java classes. * - *

The below example in this App class demonstrates the use of the component interfaces - * for separate objects (player & NPC) and updating of these components as per the - * implementations in GameObject class and the component classes. + *

The below example in this App class demonstrates the use of the component interfaces for + * separate objects (player & NPC) and updating of these components as per the implementations in + * GameObject class and the component classes. */ @Slf4j public final class App { @@ -29,7 +52,6 @@ public static void main(String[] args) { final var player = GameObject.createPlayer(); final var npc = GameObject.createNpc(); - LOGGER.info("Player Update:"); player.update(KeyEvent.KEY_LOCATION_LEFT); LOGGER.info("NPC Update:"); diff --git a/component/src/main/java/com/iluwatar/component/GameObject.java b/component/src/main/java/com/iluwatar/component/GameObject.java index 10410c18e160..87c35b24e8c4 100644 --- a/component/src/main/java/com/iluwatar/component/GameObject.java +++ b/component/src/main/java/com/iluwatar/component/GameObject.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component; import com.iluwatar.component.component.graphiccomponent.GraphicComponent; @@ -11,8 +35,8 @@ import lombok.RequiredArgsConstructor; /** - * The GameObject class has three component class instances that allow - * the creation of different game objects based on the game design requirements. + * The GameObject class has three component class instances that allow the creation of different + * game objects based on the game design requirements. */ @Getter @RequiredArgsConstructor @@ -31,13 +55,13 @@ public class GameObject { * @return player object */ public static GameObject createPlayer() { - return new GameObject(new PlayerInputComponent(), + return new GameObject( + new PlayerInputComponent(), new ObjectPhysicComponent(), new ObjectGraphicComponent(), "player"); } - /** * Creates a NPC game object. * @@ -45,16 +69,12 @@ public static GameObject createPlayer() { */ public static GameObject createNpc() { return new GameObject( - new DemoInputComponent(), - new ObjectPhysicComponent(), - new ObjectGraphicComponent(), - "npc"); + new DemoInputComponent(), new ObjectPhysicComponent(), new ObjectGraphicComponent(), "npc"); } /** - * Updates the three components of the NPC object used in the demo in App.java - * note that this is simply a duplicate of update() without the key event for - * demonstration purposes. + * Updates the three components of the NPC object used in the demo in App.java note that this is + * simply a duplicate of update() without the key event for demonstration purposes. * *

This method is usually used in games if the player becomes inactive. */ @@ -84,10 +104,7 @@ public void updateVelocity(int acceleration) { this.velocity += acceleration; } - - /** - * Set the c based on the current velocity. - */ + /** Set the c based on the current velocity. */ public void updateCoordinate() { this.coordinate += this.velocity; } diff --git a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java index 26a80cc7a514..600ee8e5275d 100644 --- a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java @@ -1,10 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component.component.graphiccomponent; import com.iluwatar.component.GameObject; -/** - * Generic GraphicComponent interface. - */ +/** Generic GraphicComponent interface. */ public interface GraphicComponent { void update(GameObject gameObject); } diff --git a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java index b147a916d78b..7f595763f10c 100644 --- a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java @@ -1,11 +1,33 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component.component.graphiccomponent; import com.iluwatar.component.GameObject; import lombok.extern.slf4j.Slf4j; -/** - * ObjectGraphicComponent class mimics the graphic component of the Game Object. - */ +/** ObjectGraphicComponent class mimics the graphic component of the Game Object. */ @Slf4j public class ObjectGraphicComponent implements GraphicComponent { diff --git a/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java b/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java index 34e0e1202a0b..b7ff51c3f2e7 100644 --- a/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java @@ -1,14 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component.component.inputcomponent; import com.iluwatar.component.GameObject; import lombok.extern.slf4j.Slf4j; /** - * Take this component class to control player or the NPC for demo mode. - * and implemented the InputComponent interface. + * Take this component class to control player or the NPC for demo mode. and implemented the + * InputComponent interface. * - *

Essentially, the demo mode is utilised during a game if the user become inactive. - * Please see: http://gameprogrammingpatterns.com/component.html + *

Essentially, the demo mode is utilised during a game if the user become inactive. Please see: + * http://gameprogrammingpatterns.com/component.html */ @Slf4j public class DemoInputComponent implements InputComponent { @@ -18,7 +42,7 @@ public class DemoInputComponent implements InputComponent { * Redundant method in the demo mode. * * @param gameObject the gameObject instance - * @param e key event instance + * @param e key event instance */ @Override public void update(GameObject gameObject, int e) { diff --git a/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java b/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java index a597086039a0..65bab37fc59d 100644 --- a/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java @@ -1,10 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component.component.inputcomponent; import com.iluwatar.component.GameObject; -/** - * Generic InputComponent interface. - */ +/** Generic InputComponent interface. */ public interface InputComponent { void update(GameObject gameObject, int e); } diff --git a/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java b/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java index c2048b6198e7..d38682de49f9 100644 --- a/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component.component.inputcomponent; import com.iluwatar.component.GameObject; @@ -5,8 +29,8 @@ import lombok.extern.slf4j.Slf4j; /** - * PlayerInputComponent is used to handle user key event inputs, - * and thus it implements the InputComponent interface. + * PlayerInputComponent is used to handle user key event inputs, and thus it implements the + * InputComponent interface. */ @Slf4j public class PlayerInputComponent implements InputComponent { @@ -16,7 +40,7 @@ public class PlayerInputComponent implements InputComponent { * The update method to change the velocity based on the input key event. * * @param gameObject the gameObject instance - * @param e key event instance + * @param e key event instance */ @Override public void update(GameObject gameObject, int e) { diff --git a/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java b/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java index 2fc4f53a7dbf..a7c189efc463 100644 --- a/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java @@ -1,11 +1,33 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component.component.physiccomponent; import com.iluwatar.component.GameObject; import lombok.extern.slf4j.Slf4j; -/** - * Take this component class to update the x coordinate for the Game Object instance. - */ +/** Take this component class to update the x coordinate for the Game Object instance. */ @Slf4j public class ObjectPhysicComponent implements PhysicComponent { diff --git a/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java b/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java index d7f55153f401..67c33026024d 100644 --- a/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java @@ -1,10 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component.component.physiccomponent; import com.iluwatar.component.GameObject; -/** - * Generic PhysicComponent interface. - */ +/** Generic PhysicComponent interface. */ public interface PhysicComponent { void update(GameObject gameObject); } diff --git a/component/src/test/java/com/iluwatar/component/AppTest.java b/component/src/test/java/com/iluwatar/component/AppTest.java index 35b5f243f51a..7de0745e7685 100644 --- a/component/src/test/java/com/iluwatar/component/AppTest.java +++ b/component/src/test/java/com/iluwatar/component/AppTest.java @@ -1,17 +1,41 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + /** - * Tests App class : src/main/java/com/iluwatar/component/App.java - * General execution test of the application. + * Tests App class : src/main/java/com/iluwatar/component/App.java General execution test of the + * application. */ class AppTest { - @Test - void shouldExecuteComponentWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldExecuteComponentWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/component/src/test/java/com/iluwatar/component/GameObjectTest.java b/component/src/test/java/com/iluwatar/component/GameObjectTest.java index 68a35321255e..09b3b3995268 100644 --- a/component/src/test/java/com/iluwatar/component/GameObjectTest.java +++ b/component/src/test/java/com/iluwatar/component/GameObjectTest.java @@ -1,71 +1,87 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.component; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; + import java.awt.event.KeyEvent; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -/** - * Tests GameObject class. - * src/main/java/com/iluwatar/component/GameObject.java - */ +/** Tests GameObject class. src/main/java/com/iluwatar/component/GameObject.java */ @Slf4j class GameObjectTest { - GameObject playerTest; - GameObject npcTest; - @BeforeEach - public void initEach() { - //creates player & npc objects for testing - //note that velocity and coordinates are initialised to 0 in GameObject.java - playerTest = GameObject.createPlayer(); - npcTest = GameObject.createNpc(); - } + GameObject playerTest; + GameObject npcTest; + + @BeforeEach + public void initEach() { + // creates player & npc objects for testing + // note that velocity and coordinates are initialised to 0 in GameObject.java + playerTest = GameObject.createPlayer(); + npcTest = GameObject.createNpc(); + } - /** - * Tests the create methods - createPlayer() and createNPC(). - */ - @Test - void objectTest(){ - LOGGER.info("objectTest:"); - assertEquals("player",playerTest.getName()); - assertEquals("npc",npcTest.getName()); - } + /** Tests the create methods - createPlayer() and createNPC(). */ + @Test + void objectTest() { + LOGGER.info("objectTest:"); + assertEquals("player", playerTest.getName()); + assertEquals("npc", npcTest.getName()); + } - /** - * Tests the input component with varying key event inputs. - * Targets the player game object. - */ - @Test - void eventInputTest(){ - LOGGER.info("eventInputTest:"); - playerTest.update(KeyEvent.KEY_LOCATION_LEFT); - assertEquals(-1, playerTest.getVelocity()); - assertEquals(-1, playerTest.getCoordinate()); + /** Tests the input component with varying key event inputs. Targets the player game object. */ + @Test + void eventInputTest() { + LOGGER.info("eventInputTest:"); + playerTest.update(KeyEvent.KEY_LOCATION_LEFT); + assertEquals(-1, playerTest.getVelocity()); + assertEquals(-1, playerTest.getCoordinate()); - playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); - playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); - assertEquals(1, playerTest.getVelocity()); - assertEquals(0, playerTest.getCoordinate()); + playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); + playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); + assertEquals(1, playerTest.getVelocity()); + assertEquals(0, playerTest.getCoordinate()); - LOGGER.info(Integer.toString(playerTest.getCoordinate())); - LOGGER.info(Integer.toString(playerTest.getVelocity())); + LOGGER.info(Integer.toString(playerTest.getCoordinate())); + LOGGER.info(Integer.toString(playerTest.getVelocity())); - GameObject p2 = GameObject.createPlayer(); - p2.update(KeyEvent.KEY_LOCATION_LEFT); - //in the case of an unknown, object stats are set to default - p2.update(KeyEvent.KEY_LOCATION_UNKNOWN); - assertEquals(-1, p2.getVelocity()); - } + GameObject p2 = GameObject.createPlayer(); + p2.update(KeyEvent.KEY_LOCATION_LEFT); + // in the case of an unknown, object stats are set to default + p2.update(KeyEvent.KEY_LOCATION_UNKNOWN); + assertEquals(-1, p2.getVelocity()); + } - /** - * Tests the demo component interface. - */ - @Test - void npcDemoTest(){ - LOGGER.info("npcDemoTest:"); - npcTest.demoUpdate(); - assertEquals(2, npcTest.getVelocity()); - assertEquals(2, npcTest.getCoordinate()); - } + /** Tests the demo component interface. */ + @Test + void npcDemoTest() { + LOGGER.info("npcDemoTest:"); + npcTest.demoUpdate(); + assertEquals(2, npcTest.getVelocity()); + assertEquals(2, npcTest.getCoordinate()); + } } diff --git a/composite-entity/README.md b/composite-entity/README.md index d02d92de6e60..9ac14d97e3c7 100644 --- a/composite-entity/README.md +++ b/composite-entity/README.md @@ -1,55 +1,72 @@ --- -title: Composite Entity +title: "Composite Entity Pattern in Java: Streamlining Persistent Object Management" +shortTitle: Composite Entity +description: "Learn about the Composite Entity design pattern in Java, a structural pattern used to manage interrelated persistent objects as a single entity. Ideal for enterprise applications and EJB, this pattern simplifies complex data structures and client interactions." category: Structural language: en tag: - - Enterprise Integration Pattern + - Client-server + - Data access + - Decoupling + - Enterprise patterns + - Object composition + - Persistence + - Resource management --- -## Intent +## Also known as -It is used to model, represent, and manage a set of persistent objects that are interrelated, rather than representing them as individual fine-grained entities. +* Coarse-Grained Entity -## Explanation +## Intent of Composite Entity Design Pattern -Real world example +The Composite Entity design pattern in Java is aimed at managing a set of interrelated persistent objects as if they were a single entity. It is commonly used in enterprise applications, particularly within the context of Enterprise JavaBeans (EJB) and similar enterprise frameworks, to represent graph-based data structures within business models. This pattern enables clients to treat these complex structures as a single unit, simplifying operations and interactions. -> For a console, there may be many interfaces that need to be managed and controlled. Using the composite entity pattern, dependent objects such as messages and signals can be combined together and controlled using a single object. +## Detailed Explanation of Composite Entity Pattern with Real-World Examples + +Real-world example + +> Consider a university registration system where a "Student" entity is a composite entity. Each "Student" object includes several dependent objects: personal details, course enrollments, grades, and payment information. Instead of managing each of these aspects separately, the Composite Entity design pattern allows the university system to treat the "Student" as a single entity. This simplifies operations such as enrolling a student in a new course, updating grades, and processing payments, since all related actions can be managed through the composite "Student" object. In plain words -> Composite entity pattern allows a set of related objects to be represented and managed by a unified object. +> The Composite Entity pattern in Java allows a set of related persistent objects to be represented and managed by a unified object, simplifying enterprise application design. + +Wikipedia says + +> Composite entity is a Java EE Software design pattern and it is used to model, represent, and manage a set of interrelated persistent objects rather than representing them as individual fine-grained entity beans, and also a composite entity bean represents a graph of objects. -**Programmatic Example** +## Programmatic Example of Composite Entity in Java -We need a generic solution for the problem. To achieve this, let's introduce a generic -Composite Entity Pattern. +For a console, there may be many interfaces that need to be managed and controlled. Using the composite entity pattern, dependent objects such as messages and signals can be combined and controlled using a single object. + +We need a generic solution for the problem. To achieve this, let's introduce a generic Composite Entity Pattern. ```java public abstract class DependentObject { - T data; + T data; - public void setData(T message) { - this.data = message; - } + public void setData(T message) { + this.data = message; + } - public T getData() { - return data; - } + public T getData() { + return data; + } } public abstract class CoarseGrainedObject { - DependentObject[] dependentObjects; + DependentObject[] dependentObjects; - public void setData(T... data) { - IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i])); - } + public void setData(T... data) { + IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i])); + } - public T[] getData() { - return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray(); - } + public T[] getData() { + return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray(); + } } ``` @@ -67,54 +84,83 @@ public class SignalDependentObject extends DependentObject { public class ConsoleCoarseGrainedObject extends CoarseGrainedObject { - @Override - public String[] getData() { - super.getData(); - return new String[]{ - dependentObjects[0].getData(), dependentObjects[1].getData() - }; - } - - public void init() { - dependentObjects = new DependentObject[]{ - new MessageDependentObject(), new SignalDependentObject()}; - } + @Override + public String[] getData() { + super.getData(); + return new String[] { + dependentObjects[0].getData(), dependentObjects[1].getData() + }; + } + + public void init() { + dependentObjects = new DependentObject[] { + new MessageDependentObject(), new SignalDependentObject()}; + } } public class CompositeEntity { - private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); + private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); - public void setData(String message, String signal) { - console.setData(message, signal); - } + public void setData(String message, String signal) { + console.setData(message, signal); + } - public String[] getData() { - return console.getData(); - } + public String[] getData() { + return console.getData(); + } } ``` Now managing the assignment of message and signal objects with the composite entity `console`. ```java -var console = new CompositeEntity(); -console.init(); -console.setData("No Danger", "Green Light"); -Arrays.stream(console.getData()).forEach(LOGGER::info); -console.setData("Danger", "Red Light"); -Arrays.stream(console.getData()).forEach(LOGGER::info); +public App(String message, String signal) { + var console = new CompositeEntity(); + console.init(); + console.setData(message, signal); + Arrays.stream(console.getData()).forEach(LOGGER::info); + console.setData("Danger", "Red Light"); + Arrays.stream(console.getData()).forEach(LOGGER::info); +} ``` -## Class diagram +## When to Use the Composite Entity Pattern in Java + +* Useful in Java enterprise applications where business objects are complex and involve various interdependent persistent objects. +* Ideal for scenarios where clients need to work with a unified interface to a set of objects rather than individual entities. +* Applicable in systems that require a simplified view of a complex data model for external clients or services. + + +## Real-World Applications of Composite Entity Pattern in Java + +* Enterprise applications with complex business models, particularly those using EJB or similar enterprise frameworks. +* Systems requiring abstraction over complex database schemas to simplify client interactions. +* Applications that need to enforce consistency or transactions across multiple persistent objects in a business entity. + +## Benefits and Trade-offs of Composite Entity Pattern + +Benefits: + +* Simplifies client interactions with complex entity models by providing a unified interface. +* Enhances reusability and maintainability of the business layer by decoupling client code from the complex internals of business entities. +* Facilitates easier transaction management and consistency enforcement across a set of related persistent objects. + +Trade-offs: -![alt text](./etc/composite_entity.urm.png "Composite Entity Pattern") +* May introduce a level of indirection that could impact performance. +* Can lead to overly coarse-grained interfaces that might not be as flexible for all client needs. +* Requires careful design to avoid bloated composite entities that are difficult to manage. -## Applicability +## Related Java Design Patterns -Use the Composite Entity Pattern in the following situation: +* [Decorator](https://java-design-patterns.com/patterns/decorator/): For dynamically adding behavior to individual objects within the composite entity without affecting the structure. +* [Facade](https://java-design-patterns.com/patterns/facade/): Provides a simplified interface to a complex subsystem, similar to how a composite entity simplifies access to a set of objects. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Useful for managing shared objects within a composite entity to reduce memory footprint. -* You want to manage multiple dependency objects through one object to adjust the degree of granularity between objects. At the same time, the lifetime of dependency objects depends on a coarse-grained object. -## Credits +## References and Credits -* [Composite Entity Pattern in wikipedia](https://en.wikipedia.org/wiki/Composite_entity_pattern) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Enterprise Patterns and MDA: Building Better Software with Archetype Patterns and UML](https://amzn.to/49mslqS) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3xjKdpe) +* [Composite Entity Pattern (Wikipedia)](https://en.wikipedia.org/wiki/Composite_entity_pattern) diff --git a/composite-entity/pom.xml b/composite-entity/pom.xml index bed5c3a40048..5d11234c3a0a 100644 --- a/composite-entity/pom.xml +++ b/composite-entity/pom.xml @@ -34,6 +34,14 @@ 4.0.0 composite-entity + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java index 68569b37d26e..c3ae1181a800 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java @@ -27,7 +27,6 @@ import java.util.Arrays; import lombok.extern.slf4j.Slf4j; - /** * Composite entity is a Java EE Software design pattern and it is used to model, represent, and * manage a set of interrelated persistent objects rather than representing them as individual @@ -36,10 +35,7 @@ @Slf4j public class App { - - /** - * An instance that a console manages two related objects. - */ + /** An instance that a console manages two related objects. */ public App(String message, String signal) { var console = new CompositeEntity(); console.init(); @@ -57,6 +53,5 @@ public App(String message, String signal) { public static void main(String[] args) { new App("No Danger", "Green Light"); - } } diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java index 1eefbf2e4659..fa195c2cd9fd 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java @@ -32,7 +32,6 @@ * other objects. It can be an object contained in the composite entity, or, composite entity itself * can be the coarse-grained object which holds dependent objects. */ - public abstract class CoarseGrainedObject { DependentObject[] dependentObjects; diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java index cd52841ca114..0c4a05155ca8 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java @@ -28,7 +28,6 @@ * Composite entity is the coarse-grained entity bean which may be the coarse-grained object, or may * contain a reference to the coarse-grained object. */ - public class CompositeEntity { private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); @@ -44,4 +43,4 @@ public String[] getData() { public void init() { console.init(); } -} \ No newline at end of file +} diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java index 4ec8413a99fb..422fdc541079 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java @@ -24,21 +24,16 @@ */ package com.iluwatar.compositeentity; -/** - * A specific CoarseGrainedObject to implement a console. - */ - +/** A specific CoarseGrainedObject to implement a console. */ public class ConsoleCoarseGrainedObject extends CoarseGrainedObject { @Override public String[] getData() { - return new String[]{ - dependentObjects[0].getData(), dependentObjects[1].getData() - }; + return new String[] {dependentObjects[0].getData(), dependentObjects[1].getData()}; } public void init() { - dependentObjects = new DependentObject[]{ - new MessageDependentObject(), new SignalDependentObject()}; + dependentObjects = + new DependentObject[] {new MessageDependentObject(), new SignalDependentObject()}; } -} \ No newline at end of file +} diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java index 4d072c781d7d..90f2024bc127 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java @@ -24,21 +24,17 @@ */ package com.iluwatar.compositeentity; +import lombok.Getter; +import lombok.Setter; + /** * It is an object, which can contain other dependent objects (there may be a tree of objects within * the composite entity), that depends on the coarse-grained object and has its life cycle managed * by the coarse-grained object. */ - +@Setter +@Getter public abstract class DependentObject { T data; - - public void setData(T message) { - this.data = message; - } - - public T getData() { - return data; - } } diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java index 98b351fe62e8..199e5ee1d859 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java @@ -24,10 +24,5 @@ */ package com.iluwatar.compositeentity; -/** - * The first DependentObject to show message. - */ - -public class MessageDependentObject extends DependentObject { - -} \ No newline at end of file +/** The first DependentObject to show message. */ +public class MessageDependentObject extends DependentObject {} diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java index 58b868e8d8ee..cc3847c2905d 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java @@ -24,10 +24,5 @@ */ package com.iluwatar.compositeentity; -/** - * The second DependentObject to show message. - */ - -public class SignalDependentObject extends DependentObject { - -} \ No newline at end of file +/** The second DependentObject to show message. */ +public class SignalDependentObject extends DependentObject {} diff --git a/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java b/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java index 85b8da7d600f..4010c2912dae 100644 --- a/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java +++ b/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java @@ -28,22 +28,18 @@ import org.junit.jupiter.api.Test; -/** - * com.iluwatar.compositeentity.App running test - */ +/** com.iluwatar.compositeentity.App running test */ class AppTest { /** * Issue: Add at least one assertion to this test case. - *

- * Solution: Inserted assertion to check whether the execution of the main method in {@link + * + *

Solution: Inserted assertion to check whether the execution of the main method in {@link * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java b/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java index b90272f3b5b5..08e7899b4a58 100644 --- a/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java +++ b/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java @@ -24,13 +24,13 @@ */ package com.iluwatar.compositeentity; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + class PersistenceTest { - final static ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); + static final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); @Test void dependentObjectChangedForPersistenceTest() { diff --git a/composite-view/README.md b/composite-view/README.md index ec0faf513c27..bb2704eed94f 100644 --- a/composite-view/README.md +++ b/composite-view/README.md @@ -1,322 +1,346 @@ --- -title: Composite View +title: "Composite View Pattern in Java: Enhancing UI Consistency Across Applications" +shortTitle: Composite View +description: "Learn about the Composite View design pattern in Java, which helps in managing complex hierarchical views. This guide includes examples, applications, and benefits of using the Composite View pattern." category: Structural language: en tag: -- Enterprise Integration Pattern -- Presentation + - Abstraction + - Enterprise patterns + - Object composition + - Presentation --- -## Name -**Composite View** +## Intent of Composite View Design Pattern -## Intent -The purpose of the Composite View Pattern is to increase re-usability and flexibility when creating views for websites/webapps. -This pattern seeks to decouple the content of the page from its layout, allowing changes to be made to either the content -or layout of the page without impacting the other. This pattern also allows content to be easily reused across different views easily. +The primary goal of the Composite View design pattern is to compose objects into tree structures to represent part-whole hierarchies. This allows clients to treat individual objects and compositions of objects uniformly, simplifying the management of complex hierarchical views. -## Explanation -Real World Example -> A news site wants to display the current date and news to different users -> based on that user's preferences. The news site will substitute in different news feed -> components depending on the user's interest, defaulting to local news. +## Detailed Explanation of Composite View Pattern with Real-World Examples -In Plain Words -> Composite View Pattern is having a main view being composed of smaller subviews. -> The layout of this composite view is based on a template. A View-manager then decides which -> subviews to include in this template. +Real-world example -Wikipedia Says -> Composite views that are composed of multiple atomic subviews. Each component of -> the template may be included dynamically into the whole and the layout of the page may be managed independently of the content. -> This solution provides for the creation of a composite view based on the inclusion and substitution of -> modular dynamic and static template fragments. -> It promotes the reuse of atomic portions of the view by encouraging modular design. +> A real-world example of the Composite View design pattern is the layout of a dashboard in a web application. Consider a financial dashboard that displays various widgets such as stock charts, recent transactions, account balances, and news feeds. Each of these widgets is a separate view component that can be independently updated and managed. By using the Composite View pattern, these individual widgets are composed into a single unified dashboard view. This approach allows for easy reorganization of the dashboard, the addition of new widgets without disrupting existing ones, and consistent management of the overall layout. This hierarchical composition of views mirrors how different sections of the dashboard are treated both as individual entities and as part of a larger whole. -**Programmatic Example** +In plain words -Since this is a web development pattern, a server is required to demonstrate it. -This example uses Tomcat 10.0.13 to run the servlet, and this programmatic example will only work with Tomcat 10+. +> Composite View Pattern is having a main view being composed of smaller subviews. The layout of this composite view is based on a template. A View-manager then decides which subviews to include in this template. + +Wikipedia says + +> Composite views that are composed of multiple atomic subviews. Each component of the template may be included dynamically into the whole and the layout of the page may be managed independently of the content. This solution provides for the creation of a composite view based on the inclusion and substitution of modular dynamic and static template fragments. It promotes the reuse of atomic portions of the view by encouraging modular design. + +## Programmatic Example of Composite View Pattern in Java + +A news site wants to display the current date and news to different users based on that user's preferences. The news site will substitute in different news feed components depending on the user's interest, defaulting to local news. + +Since this is a web development pattern, a server is required to demonstrate it. This example uses Tomcat 10.0.13 to run the servlet, and this programmatic example will only work with Tomcat 10+. + +Firstly, there is `AppServlet` which is an `HttpServlet` that runs on Tomcat 10+. -Firstly there is `AppServlet` which is an `HttpServlet` that runs on Tomcat 10+. ```java public class AppServlet extends HttpServlet { - private String msgPartOne = "

This Server Doesn't Support"; - private String msgPartTwo = "Requests

\n" - + "

Use a GET request with boolean values for the following parameters

\n" - + "

'name'

\n

'bus'

\n

'sports'

\n

'sci'

\n

'world'

"; - - private String destination = "newsDisplay.jsp"; - - public AppServlet() { - - } - - @Override - public void doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); - ClientPropertiesBean reqParams = new ClientPropertiesBean(req); - req.setAttribute("properties", reqParams); - requestDispatcher.forward(req, resp); - } - - @Override - public void doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - resp.setContentType("text/html"); - PrintWriter out = resp.getWriter(); - out.println(msgPartOne + " Post " + msgPartTwo); - - } - - @Override - public void doDelete(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - resp.setContentType("text/html"); - PrintWriter out = resp.getWriter(); - out.println(msgPartOne + " Delete " + msgPartTwo); - - } - - @Override - public void doPut(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - resp.setContentType("text/html"); - PrintWriter out = resp.getWriter(); - out.println(msgPartOne + " Put " + msgPartTwo); - - } + private String msgPartOne = "

This Server Doesn't Support"; + private String msgPartTwo = "Requests

\n" + + "

Use a GET request with boolean values for the following parameters

\n" + + "

'name'

\n

'bus'

\n

'sports'

\n

'sci'

\n

'world'

"; + + private String destination = "newsDisplay.jsp"; + + public AppServlet() { + + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean reqParams = new ClientPropertiesBean(req); + req.setAttribute("properties", reqParams); + requestDispatcher.forward(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Post " + msgPartTwo); + + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Delete " + msgPartTwo); + + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Put " + msgPartTwo); + + } } ``` -This servlet is not part of the pattern, and simply forwards GET requests to the correct JSP. -PUT, POST, and DELETE requests are not supported and will simply show an error message. + +This servlet is not part of the pattern, and simply forwards GET requests to the correct JSP. PUT, POST, and DELETE requests are not supported and will simply show an error message. The view management in this example is done via a javabean class: `ClientPropertiesBean`, which stores user preferences. + ```java public class ClientPropertiesBean implements Serializable { - private static final String WORLD_PARAM = "world"; - private static final String SCIENCE_PARAM = "sci"; - private static final String SPORTS_PARAM = "sport"; - private static final String BUSINESS_PARAM = "bus"; - private static final String NAME_PARAM = "name"; - - private static final String DEFAULT_NAME = "DEFAULT_NAME"; - private boolean worldNewsInterest; - private boolean sportsInterest; - private boolean businessInterest; - private boolean scienceNewsInterest; - private String name; - - public ClientPropertiesBean() { - worldNewsInterest = true; - sportsInterest = true; - businessInterest = true; - scienceNewsInterest = true; - name = DEFAULT_NAME; - - } - - public ClientPropertiesBean(HttpServletRequest req) { - worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM)); - sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM)); - businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM)); - scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM)); - String tempName = req.getParameter(NAME_PARAM); - if (tempName == null || tempName == "") { - tempName = DEFAULT_NAME; + private static final String WORLD_PARAM = "world"; + private static final String SCIENCE_PARAM = "sci"; + private static final String SPORTS_PARAM = "sport"; + private static final String BUSINESS_PARAM = "bus"; + private static final String NAME_PARAM = "name"; + + private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private boolean worldNewsInterest; + private boolean sportsInterest; + private boolean businessInterest; + private boolean scienceNewsInterest; + private String name; + + public ClientPropertiesBean() { + worldNewsInterest = true; + sportsInterest = true; + businessInterest = true; + scienceNewsInterest = true; + name = DEFAULT_NAME; + + } + + public ClientPropertiesBean(HttpServletRequest req) { + worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM)); + sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM)); + businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM)); + scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM)); + String tempName = req.getParameter(NAME_PARAM); + if (tempName == null || tempName == "") { + tempName = DEFAULT_NAME; + } + name = tempName; } - name = tempName; - } - // getters and setters generated by Lombok + // getters and setters generated by Lombok } ``` + This javabean has a default constructor, and another that takes an `HttpServletRequest`. -This second constructor takes the request object, parses out the request parameters which contain the -user preferences for different types of news. + +This second constructor takes the request object, parses out the request parameters which contain the user preferences for different types of news. The template for the news page is in `newsDisplay.jsp` + ```html + - <%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> -

Welcome <%= propertiesBean.getName()%>

- - - - - - <% if(propertiesBean.isWorldNewsInterest()) { %> - - <% } else { %> - - <% } %> - - - - <% if(propertiesBean.isBusinessInterest()) { %> - - <% } else { %> - - <% } %> - - <% if(propertiesBean.isSportsInterest()) { %> - - <% } else { %> - - <% } %> - - - - <% if(propertiesBean.isScienceNewsInterest()) { %> - - <% } else { %> - - <% } %> - - -
<%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="businessNews.jsp"%><%@include file="localNews.jsp"%><%@include file="sportsNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="scienceNews.jsp"%><%@include file="localNews.jsp"%>
+<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> +

Welcome <%= propertiesBean.getName()%>

+ + + + + + <% if(propertiesBean.isWorldNewsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isBusinessInterest()) { %> + + <% } else { %> + + <% } %> + + <% if(propertiesBean.isSportsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isScienceNewsInterest()) { %> + + <% } else { %> + + <% } %> + + +
<%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="businessNews.jsp"%><%@include file="localNews.jsp"%><%@include file="sportsNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="scienceNews.jsp"%><%@include file="localNews.jsp"%>
``` -This JSP page is the template. It declares a table with three rows, with one component in the first row, -two components in the second row, and one component in the third row. -The scriplets in the file are part of the -view management strategy that include different atomic subviews based on the user preferences in the Javabean. +This JSP page is the template. It declares a table with three rows, with one component in the first row, two components in the second row, and one component in the third row. + +The scriplets in the file are part of the view management strategy that include different atomic subviews based on the user preferences in the Javabean. + +Here are two examples of the mock atomic subviews used in the composite: `businessNews.jsp` -Here are two examples of the mock atomic subviews used in the composite: -`businessNews.jsp` ```html + - - - - -

- Generic Business News -

- - - - - - - - - -
Stock prices up across the worldNew tech companies to invest in
Industry leaders unveil new projectPrice fluctuations and what they mean
- + + + + +

+ Generic Business News +

+ + + + + + + + + +
Stock prices up across the worldNew tech companies to invest in
Industry leaders unveil new projectPrice fluctuations and what they mean
+ ``` + `localNews.jsp` + ```html + - -
-

- Generic Local News -

-
    -
  • - Mayoral elections coming up in 2 weeks -
  • -
  • - New parking meter rates downtown coming tomorrow -
  • -
  • - Park renovations to finish by the next year -
  • -
  • - Annual marathon sign ups available online -
  • -
-
- + +
+

+ Generic Local News +

+
    +
  • + Mayoral elections coming up in 2 weeks +
  • +
  • + New parking meter rates downtown coming tomorrow +
  • +
  • + Park renovations to finish by the next year +
  • +
  • + Annual marathon sign ups available online +
  • +
+
+ ``` -The results are as such: - -1) The user has put their name as `Tammy` in the request parameters and no preferences: -![alt text](./etc/images/noparam.png) -2) The user has put their name as `Johnny` in the request parameters and has a preference for world, business, and science news: -![alt text](./etc/images/threeparams.png) -The different subviews such as `worldNews.jsp`, `businessNews.jsp`, etc. are included conditionally -based on the request parameters. +The different subviews such as `worldNews.jsp`, `businessNews.jsp`, etc. are included conditionally based on the request parameters. **How To Use** -To try this example, make sure you have Tomcat 10+ installed. -Set up your IDE to build a WAR file from the module and deploy that file to the server +To try this example, make sure you have Tomcat 10+ installed. Set up your IDE to build a WAR file from the module and deploy that file to the server IntelliJ: -Under `Run` and `edit configurations` Make sure Tomcat server is one of the run configurations. -Go to the deployment tab, and make sure there is one artifact being built called `composite-view:war exploded`. -If not present, add one. +Under `Run` and `edit configurations` Make sure Tomcat server is one of the run configurations. Go to the deployment tab, and make sure there is one artifact being built called `composite-view:war exploded`. If not present, add one. + +Ensure that the artifact is being built from the content of the `web` directory and the compilation results of the module. Point the output of the artifact to a convenient place. Run the configuration and view the landing page, follow instructions on that page to continue. + +## When to Use the Composite View Pattern in Java + +Use the Composite View design pattern when: + +* You want to represent part-whole hierarchies of objects. +* You expect that the composite structures might include any new components in the future. +* You want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly. -Ensure that the artifact is being built from the content of the `web` directory and the compilation results of the module. -Point the output of the artifact to a convenient place. Run the configuration and view the landing page, -follow instructions on that page to continue. +## Composite View Pattern Java Tutorials -## Class diagram +* [Composite View Design Pattern – Core J2EE Patterns (Dinesh on Java)](https://www.dineshonjava.com/composite-view-design-pattern/) -![alt text](./etc/composite_view.png) +## Real-World Applications of Composite View Pattern in Java -The class diagram here displays the Javabean which is the view manager. -The views are JSP's held inside the web directory. +* Graphical User Interfaces (GUIs) where widgets can contain other widgets (e.g., a window containing panels, buttons, and text fields). +* Document structures, such as the representation of tables containing rows, which in turn contain cells, all of which can be treated as elements in a unified hierarchy. -## Applicability +## Benefits and Trade-offs of Composite View Pattern -This pattern is applicable to most websites that require content to be displayed dynamically/conditionally. -If there are components that need to be re-used for multiple views, or if the project requires reusing a template, -or if it needs to include content depending on certain conditions, then this pattern is a good choice. +Benefits: -## Known uses +* High flexibility in adding new components: Since composites and leaf nodes are treated uniformly, it's easier to add new kinds of components. +* Simplified client code: Clients can treat composite structures and individual elements uniformly, reducing the complexity in client code. -Most modern websites use composite views in some shape or form, as they have templates for views and small atomic components -that are included in the page dynamically. Most modern Javascript libraries, like React, support this design pattern -with components. +Trade-offs: -## Consequences -**Pros** -* Easy to re-use components -* Change layout/content without affecting the other -* Reduce code duplication -* Code is more maintainable and modular +* Overgeneralization: Designing the system might become more complex if you make everything composite, especially if your application doesn't require it. +* Difficulty in constraint enforcement: It can be harder to restrict the components of a composite to only certain types. -**Cons** -* Overhead cost at runtime -* Slower response compared to directly embedding elements -* Increases potential for display errors +## Related Java Design Patterns -## Related patterns -* [Composite (GoF)](https://java-design-patterns.com/patterns/composite/) -* [View Helper](https://www.oracle.com/java/technologies/viewhelper.html) +* [Composite](https://java-design-patterns.com/patterns/composite/): General structural pattern that is the foundation for Composite View, used for treating individual objects and compositions uniformly. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Enhances the behavior of individual views without modifying the underlying view. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Can be used to manage memory consumption of large numbers of similar view objects. +* View Helper: Separates the view logic from business logic, aiding in the clean organization and management of view components. -## Credits -* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html) -* [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/) +## References and Credits +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3xfntGJ) +* [Patterns of Enterprise Application Architecture](https://amzn.to/49jpQG3) +* [Core J2EE Patterns - Composite View (Oracle)](https://www.oracle.com/java/technologies/composite-view.html) diff --git a/composite-view/pom.xml b/composite-view/pom.xml index bfeae14206db..f8b38b5233f6 100644 --- a/composite-view/pom.xml +++ b/composite-view/pom.xml @@ -37,19 +37,22 @@ composite-view - org.junit.jupiter - junit-jupiter-engine - test + org.slf4j + slf4j-api - junit - junit + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine test jakarta.servlet jakarta.servlet-api - 5.0.0 + 6.1.0 compile diff --git a/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java b/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java index 0cef25d2dd22..3b803a6152f3 100644 --- a/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java +++ b/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java @@ -25,22 +25,21 @@ package com.iluwatar.compositeview; import jakarta.servlet.RequestDispatcher; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; import java.io.PrintWriter; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; -/** - * A servlet object that extends HttpServlet. - * Runs on Tomcat 10 and handles Http requests - */ - +/** A servlet object that extends HttpServlet. Runs on Tomcat 10 and handles Http requests */ +@Slf4j +@NoArgsConstructor public final class AppServlet extends HttpServlet { private static final String CONTENT_TYPE = "text/html"; private String msgPartOne = "

This Server Doesn't Support"; - private String msgPartTwo = """ + private String msgPartTwo = + """ Requests

Use a GET request with boolean values for the following parameters

'name'

@@ -51,44 +50,45 @@ public final class AppServlet extends HttpServlet { private String destination = "newsDisplay.jsp"; - public AppServlet() { - - } - @Override - public void doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); - ClientPropertiesBean reqParams = new ClientPropertiesBean(req); - req.setAttribute("properties", reqParams); - requestDispatcher.forward(req, resp); + public void doGet(HttpServletRequest req, HttpServletResponse resp) { + try { + RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean reqParams = new ClientPropertiesBean(req); + req.setAttribute("properties", reqParams); + requestDispatcher.forward(req, resp); + } catch (Exception e) { + LOGGER.error("Exception occurred GET request processing ", e); + } } @Override - public void doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { + public void doPost(HttpServletRequest req, HttpServletResponse resp) { resp.setContentType(CONTENT_TYPE); try (PrintWriter out = resp.getWriter()) { out.println(msgPartOne + " Post " + msgPartTwo); + } catch (Exception e) { + LOGGER.error("Exception occurred POST request processing ", e); } - } @Override - public void doDelete(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { + public void doDelete(HttpServletRequest req, HttpServletResponse resp) { resp.setContentType(CONTENT_TYPE); try (PrintWriter out = resp.getWriter()) { out.println(msgPartOne + " Delete " + msgPartTwo); + } catch (Exception e) { + LOGGER.error("Exception occurred DELETE request processing ", e); } } @Override - public void doPut(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { + public void doPut(HttpServletRequest req, HttpServletResponse resp) { resp.setContentType(CONTENT_TYPE); try (PrintWriter out = resp.getWriter()) { out.println(msgPartOne + " Put " + msgPartTwo); + } catch (Exception e) { + LOGGER.error("Exception occurred PUT request processing ", e); } } } diff --git a/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java b/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java index 8baf2ee538ab..ecda0cb38e6e 100644 --- a/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java +++ b/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java @@ -30,15 +30,13 @@ import lombok.NoArgsConstructor; import lombok.Setter; - /** - * A Java beans class that parses a http request and stores parameters. - * Java beans used in JSP's to dynamically include elements in view. - * DEFAULT_NAME = a constant, default name to be used for the default constructor - * worldNewsInterest = whether current request has world news interest - * sportsInterest = whether current request has a sportsInterest - * businessInterest = whether current request has a businessInterest - * scienceNewsInterest = whether current request has a scienceNewsInterest + * A Java beans class that parses a http request and stores parameters. Java beans used in JSP's to + * dynamically include elements in view. DEFAULT_NAME = a constant, default name to be used for the + * default constructor worldNewsInterest = whether current request has world news interest + * sportsInterest = whether current request has a sportsInterest businessInterest = whether current + * request has a businessInterest scienceNewsInterest = whether current request has a + * scienceNewsInterest */ @Getter @Setter @@ -51,7 +49,7 @@ public class ClientPropertiesBean implements Serializable { private static final String BUSINESS_PARAM = "bus"; private static final String NAME_PARAM = "name"; - private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private static final String DEFAULT_NAME = "DEFAULT_NAME"; private boolean worldNewsInterest = true; private boolean sportsInterest = true; private boolean businessInterest = true; diff --git a/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java b/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java index c585be29ebfb..8219a56e4f39 100644 --- a/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java +++ b/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java @@ -24,88 +24,93 @@ */ package com.iluwatar.compositeview; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import java.io.PrintWriter; import java.io.StringWriter; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; - -/* Written with reference from https://stackoverflow.com/questions/5434419/how-to-test-my-servlet-using-junit -and https://stackoverflow.com/questions/50211433/servlets-unit-testing - */ +class AppServletTest { -class AppServletTest extends Mockito{ - private String msgPartOne = "

This Server Doesn't Support"; - private String msgPartTwo = """ - Requests

-

Use a GET request with boolean values for the following parameters

-

'name'

-

'bus'

-

'sports'

-

'sci'

-

'world'

"""; - private String destination = "newsDisplay.jsp"; + private final String msgPartOne = "

This Server Doesn't Support"; + private final String msgPartTwo = + """ + Requests

+

Use a GET request with boolean values for the following parameters

+

'name'

+

'bus'

+

'sports'

+

'sci'

+

'world'

"""; + private final String destination = "newsDisplay.jsp"; @Test void testDoGet() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); - RequestDispatcher mockDispatcher = Mockito.mock(RequestDispatcher.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); + RequestDispatcher mockDispatcher = mock(RequestDispatcher.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); when(mockReq.getRequestDispatcher(destination)).thenReturn(mockDispatcher); + AppServlet curServlet = new AppServlet(); curServlet.doGet(mockReq, mockResp); + verify(mockReq, times(1)).getRequestDispatcher(destination); verify(mockDispatcher).forward(mockReq, mockResp); - - } @Test void testDoPost() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); AppServlet curServlet = new AppServlet(); curServlet.doPost(mockReq, mockResp); printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Post " + msgPartTwo)); } @Test void testDoPut() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); AppServlet curServlet = new AppServlet(); curServlet.doPut(mockReq, mockResp); printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Put " + msgPartTwo)); } @Test void testDoDelete() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); AppServlet curServlet = new AppServlet(); curServlet.doDelete(mockReq, mockResp); printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Delete " + msgPartTwo)); } } diff --git a/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java b/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java index 826a0088119b..8e27a20e06f9 100644 --- a/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java +++ b/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java @@ -24,72 +24,78 @@ */ package com.iluwatar.compositeview; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import static org.junit.Assert.*; class JavaBeansTest { - @Test - void testDefaultConstructor() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertEquals("DEFAULT_NAME", newBean.getName()); - assertTrue(newBean.isBusinessInterest()); - assertTrue(newBean.isScienceNewsInterest()); - assertTrue(newBean.isSportsInterest()); - assertTrue(newBean.isWorldNewsInterest()); - - } - - @Test - void testNameGetterSetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertEquals("DEFAULT_NAME", newBean.getName()); - newBean.setName("TEST_NAME_ONE"); - assertEquals("TEST_NAME_ONE", newBean.getName()); - } - - @Test - void testBusinessSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isBusinessInterest()); - newBean.setBusinessInterest(false); - assertFalse(newBean.isBusinessInterest()); - } - - @Test - void testScienceSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isScienceNewsInterest()); - newBean.setScienceNewsInterest(false); - assertFalse(newBean.isScienceNewsInterest()); - } - - @Test - void testSportsSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isSportsInterest()); - newBean.setSportsInterest(false); - assertFalse(newBean.isSportsInterest()); - } - - @Test - void testWorldSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isWorldNewsInterest()); - newBean.setWorldNewsInterest(false); - assertFalse(newBean.isWorldNewsInterest()); - } - - @Test - void testRequestConstructor(){ - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - ClientPropertiesBean newBean = new ClientPropertiesBean((mockReq)); - assertEquals("DEFAULT_NAME", newBean.getName()); - assertFalse(newBean.isWorldNewsInterest()); - assertFalse(newBean.isBusinessInterest()); - assertFalse(newBean.isScienceNewsInterest()); - assertFalse(newBean.isSportsInterest()); - } + + @Test + void testDefaultConstructor() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertEquals("DEFAULT_NAME", newBean.getName()); + assertTrue(newBean.isBusinessInterest()); + assertTrue(newBean.isScienceNewsInterest()); + assertTrue(newBean.isSportsInterest()); + assertTrue(newBean.isWorldNewsInterest()); + } + + @Test + void testNameGetterSetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertEquals("DEFAULT_NAME", newBean.getName()); + + newBean.setName("TEST_NAME_ONE"); + assertEquals("TEST_NAME_ONE", newBean.getName()); + } + + @Test + void testBusinessSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isBusinessInterest()); + + newBean.setBusinessInterest(false); + assertFalse(newBean.isBusinessInterest()); + } + + @Test + void testScienceSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isScienceNewsInterest()); + + newBean.setScienceNewsInterest(false); + assertFalse(newBean.isScienceNewsInterest()); + } + + @Test + void testSportsSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isSportsInterest()); + + newBean.setSportsInterest(false); + assertFalse(newBean.isSportsInterest()); + } + + @Test + void testWorldSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isWorldNewsInterest()); + + newBean.setWorldNewsInterest(false); + assertFalse(newBean.isWorldNewsInterest()); + } + + @Test + void testRequestConstructor() { + HttpServletRequest mockReq = mock(HttpServletRequest.class); + ClientPropertiesBean newBean = new ClientPropertiesBean(mockReq); + + assertEquals("DEFAULT_NAME", newBean.getName()); + assertFalse(newBean.isWorldNewsInterest()); + assertFalse(newBean.isBusinessInterest()); + assertFalse(newBean.isScienceNewsInterest()); + assertFalse(newBean.isSportsInterest()); + } } diff --git a/composite-view/web/header.jsp b/composite-view/web/header.jsp index 959f1b589a06..3327c7bca654 100644 --- a/composite-view/web/header.jsp +++ b/composite-view/web/header.jsp @@ -39,11 +39,30 @@ h1 { text-align: center;} h2 { text-align: center;} h3 { text-align: center;} + nav { + text-align: center; + margin-bottom: 20px; + } + .home-link { + padding: 10px 20px; + background-color: #007bff; + color: white; + text-decoration: none; + border-radius: 5px; + } + .home-link:hover { + background-color: #0056b3; + } + + <% String todayDateStr = (new Date().toString()); %>

Today's Personalized Frontpage

-

<%=todayDateStr%>

+

<%= todayDateStr %>

diff --git a/composite-view/web/index.jsp b/composite-view/web/index.jsp index cb268a7637d4..47a8d774caad 100644 --- a/composite-view/web/index.jsp +++ b/composite-view/web/index.jsp @@ -26,21 +26,53 @@ --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + + Composite Patterns Mock News Site - - + +

Welcome To The Composite Patterns Mock News Site

-

Send a GET request to the "/news" path to see the composite view with mock news

-

Use the following parameters:

-

name: string name to be dynamically displayed

-

bus: boolean for whether you want to see the mock business news

-

world: boolean for whether you want to see the mock world news

-

sci: boolean for whether you want to see the mock world news

-

sport: boolean for whether you want to see the mock world news

- +
+

Send a GET request to the "/news" path to see the composite view with mock news

+

Use the following parameters:

+

name: string - Your name to be dynamically displayed

+

bus: boolean - Set to true to see mock business news

+

world: boolean - Set to true to see mock world news

+

sci: boolean - Set to true to see mock science news

+

sport: boolean - Set to true to see mock sports news

+

Example Request:

+

/news?name=John&bus=true&world=false&sci=true&sport=false

+

If the request fails, ensure you have the correct parameters and try again.

+
+ diff --git a/composite/README.md b/composite/README.md index e44530f9ce09..0dc740c3bc1f 100644 --- a/composite/README.md +++ b/composite/README.md @@ -1,109 +1,113 @@ --- -title: Composite +title: "Composite Pattern in Java: Building Flexible Tree Structures" +shortTitle: Composite +description: "Explore the Composite Design Pattern in Java. Learn how to compose objects into tree structures to represent part-whole hierarchies, making it easier to treat individual objects and compositions uniformly. Ideal for graphical user interfaces, file systems, and organizational structures." category: Structural language: en tag: - - Gang of Four + - Decoupling + - Gang of Four + - Object composition + - Recursion --- -## Intent +## Also known as -Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients -treat individual objects and compositions of objects uniformly. +* Object Tree +* Composite Structure -## Explanation +## Intent of Composite Design Pattern + +Compose objects into tree structures to represent part-whole hierarchies. The Composite Design Pattern lets clients treat individual objects and compositions of objects uniformly. + +## Detailed Explanation of Composite Pattern with Real-World Examples Real-world example -> Every sentence is composed of words which are in turn composed of characters. Each of these -> objects are printable and they can have something printed before or after them like sentence -> always ends with full stop and word always has space before it. +> In a real-world example, consider a company with a complex organizational structure. The company consists of various departments, each of which can contain sub-departments, and ultimately individual employees. The Composite Design Pattern can be used to represent this structure. Each department and employee are treated as a node in a tree structure, where departments can contain other departments or employees, but employees are leaf nodes with no children. This allows the company to perform operations uniformly, such as calculating total salaries or printing the organizational chart, by treating individual employees and entire departments in the same way. In plain words -> Composite pattern lets clients uniformly treat the individual objects. +> The Composite Design Pattern lets clients uniformly treat individual objects and compositions of objects. Wikipedia says -> In software engineering, the composite pattern is a partitioning design pattern. The composite -> pattern describes that a group of objects is to be treated in the same way as a single instance of -> an object. The intent of a composite is to "compose" objects into tree structures to represent -> part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects -> and compositions uniformly. +> In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly. -**Programmatic Example** +## Programmatic Example of Composite Pattern in Java -Taking our sentence example from above. Here we have the base class `LetterComposite` and the -different printable types `Letter`, `Word` and `Sentence`. +Every sentence is composed of words which are in turn composed of characters. Each of these objects are printable, and they can have something printed before or after them like sentence always ends with full stop and word always has space before it. + +Here we have the base class `LetterComposite` and the different printable types `Letter`, `Word` and `Sentence`. ```java public abstract class LetterComposite { - private final List children = new ArrayList<>(); + private final List children = new ArrayList<>(); - public void add(LetterComposite letter) { - children.add(letter); - } + public void add(LetterComposite letter) { + children.add(letter); + } - public int count() { - return children.size(); - } + public int count() { + return children.size(); + } - protected void printThisBefore() { - } + protected void printThisBefore() { + } - protected void printThisAfter() { - } + protected void printThisAfter() { + } - public void print() { - printThisBefore(); - children.forEach(LetterComposite::print); - printThisAfter(); - } + public void print() { + printThisBefore(); + children.forEach(LetterComposite::print); + printThisAfter(); + } } public class Letter extends LetterComposite { - private final char character; + private final char character; - public Letter(char c) { - this.character = c; - } + public Letter(char c) { + this.character = c; + } - @Override - protected void printThisBefore() { - System.out.print(character); - } + @Override + protected void printThisBefore() { + System.out.print(character); + } } public class Word extends LetterComposite { - public Word(List letters) { - letters.forEach(this::add); - } + public Word(List letters) { + letters.forEach(this::add); + } - public Word(char... letters) { - for (char letter : letters) { - this.add(new Letter(letter)); + public Word(char... letters) { + for (char letter : letters) { + this.add(new Letter(letter)); + } } - } - @Override - protected void printThisBefore() { - System.out.print(" "); - } + @Override + protected void printThisBefore() { + System.out.print(" "); + } } public class Sentence extends LetterComposite { - public Sentence(List words) { - words.forEach(this::add); - } + public Sentence(List words) { + words.forEach(this::add); + } - @Override - protected void printThisAfter() { - System.out.print("."); - } + @Override + protected void printThisAfter() { + System.out.print("."); + } } ``` @@ -112,38 +116,38 @@ Then we have a messenger to carry messages: ```java public class Messenger { - LetterComposite messageFromOrcs() { + LetterComposite messageFromOrcs() { - var words = List.of( - new Word('W', 'h', 'e', 'r', 'e'), - new Word('t', 'h', 'e', 'r', 'e'), - new Word('i', 's'), - new Word('a'), - new Word('w', 'h', 'i', 'p'), - new Word('t', 'h', 'e', 'r', 'e'), - new Word('i', 's'), - new Word('a'), - new Word('w', 'a', 'y') - ); + var words = List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y') + ); - return new Sentence(words); + return new Sentence(words); - } + } - LetterComposite messageFromElves() { + LetterComposite messageFromElves() { - var words = List.of( - new Word('M', 'u', 'c', 'h'), - new Word('w', 'i', 'n', 'd'), - new Word('p', 'o', 'u', 'r', 's'), - new Word('f', 'r', 'o', 'm'), - new Word('y', 'o', 'u', 'r'), - new Word('m', 'o', 'u', 't', 'h') - ); + var words = List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h') + ); - return new Sentence(words); + return new Sentence(words); - } + } } ``` @@ -151,43 +155,64 @@ public class Messenger { And then it can be used as: ```java -var messenger = new Messenger(); + public static void main(String[] args) { -LOGGER.info("Message from the orcs: "); -messenger.messageFromOrcs().print(); + var messenger = new Messenger(); -LOGGER.info("Message from the elves: "); -messenger.messageFromElves().print(); + LOGGER.info("Message from the orcs: "); + messenger.messageFromOrcs().print(); + + LOGGER.info("Message from the elves: "); + messenger.messageFromElves().print(); +} ``` The console output: ``` -Message from the orcs: +20:43:54.801 [main] INFO com.iluwatar.composite.App -- Message from the orcs: Where there is a whip there is a way. -Message from the elves: +20:43:54.803 [main] INFO com.iluwatar.composite.App -- Message from the elves: Much wind pours from your mouth. ``` -## Class diagram - -![alt text](./etc/composite.urm.png "Composite class diagram") - -## Applicability +## When to Use the Composite Pattern in Java Use the Composite pattern when * You want to represent part-whole hierarchies of objects. -* You want clients to be able to ignore the difference between compositions of objects and -individual objects. Clients will treat all objects in the composite structure uniformly. +* You want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly. -## Known uses +## Real-World Applications of Composite Pattern in Java +* Graphical user interfaces where components can contain other components (e.g., panels containing buttons, labels, other panels). +* File system representations where directories can contain files and other directories. +* Organizational structures where a department can contain sub-departments and employees. * [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) and [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) * [Apache Wicket](https://github.com/apache/wicket) component tree, see [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) and [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) -## Credits +## Benefits and Trade-offs of Composite Pattern + +Benefits: + +* Simplifies client code, as it can treat composite structures and individual objects uniformly. +* Makes it easier to add new kinds of components, as existing code doesn't need to be changed. + +Trade-offs: + +* Can make the design overly general. It might be difficult to restrict the components of a composite. +* Can make it harder to restrict the types of components in a composite. + +## Related Java Design Patterns + +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Composite can use Flyweight to share component instances among several composites. +* [Iterator](https://java-design-patterns.com/patterns/iterator/): Can be used to traverse Composite structures. +* [Visitor](https://java-design-patterns.com/patterns/visitor/): Can apply an operation over a Composite structure. + +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3xoLAmi) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3vBKXWb) diff --git a/composite/pom.xml b/composite/pom.xml index d9893de052ea..7beb910d901e 100644 --- a/composite/pom.xml +++ b/composite/pom.xml @@ -34,6 +34,14 @@ composite + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/composite/src/main/java/com/iluwatar/composite/App.java b/composite/src/main/java/com/iluwatar/composite/App.java index d890328de3ec..366ceffa45e9 100644 --- a/composite/src/main/java/com/iluwatar/composite/App.java +++ b/composite/src/main/java/com/iluwatar/composite/App.java @@ -35,7 +35,6 @@ * *

In this example we have sentences composed of words composed of letters. All of the objects * can be treated through the same interface ({@link LetterComposite}). - * */ @Slf4j public class App { diff --git a/composite/src/main/java/com/iluwatar/composite/Letter.java b/composite/src/main/java/com/iluwatar/composite/Letter.java index 152299d3cc64..4adcba864643 100644 --- a/composite/src/main/java/com/iluwatar/composite/Letter.java +++ b/composite/src/main/java/com/iluwatar/composite/Letter.java @@ -26,9 +26,7 @@ import lombok.RequiredArgsConstructor; -/** - * Letter. - */ +/** Letter. */ @RequiredArgsConstructor public class Letter extends LetterComposite { diff --git a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java index 2b11d1cc4202..a5b0d1cc3d55 100644 --- a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java +++ b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java @@ -27,9 +27,7 @@ import java.util.ArrayList; import java.util.List; -/** - * Composite interface. - */ +/** Composite interface. */ public abstract class LetterComposite { private final List children = new ArrayList<>(); @@ -42,15 +40,11 @@ public int count() { return children.size(); } - protected void printThisBefore() { - } + protected void printThisBefore() {} - protected void printThisAfter() { - } + protected void printThisAfter() {} - /** - * Print. - */ + /** Print. */ public void print() { printThisBefore(); children.forEach(LetterComposite::print); diff --git a/composite/src/main/java/com/iluwatar/composite/Messenger.java b/composite/src/main/java/com/iluwatar/composite/Messenger.java index 011f91b82a89..3af972bc6ca0 100644 --- a/composite/src/main/java/com/iluwatar/composite/Messenger.java +++ b/composite/src/main/java/com/iluwatar/composite/Messenger.java @@ -26,42 +26,37 @@ import java.util.List; -/** - * Messenger. - */ +/** Messenger. */ public class Messenger { LetterComposite messageFromOrcs() { - var words = List.of( - new Word('W', 'h', 'e', 'r', 'e'), - new Word('t', 'h', 'e', 'r', 'e'), - new Word('i', 's'), - new Word('a'), - new Word('w', 'h', 'i', 'p'), - new Word('t', 'h', 'e', 'r', 'e'), - new Word('i', 's'), - new Word('a'), - new Word('w', 'a', 'y') - ); + var words = + List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y')); return new Sentence(words); - } LetterComposite messageFromElves() { - var words = List.of( - new Word('M', 'u', 'c', 'h'), - new Word('w', 'i', 'n', 'd'), - new Word('p', 'o', 'u', 'r', 's'), - new Word('f', 'r', 'o', 'm'), - new Word('y', 'o', 'u', 'r'), - new Word('m', 'o', 'u', 't', 'h') - ); + var words = + List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h')); return new Sentence(words); - } - } diff --git a/composite/src/main/java/com/iluwatar/composite/Sentence.java b/composite/src/main/java/com/iluwatar/composite/Sentence.java index 95143b83f020..448d2096cc5d 100644 --- a/composite/src/main/java/com/iluwatar/composite/Sentence.java +++ b/composite/src/main/java/com/iluwatar/composite/Sentence.java @@ -26,14 +26,10 @@ import java.util.List; -/** - * Sentence. - */ +/** Sentence. */ public class Sentence extends LetterComposite { - /** - * Constructor. - */ + /** Constructor. */ public Sentence(List words) { words.forEach(this::add); } diff --git a/composite/src/main/java/com/iluwatar/composite/Word.java b/composite/src/main/java/com/iluwatar/composite/Word.java index e84bd0791445..5b0f82049d9c 100644 --- a/composite/src/main/java/com/iluwatar/composite/Word.java +++ b/composite/src/main/java/com/iluwatar/composite/Word.java @@ -26,20 +26,17 @@ import java.util.List; -/** - * Word. - */ +/** Word. */ public class Word extends LetterComposite { - /** - * Constructor. - */ + /** Constructor. */ public Word(List letters) { letters.forEach(this::add); } /** * Constructor. + * * @param letters to include */ public Word(char... letters) { diff --git a/composite/src/test/java/com/iluwatar/composite/AppTest.java b/composite/src/test/java/com/iluwatar/composite/AppTest.java index a8cd66afeb57..b1c79d7357b1 100644 --- a/composite/src/test/java/com/iluwatar/composite/AppTest.java +++ b/composite/src/test/java/com/iluwatar/composite/AppTest.java @@ -27,20 +27,17 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - Assertions.assertDoesNotThrow(() -> App.main(new String[]{})); + Assertions.assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/composite/src/test/java/com/iluwatar/composite/MessengerTest.java b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java index 53412bfcdb5b..b22b94cc6b89 100644 --- a/composite/src/test/java/com/iluwatar/composite/MessengerTest.java +++ b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java @@ -33,21 +33,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/11/15 - 8:12 PM - * - * @author Jeroen Meulemeester - */ +/** MessengerTest */ class MessengerTest { - /** - * The buffer used to capture every write to {@link System#out} - */ + /** The buffer used to capture every write to {@link System#out} */ private ByteArrayOutputStream stdOutBuffer = new ByteArrayOutputStream(); - /** - * Keep the original std-out so it can be restored after the test - */ + /** Keep the original std-out so it can be restored after the test */ private final PrintStream realStdOut = System.out; /** @@ -59,43 +51,31 @@ void setUp() { System.setOut(new PrintStream(stdOutBuffer)); } - /** - * Removed the mocked std-out {@link PrintStream} again from the {@link System} class - */ + /** Removed the mocked std-out {@link PrintStream} again from the {@link System} class */ @AfterEach void tearDown() { System.setOut(realStdOut); } - /** - * Test the message from the orcs - */ + /** Test the message from the orcs */ @Test void testMessageFromOrcs() { final var messenger = new Messenger(); - testMessage( - messenger.messageFromOrcs(), - "Where there is a whip there is a way." - ); + testMessage(messenger.messageFromOrcs(), "Where there is a whip there is a way."); } - /** - * Test the message from the elves - */ + /** Test the message from the elves */ @Test void testMessageFromElves() { final var messenger = new Messenger(); - testMessage( - messenger.messageFromElves(), - "Much wind pours from your mouth." - ); + testMessage(messenger.messageFromElves(), "Much wind pours from your mouth."); } /** * Test if the given composed message matches the expected message * * @param composedMessage The composed message, received from the messenger - * @param message The expected message + * @param message The expected message */ private void testMessage(final LetterComposite composedMessage, final String message) { // Test is the composed message has the correct number of words @@ -109,5 +89,4 @@ private void testMessage(final LetterComposite composedMessage, final String mes // ... and verify if the message matches with the expected one assertEquals(message, new String(this.stdOutBuffer.toByteArray()).trim()); } - } diff --git a/context-object/README.md b/context-object/README.md index 15848371bccc..831abb7c7323 100644 --- a/context-object/README.md +++ b/context-object/README.md @@ -1,78 +1,59 @@ --- -title: Context object -category: Creational +title: "Context Object Pattern in Java: Simplifying Access to Contextual Data" +shortTitle: Context Object +description: "Learn about the Context Object pattern in Java Design Patterns. Encapsulate state and behaviors relevant to users or requests to decouple application components from environmental complexities. Explore real-world examples, benefits, and implementation tips." +category: Behavioral language: en tags: -- Data access + - Context + - Decoupling + - Encapsulation + - Session management --- -## Name / classification - -Context Object - ## Also known as -Context, Encapsulate Context +* Context +* Context Encapsulation +* Context Holder +* Encapsulate Context -## Intent +## Intent of Context Object Design Pattern -Decouple data from protocol-specific classes and store the scoped data in an object independent -of the underlying protocol technology. +Encapsulate the context (state and behaviors) relevant to the user or the request being processed to decouple Java application components from the complexities of the environment. This design pattern helps in managing the application's context efficiently. -## Explanation +## Detailed Explanation of Context Object Pattern with Real-World Examples Real-world example -> This application has different layers labelled A, B and C with each extracting specific information -> from a similar context for further use in the software. Passing down each pieces of information -> individually would be inefficient, a method to efficiently store and pass information is needed. +> Imagine a busy airport where multiple services need to access and share passenger information throughout their journey. Instead of each service requesting and passing passenger details separately, the airport uses a "Passenger Context Object." This context object holds all relevant passenger information, such as identity, flight details, and preferences. Various services like check-in, security, boarding, and customer service access this context object to get or update passenger data as needed. This approach ensures consistent and efficient information handling without tightly coupling the services, similar to how the Context Object design pattern works in software. In plain words -> Create an object and store the data there and pass this object to where it is needed. +> Create an object to store and manage context data, and pass this context object wherever needed in the Java application, ensuring decoupled and cleaner code. [Core J2EE Patterns](http://corej2eepatterns.com/ContextObject.htm) says -> Use a Context Object to encapsulate state in a protocol-independent way to be shared throughout your application. +> Use a Context Object to encapsulate state in a protocol-independent way to be shared throughout your application. + +## Programmatic Example of Context Object in Java -**Programmatic Example** +In a multi-layered Java application, different layers such as A, B, and C extract specific information from a shared context. Passing each piece of information individually is inefficient. The Context Object pattern efficiently stores and passes this information, improving the overall performance and maintainability of the Java application. -We define what data a service context object contains. +Define the data that the `ServiceContext` object contains. ```Java +@Getter +@Setter public class ServiceContext { - String ACCOUNT_SERVICE, SESSION_SERVICE, SEARCH_SERVICE; - - public void setACCOUNT_SERVICE(String ACCOUNT_SERVICE) { - this.ACCOUNT_SERVICE = ACCOUNT_SERVICE; - } - - public void setSESSION_SERVICE(String SESSION_SERVICE) { - this.SESSION_SERVICE = SESSION_SERVICE; - } - - public void setSEARCH_SERVICE(String SEARCH_SERVICE) { - this.SEARCH_SERVICE = SEARCH_SERVICE; - } - - public String getACCOUNT_SERVICE() { - return ACCOUNT_SERVICE; - } - - public String getSESSION_SERVICE() { - return SESSION_SERVICE; - } - - public String getSEARCH_SERVICE() { - return SEARCH_SERVICE; - } - - public String toString() { return ACCOUNT_SERVICE + " " + SESSION_SERVICE + " " + SEARCH_SERVICE;} + String accountService; + String sessionService; + String searchService; } ``` -Create an interface used in parts of the application for context objects to be created. +Create interface `ServiceContextFactory` to be used in parts of the application for context objects to be created. ```Java public class ServiceContextFactory { @@ -83,10 +64,10 @@ public class ServiceContextFactory { } ``` -Instantiate the context object in the first layer and the adjoining layer upcalls the context in the current layer, which -then further structures the object. +Instantiate the context object in the first layer. The adjoining layer calls the context in the current layer, which then further structures the object. ```Java +@Getter public class LayerA { private static ServiceContext context; @@ -95,15 +76,14 @@ public class LayerA { context = ServiceContextFactory.createContext(); } - public static ServiceContext getContext() { - return context; - } - public void addAccountInfo(String accountService) { context.setACCOUNT_SERVICE(accountService); } } +``` +```Java +@Getter public class LayerB { private static ServiceContext context; @@ -112,15 +92,14 @@ public class LayerB { this.context = layerA.getContext(); } - public static ServiceContext getContext() { - return context; - } - public void addSessionInfo(String sessionService) { context.setSESSION_SERVICE(sessionService); } } +``` +```Java +@Getter public class LayerC { public static ServiceContext context; @@ -129,57 +108,90 @@ public class LayerC { this.context = layerB.getContext(); } - public static ServiceContext getContext() { - return context; - } - public void addSearchInfo(String searchService) { context.setSEARCH_SERVICE(searchService); } } ``` + Here is the context object and layers in action. ```Java -var layerA = new LayerA(); -layerA.addAccountInfo(SERVICE); -LOGGER.info("Context = {}",layerA.getContext()); -var layerB = new LayerB(layerA); -layerB.addSessionInfo(SERVICE); -LOGGER.info("Context = {}",layerB.getContext()); -var layerC = new LayerC(layerB); -layerC.addSearchInfo(SERVICE); -LOGGER.info("Context = {}",layerC.getContext()); -``` +@Slf4j +public class App { -Program output: + private static final String SERVICE = "SERVICE"; -```Java -Context = SERVICE null null -Context = SERVICE SERVICE null -Context = SERVICE SERVICE SERVICE + public static void main(String[] args) { + //Initiate first layer and add service information into context + var layerA = new LayerA(); + layerA.addAccountInfo(SERVICE); + + logContext(layerA.getContext()); + + //Initiate second layer and preserving information retrieved in first layer through passing context object + var layerB = new LayerB(layerA); + layerB.addSessionInfo(SERVICE); + + logContext(layerB.getContext()); + + //Initiate third layer and preserving information retrieved in first and second layer through passing context object + var layerC = new LayerC(layerB); + layerC.addSearchInfo(SERVICE); + + logContext(layerC.getContext()); + } + + private static void logContext(ServiceContext context) { + LOGGER.info("Context = {}", context); + } +} ``` -## Class diagram +Program output: -![alt text](./etc/context-object.png "Context object") +``` +08:15:32.134 [main] INFO com.iluwatar.context.object.App -- Context = com.iluwatar.context.object.ServiceContext@5577140b +08:15:32.136 [main] INFO com.iluwatar.context.object.App -- Context = com.iluwatar.context.object.ServiceContext@5577140b +08:15:32.137 [main] INFO com.iluwatar.context.object.App -- Context = com.iluwatar.context.object.ServiceContext@5577140b +``` -## Application +## When to Use the Context Object Pattern in Java -Use the Context Object pattern for: +* When there is a need to abstract and encapsulate context information in a Java application to avoid cluttering the business logic with environment-specific code. This is especially useful in web applications for encapsulating request-specific information and in distributed systems for managing user preferences and security credentials. +* In web applications, to encapsulate request-specific information and make it easily accessible throughout the application without passing it explicitly between functions or components. +* In distributed systems, to encapsulate contextual information about the task being performed, user preferences, or security credentials, facilitating their propagation across different components and services. -* Sharing information across different system layers. -* Decoupling software data from protocol-specific contexts. -* Exposing only the relevant API's within the context. +## Real-World Applications of Context Object Pattern in Java -## Known uses +* Web application frameworks often use the Context Object pattern to encapsulate HTTP request and response objects, session information, and other request-specific data. Enterprise Java applications leverage this pattern to manage and propagate transactional information, security credentials, and user-specific settings across different layers and services. +* Enterprise applications use Context Objects to manage and propagate transactional information, security credentials, and user-specific settings across different layers and services. * [Spring: ApplicationContext](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html) * [Oracle: SecurityContext](https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/SecurityContext.html) * [Oracle: ServletContext](https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html) -## Credits +## Benefits and Trade-offs of Context Object Pattern + +Benefits: + +* Decoupling: Components and services are decoupled from the specificities of the execution environment, enhancing modularity and maintainability. +* Centralization: Contextual information is centralized in one place, making it easier to manage, access, and debug. +* Flexibility: The pattern allows for flexible and dynamic context management, which can adapt to changes in the environment or requirements. + +Trade-offs: + +* Overhead: Introducing a Context Object can add overhead in terms of performance, especially if not implemented efficiently. +* Complexity: If the Context Object is not well-designed, it can become a bloated and complex monolith, difficult to manage and understand. + +## Related Java Design Patterns + +* [Singleton](https://java-design-patterns.com/patterns/singleton/): The Context Object is often implemented as a Singleton to ensure a global point of access. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Context Objects can use Strategies to adapt their behavior based on the context they encapsulate. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used to dynamically add responsibilities to the Context Object. + +## References and Credits * [Core J2EE Design Patterns](https://amzn.to/3IhcY9w) -* [Core J2EE Design Patterns website - Context Object](http://corej2eepatterns.com/ContextObject.htm) -* [Allan Kelly - The Encapsulate Context Pattern](https://accu.org/journals/overload/12/63/kelly_246/) -* [Arvid S. Krishna et al. - Context Object](https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf) +* [Context Object (Core J2EE Patterns)](http://corej2eepatterns.com/ContextObject.htm) +* [The Encapsulate Context Pattern (Accu)](https://accu.org/journals/overload/12/63/kelly_246/) +* [Context Object - A Design Pattern for Efficient Information Sharing across Multiple System Layers (Arvid S. Krishna et al.)](https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf) diff --git a/context-object/pom.xml b/context-object/pom.xml index 4eb0237a6263..82f8862b6905 100644 --- a/context-object/pom.xml +++ b/context-object/pom.xml @@ -34,6 +34,14 @@ context-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/context-object/src/main/java/com/iluwatar/context/object/App.java b/context-object/src/main/java/com/iluwatar/context/object/App.java index 4147357d29aa..f0fc505c31e3 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/App.java +++ b/context-object/src/main/java/com/iluwatar/context/object/App.java @@ -27,12 +27,14 @@ import lombok.extern.slf4j.Slf4j; /** - * In the context object pattern, information and data from underlying protocol-specific classes/systems is decoupled - * and stored into a protocol-independent object in an organised format. The pattern ensures the data contained within - * the context object can be shared and further structured between different layers of a software system. + * In the context object pattern, information and data from underlying protocol-specific + * classes/systems is decoupled and stored into a protocol-independent object in an organised + * format. The pattern ensures the data contained within the context object can be shared and + * further structured between different layers of a software system. * - *

In this example we show how a context object {@link ServiceContext} can be initiated, edited and passed/retrieved - * in different layers of the program ({@link LayerA}, {@link LayerB}, {@link LayerC}) through use of static methods.

+ *

In this example we show how a context object {@link ServiceContext} can be initiated, edited + * and passed/retrieved in different layers of the program ({@link LayerA}, {@link LayerB}, {@link + * LayerC}) through use of static methods. */ @Slf4j public class App { @@ -45,22 +47,28 @@ public class App { * @param args command line args */ public static void main(String[] args) { - //Initiate first layer and add service information into context + // Initiate first layer and add service information into context var layerA = new LayerA(); layerA.addAccountInfo(SERVICE); - LOGGER.info("Context = {}", layerA.getContext()); + logContext(layerA.getContext()); - //Initiate second layer and preserving information retrieved in first layer through passing context object + // Initiate second layer and preserving information retrieved in first layer through passing + // context object var layerB = new LayerB(layerA); layerB.addSessionInfo(SERVICE); - LOGGER.info("Context = {}", layerB.getContext()); + logContext(layerB.getContext()); - //Initiate third layer and preserving information retrieved in first and second layer through passing context object + // Initiate third layer and preserving information retrieved in first and second layer through + // passing context object var layerC = new LayerC(layerB); layerC.addSearchInfo(SERVICE); - LOGGER.info("Context = {}", layerC.getContext()); + logContext(layerC.getContext()); + } + + private static void logContext(ServiceContext context) { + LOGGER.info("Context = {}", context); } } diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerA.java b/context-object/src/main/java/com/iluwatar/context/object/LayerA.java index 87faec2a82f0..4d37f079052e 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/LayerA.java +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerA.java @@ -26,9 +26,7 @@ import lombok.Getter; -/** - * Layer A in the context object pattern. - */ +/** Layer A in the context object pattern. */ @Getter public class LayerA { diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerB.java b/context-object/src/main/java/com/iluwatar/context/object/LayerB.java index d4991f17f2f5..a67aba9bd5f7 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/LayerB.java +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerB.java @@ -26,9 +26,7 @@ import lombok.Getter; -/** - * Layer B in the context object pattern. - */ +/** Layer B in the context object pattern. */ @Getter public class LayerB { diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerC.java b/context-object/src/main/java/com/iluwatar/context/object/LayerC.java index 78c6dcec50f2..d33a62998030 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/LayerC.java +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerC.java @@ -26,9 +26,7 @@ import lombok.Getter; -/** - * Layer C in the context object pattern. - */ +/** Layer C in the context object pattern. */ @Getter public class LayerC { diff --git a/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java b/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java index dcd4e1450602..3de91a20e672 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java +++ b/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java @@ -26,11 +26,8 @@ import lombok.Getter; import lombok.Setter; -import lombok.ToString; -/** - * Where context objects are defined. - */ +/** Where context objects are defined. */ @Getter @Setter public class ServiceContext { diff --git a/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java b/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java index d7094a0c080b..bee65e828914 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java +++ b/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java @@ -24,9 +24,7 @@ */ package com.iluwatar.context.object; -/** - * An interface to create context objects passed through layers. - */ +/** An interface to create context objects passed through layers. */ public class ServiceContextFactory { public static ServiceContext createContext() { diff --git a/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java b/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java index 9c5d72fde8ba..2cb5e901a80e 100644 --- a/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java +++ b/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java @@ -24,16 +24,14 @@ */ package com.iluwatar.contect.object; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.context.object.App; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - public class AppTest { - /** - * Test example app runs without error. - */ + /** Test example app runs without error. */ @Test void shouldExecuteWithoutException() { assertDoesNotThrow(() -> App.main(new String[] {})); diff --git a/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java b/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java index 7fdbb68b4dc5..fdfd56af7948 100644 --- a/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java +++ b/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java @@ -24,6 +24,11 @@ */ package com.iluwatar.contect.object; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + import com.iluwatar.context.object.LayerA; import com.iluwatar.context.object.LayerB; import com.iluwatar.context.object.LayerC; @@ -31,16 +36,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -/** - * Date: 10/24/2022 - 3:18 - * - * @author Chak Chan - */ +/** ServiceContextTest */ public class ServiceContextTest { private static final String SERVICE = "SERVICE"; @@ -83,27 +79,23 @@ void testLayerContexts() { assertAll( () -> assertNull(layerA.getContext().getAccountService()), () -> assertNull(layerA.getContext().getSearchService()), - () -> assertNull(layerA.getContext().getSessionService()) - ); + () -> assertNull(layerA.getContext().getSessionService())); layerA.addAccountInfo(SERVICE); assertAll( () -> assertEquals(SERVICE, layerA.getContext().getAccountService()), () -> assertNull(layerA.getContext().getSearchService()), - () -> assertNull(layerA.getContext().getSessionService()) - ); + () -> assertNull(layerA.getContext().getSessionService())); var layerB = new LayerB(layerA); layerB.addSessionInfo(SERVICE); assertAll( () -> assertEquals(SERVICE, layerB.getContext().getAccountService()), () -> assertEquals(SERVICE, layerB.getContext().getSessionService()), - () -> assertNull(layerB.getContext().getSearchService()) - ); + () -> assertNull(layerB.getContext().getSearchService())); var layerC = new LayerC(layerB); layerC.addSearchInfo(SERVICE); assertAll( () -> assertEquals(SERVICE, layerC.getContext().getAccountService()), () -> assertEquals(SERVICE, layerC.getContext().getSearchService()), - () -> assertEquals(SERVICE, layerC.getContext().getSessionService()) - ); + () -> assertEquals(SERVICE, layerC.getContext().getSessionService())); } } diff --git a/converter/README.md b/converter/README.md index f5d9921d5115..adf29383d8a3 100644 --- a/converter/README.md +++ b/converter/README.md @@ -1,106 +1,170 @@ --- -title: Converter -category: Creational +title: "Converter Pattern in Java: Streamlining Data Conversion Across Layers" +shortTitle: Converter +description: "Discover the benefits and implementation of the Converter Pattern in Java. Learn how to achieve seamless bidirectional conversion between different data formats, promoting clean code and flexibility in your applications." +category: Structural language: en tag: - - Decoupling + - Compatibility + - Data transformation + - Decoupling + - Interface + - Object mapping + - Wrapping --- -## Intent +## Also known as -The purpose of the Converter pattern is to provide a generic, common way of bidirectional -conversion between corresponding types, allowing a clean implementation in which the types do not -need to be aware of each other. Moreover, the Converter pattern introduces bidirectional collection -mapping, reducing a boilerplate code to minimum. +* Mapper +* Translator -## Explanation +## Intent of Converter Design Pattern -Real world example +The purpose of the Converter Pattern is to provide a generic, systematic way of bidirectional conversion between corresponding data types. This allows for a clean, decoupled implementation where types are unaware of each other. Additionally, the Converter pattern supports bidirectional collection mapping, minimizing boilerplate code. -> In real world applications it is often the case that database layer consists of entities that need -> to be mapped into DTOs for use on the business logic layer. Similar mapping is done for -> potentially huge amount of classes and we need a generic way to achieve this. +## Detailed Explanation of Converter Pattern with Real-World Examples + +Real-world example + +> In a real-world scenario, consider a library system that interacts with a third-party book database. The library uses an internal book format, while the third-party database uses a different format. By employing the Converter Pattern, a converter class can transform the third-party book data into the library's format and vice versa. This ensures seamless integration without altering the internal structures of either system. In plain words -> Converter pattern makes it easy to map instances of one class into instances of another class. +> The Converter Pattern simplifies mapping instances of one class to instances of another class, ensuring consistent and clean data transformation. + +## Programmatic Example of Converter Pattern in Java -**Programmatic Example** +In applications, it's common for the database layer to have entities that need mapping to DTOs (Data Transfer Objects) for business logic. This mapping often involves many classes, necessitating a generic solution. -We need a generic solution for the mapping problem. To achieve this, let's introduce a generic -converter. +We introduce a generic `Converter` class: ```java public class Converter { - private final Function fromDto; - private final Function fromEntity; + private final Function fromDto; + private final Function fromEntity; - public Converter(final Function fromDto, final Function fromEntity) { - this.fromDto = fromDto; - this.fromEntity = fromEntity; - } + public Converter(final Function fromDto, final Function fromEntity) { + this.fromDto = fromDto; + this.fromEntity = fromEntity; + } - public final U convertFromDto(final T dto) { - return fromDto.apply(dto); - } + public final U convertFromDto(final T dto) { + return fromDto.apply(dto); + } - public final T convertFromEntity(final U entity) { - return fromEntity.apply(entity); - } + public final T convertFromEntity(final U entity) { + return fromEntity.apply(entity); + } - public final List createFromDtos(final Collection dtos) { - return dtos.stream().map(this::convertFromDto).collect(Collectors.toList()); - } + public final List createFromDtos(final Collection dtos) { + return dtos.stream().map(this::convertFromDto).collect(Collectors.toList()); + } - public final List createFromEntities(final Collection entities) { - return entities.stream().map(this::convertFromEntity).collect(Collectors.toList()); - } + public final List createFromEntities(final Collection entities) { + return entities.stream().map(this::convertFromEntity).collect(Collectors.toList()); + } } ``` -The specialized converters inherit from this base class as follows. +Specialized converters inherit from this base class: ```java public class UserConverter extends Converter { - public UserConverter() { - super(UserConverter::convertToEntity, UserConverter::convertToDto); - } + public UserConverter() { + super(UserConverter::convertToEntity, UserConverter::convertToDto); + } - private static UserDto convertToDto(User user) { - return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId()); - } - - private static User convertToEntity(UserDto dto) { - return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail()); - } + private static UserDto convertToDto(User user) { + return new UserDto(user.firstName(), user.lastName(), user.active(), user.userId()); + } + private static User convertToEntity(UserDto dto) { + return new User(dto.firstName(), dto.lastName(), dto.active(), dto.email()); + } } ``` -Now mapping between `User` and `UserDto` becomes trivial. +Mapping between `User` and `UserDto` becomes straightforward: ```java -var userConverter = new UserConverter(); -var dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); -var user = userConverter.convertFromDto(dtoUser); + public static void main(String[] args) { + Converter userConverter = new UserConverter(); + + UserDto dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); + User user = userConverter.convertFromDto(dtoUser); + LOGGER.info("Entity converted from DTO: {}", user); + + var users = List.of( + new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), + new User("Kate", "Smith", true, "if0243") + ); + LOGGER.info("Domain entities:"); + users.stream().map(User::toString).forEach(LOGGER::info); + + LOGGER.info("DTO entities converted from domain:"); + List dtoEntities = userConverter.createFromEntities(users); + dtoEntities.stream().map(UserDto::toString).forEach(LOGGER::info); +} ``` -## Class diagram +Program output: -![alt text](./etc/converter.png "Converter Pattern") +``` +08:28:27.019 [main] INFO com.iluwatar.converter.App -- Entity converted from DTO: User[firstName=John, lastName=Doe, active=true, userId=whatever[at]wherever.com] +08:28:27.035 [main] INFO com.iluwatar.converter.App -- Domain entities: +08:28:27.035 [main] INFO com.iluwatar.converter.App -- User[firstName=Camile, lastName=Tough, active=false, userId=124sad] +08:28:27.035 [main] INFO com.iluwatar.converter.App -- User[firstName=Marti, lastName=Luther, active=true, userId=42309fd] +08:28:27.035 [main] INFO com.iluwatar.converter.App -- User[firstName=Kate, lastName=Smith, active=true, userId=if0243] +08:28:27.036 [main] INFO com.iluwatar.converter.App -- DTO entities converted from domain: +08:28:27.037 [main] INFO com.iluwatar.converter.App -- UserDto[firstName=Camile, lastName=Tough, active=false, email=124sad] +08:28:27.037 [main] INFO com.iluwatar.converter.App -- UserDto[firstName=Marti, lastName=Luther, active=true, email=42309fd] +08:28:27.037 [main] INFO com.iluwatar.converter.App -- UserDto[firstName=Kate, lastName=Smith, active=true, email=if0243] +``` -## Applicability +## When to Use the Converter Pattern in Java Use the Converter Pattern in the following situations: -* When you have types that logically correspond with each other and you need to convert entities -between them. -* When you want to provide different ways of types conversions depending on the context. -* Whenever you introduce a DTO (Data transfer object), you will probably need to convert it into the -domain equivalence. +* When there are types that logically correspond with each other, and there is a need to convert between them. +* In applications that interact with external systems or services that require data in a specific format. +* For legacy systems integration where data models differ significantly from newer systems. +* When aiming to encapsulate conversion logic to promote single responsibility and cleaner code. + +## Converter Pattern Java Tutorials + +* [Converter Pattern in Java 8 (Boldare)](http://www.xsolve.pl/blog/converter-pattern-in-java-8/) + +## Real-World Applications of Converter Pattern in Java + +* Data Transfer Objects (DTOs) conversions in multi-layered applications. +* Adapting third-party data structures or API responses to internal models. +* ORM (Object-Relational Mapping) frameworks for mapping between database records and domain objects. +* Microservices architecture for data exchange between different services. + +## Benefits and Trade-offs of Converter Pattern + +Benefits: + +* Separation of Concerns: Encapsulates conversion logic in a single component, keeping the rest of the application unaware of the conversion details. +* Reusability: Converter components can be reused across the application or even in different applications. +* Flexibility: Makes it easy to add new conversions without impacting existing code, adhering to the [Open/Closed Principle](https://java-design-patterns.com/principles/#open-closed-principle). +* Interoperability: Facilitates communication between different systems or application layers by translating data formats. + +Trade-offs: + +* Overhead: Introducing converters can add complexity and potential performance overhead, especially in systems with numerous data formats. +* Duplication: There's a risk of duplicating model definitions if not carefully managed, leading to increased maintenance. + +## Related Java Design Patterns + +* [Adapter](https://java-design-patterns.com/patterns/adapter/): Similar in intent to adapting interfaces, but Converter focuses on data models. +* [Facade](https://java-design-patterns.com/patterns/facade/): Provides a simplified interface to a complex system, which might involve data conversion. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Converters can use different strategies for conversion, especially when multiple formats are involved. -## Credits +## References and Credits -* [Converter Pattern in Java 8](http://www.xsolve.pl/blog/converter-pattern-in-java-8/) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) diff --git a/converter/pom.xml b/converter/pom.xml index c26e68dd1c2b..525237ed17f5 100644 --- a/converter/pom.xml +++ b/converter/pom.xml @@ -34,6 +34,14 @@ converter 4.0.0 + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/converter/src/main/java/com/iluwatar/converter/App.java b/converter/src/main/java/com/iluwatar/converter/App.java index 49bcdbdf66a1..100913501018 100644 --- a/converter/src/main/java/com/iluwatar/converter/App.java +++ b/converter/src/main/java/com/iluwatar/converter/App.java @@ -48,17 +48,16 @@ public static void main(String[] args) { User user = userConverter.convertFromDto(dtoUser); LOGGER.info("Entity converted from DTO: {}", user); - var users = List.of( - new User("Camile", "Tough", false, "124sad"), - new User("Marti", "Luther", true, "42309fd"), - new User("Kate", "Smith", true, "if0243") - ); + var users = + List.of( + new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), + new User("Kate", "Smith", true, "if0243")); LOGGER.info("Domain entities:"); users.stream().map(User::toString).forEach(LOGGER::info); LOGGER.info("DTO entities converted from domain:"); List dtoEntities = userConverter.createFromEntities(users); dtoEntities.stream().map(UserDto::toString).forEach(LOGGER::info); - } } diff --git a/converter/src/main/java/com/iluwatar/converter/Converter.java b/converter/src/main/java/com/iluwatar/converter/Converter.java index bd7a73f98897..374b2ce5da7e 100644 --- a/converter/src/main/java/com/iluwatar/converter/Converter.java +++ b/converter/src/main/java/com/iluwatar/converter/Converter.java @@ -86,5 +86,4 @@ public final List createFromDtos(final Collection dtos) { public final List createFromEntities(final Collection entities) { return entities.stream().map(this::convertFromEntity).toList(); } - } diff --git a/converter/src/main/java/com/iluwatar/converter/User.java b/converter/src/main/java/com/iluwatar/converter/User.java index ab6a878cb16b..9f68047f1dcc 100644 --- a/converter/src/main/java/com/iluwatar/converter/User.java +++ b/converter/src/main/java/com/iluwatar/converter/User.java @@ -24,21 +24,5 @@ */ package com.iluwatar.converter; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -/** - * User class. - */ -@ToString -@EqualsAndHashCode -@Getter -@RequiredArgsConstructor -public class User { - private final String firstName; - private final String lastName; - private final boolean active; - private final String userId; -} +/** User record. */ +public record User(String firstName, String lastName, boolean active, String userId) {} diff --git a/converter/src/main/java/com/iluwatar/converter/UserConverter.java b/converter/src/main/java/com/iluwatar/converter/UserConverter.java index 862b684ece5a..f7dbd3138047 100644 --- a/converter/src/main/java/com/iluwatar/converter/UserConverter.java +++ b/converter/src/main/java/com/iluwatar/converter/UserConverter.java @@ -24,9 +24,7 @@ */ package com.iluwatar.converter; -/** - * Example implementation of the simple User converter. - */ +/** Example implementation of the simple User converter. */ public class UserConverter extends Converter { public UserConverter() { @@ -34,11 +32,10 @@ public UserConverter() { } private static UserDto convertToDto(User user) { - return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId()); + return new UserDto(user.firstName(), user.lastName(), user.active(), user.userId()); } private static User convertToEntity(UserDto dto) { - return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail()); + return new User(dto.firstName(), dto.lastName(), dto.active(), dto.email()); } - } diff --git a/converter/src/main/java/com/iluwatar/converter/UserDto.java b/converter/src/main/java/com/iluwatar/converter/UserDto.java index ba028ca9d5cd..dc1d861dd971 100644 --- a/converter/src/main/java/com/iluwatar/converter/UserDto.java +++ b/converter/src/main/java/com/iluwatar/converter/UserDto.java @@ -24,23 +24,5 @@ */ package com.iluwatar.converter; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -/** - * User DTO class. - */ -@RequiredArgsConstructor -@Getter -@EqualsAndHashCode -@ToString -public class UserDto { - - private final String firstName; - private final String lastName; - private final boolean active; - private final String email; - -} +/** UserDto record. */ +public record UserDto(String firstName, String lastName, boolean active, String email) {} diff --git a/converter/src/test/java/com/iluwatar/converter/AppTest.java b/converter/src/test/java/com/iluwatar/converter/AppTest.java index 164e8a9bb034..366adef7b8a7 100644 --- a/converter/src/test/java/com/iluwatar/converter/AppTest.java +++ b/converter/src/test/java/com/iluwatar/converter/AppTest.java @@ -24,26 +24,20 @@ */ package com.iluwatar.converter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * App running test - */ +import org.junit.jupiter.api.Test; + +/** App running test */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java index df0b85736a9e..da14e3e5a67c 100644 --- a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java +++ b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java @@ -30,16 +30,12 @@ import java.util.Random; import org.junit.jupiter.api.Test; -/** - * Tests for {@link Converter} - */ +/** Tests for {@link Converter} */ class ConverterTest { private final UserConverter userConverter = new UserConverter(); - /** - * Tests whether a converter created of opposite functions holds equality as a bijection. - */ + /** Tests whether a converter created of opposite functions holds equality as a bijection. */ @Test void testConversionsStartingFromDomain() { var u1 = new User("Tom", "Hanks", true, "tom@hanks.com"); @@ -47,9 +43,7 @@ void testConversionsStartingFromDomain() { assertEquals(u1, u2); } - /** - * Tests whether a converter created of opposite functions holds equality as a bijection. - */ + /** Tests whether a converter created of opposite functions holds equality as a bijection. */ @Test void testConversionsStartingFromDto() { var u1 = new UserDto("Tom", "Hanks", true, "tom@hanks.com"); @@ -63,22 +57,25 @@ void testConversionsStartingFromDto() { */ @Test void testCustomConverter() { - var converter = new Converter( - userDto -> new User( - userDto.getFirstName(), - userDto.getLastName(), - userDto.isActive(), - String.valueOf(new Random().nextInt()) - ), - user -> new UserDto( - user.getFirstName(), - user.getLastName(), - user.isActive(), - user.getFirstName().toLowerCase() + user.getLastName().toLowerCase() + "@whatever.com") - ); + var converter = + new Converter( + userDto -> + new User( + userDto.firstName(), + userDto.lastName(), + userDto.active(), + String.valueOf(new Random().nextInt())), + user -> + new UserDto( + user.firstName(), + user.lastName(), + user.active(), + user.firstName().toLowerCase() + + user.lastName().toLowerCase() + + "@whatever.com")); var u1 = new User("John", "Doe", false, "12324"); var userDto = converter.convertFromEntity(u1); - assertEquals("johndoe@whatever.com", userDto.getEmail()); + assertEquals("johndoe@whatever.com", userDto.email()); } /** @@ -87,11 +84,11 @@ void testCustomConverter() { */ @Test void testCollectionConversion() { - var users = List.of( - new User("Camile", "Tough", false, "124sad"), - new User("Marti", "Luther", true, "42309fd"), - new User("Kate", "Smith", true, "if0243") - ); + var users = + List.of( + new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), + new User("Kate", "Smith", true, "if0243")); var fromDtos = userConverter.createFromDtos(userConverter.createFromEntities(users)); assertEquals(users, fromDtos); } diff --git a/cqrs/README.md b/cqrs/README.md deleted file mode 100644 index 6ac6061ef70e..000000000000 --- a/cqrs/README.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: CQRS -category: Architectural -language: en -tag: - - Performance - - Cloud distributed ---- - -## Intent -CQRS Command Query Responsibility Segregation - Separate the query side from the command side. - -## Class diagram -![alt text](./etc/cqrs.png "CQRS") - -## Applicability -Use the CQRS pattern when - -* You want to scale the queries and commands independently. -* You want to use different data models for queries and commands. Useful when dealing with complex domains. -* You want to use architectures like event sourcing or task based UI. - -## Credits - -* [Greg Young - CQRS, Task Based UIs, Event Sourcing agh!](http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/) -* [Martin Fowler - CQRS](https://martinfowler.com/bliki/CQRS.html) -* [Oliver Wolf - CQRS for Great Good](https://www.youtube.com/watch?v=Ge53swja9Dw) -* [Command and Query Responsibility Segregation (CQRS) pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs) diff --git a/crtp/src/test/java/crtp/FightTest.java b/crtp/src/test/java/crtp/FightTest.java deleted file mode 100644 index b1d699e33385..000000000000 --- a/crtp/src/test/java/crtp/FightTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package crtp; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Slf4j -public class FightTest { - - /** - * A fighter has signed a contract with a promotion, and he will face some other fighters. A list of opponents is ready - * but for some reason not all of them belong to the same weight class. Let's ensure that the fighter will only face - * opponents in the same weight class. - */ - @Test - void testFighterCanFightOnlyAgainstSameWeightOpponents() { - MmaBantamweightFighter fighter = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); - List> opponents = getOpponents(); - List> challenged = new ArrayList<>(); - - opponents.forEach(challenger -> { - try { - ((MmaBantamweightFighter) challenger).fight(fighter); - challenged.add(challenger); - } catch (ClassCastException e) { - LOGGER.error(e.getMessage()); - } - }); - - assertFalse(challenged.isEmpty()); - assertTrue(challenged.stream().allMatch(c -> c instanceof MmaBantamweightFighter)); - } - - private static List> getOpponents() { - return List.of( - new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"), - new MmaLightweightFighter("Evan", "Evans", "Clean Coder", "Sambo"), - new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"), - new MmaBantamweightFighter("Ray", "Raymond", "Scrum Master", "Karate"), - new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu") - ); - } - - -} diff --git a/curiously-recurring-template-pattern/README.md b/curiously-recurring-template-pattern/README.md new file mode 100644 index 000000000000..0cf7505040c2 --- /dev/null +++ b/curiously-recurring-template-pattern/README.md @@ -0,0 +1,158 @@ +--- +title: "Curiously Recurring Template Pattern in Java: Leveraging Polymorphism Uniquely" +shortTitle: Curiously Recurring Template Pattern (CRTP) +description: "Discover the Curiously Recurring Template Pattern (CRTP) in Java. Learn how to achieve static polymorphism for efficient method overriding and compile-time polymorphic behavior. Perfect for performance-critical applications." +language: en +category: Structural +tag: + - Code simplification + - Extensibility + - Generic + - Idiom + - Instantiation + - Polymorphism + - Recursion +--- + +## Also known as + +* CRTP +* Mixin Inheritance +* Recursive Type Bound +* Recursive Generic +* Static Polymorphism + +## Intent of Curiously Recurring Template Pattern + +The Curiously Recurring Template Pattern (CRTP) is a powerful design pattern in Java used to achieve static polymorphism. By having a class template derive from a template instantiation of its own class, CRTP enables method overriding and compile-time polymorphic behavior, enhancing efficiency and performance in your Java applications. + +## Detailed Explanation of Curiously Recurring Template Pattern with Real-World Examples + +Real-world example + +> Consider a scenario where a library system manages various types of media: books, DVDs, and magazines. Each media type has specific attributes and behaviors, but they all share common functionality like borrowing and returning. By applying the Curiously Recurring Template Pattern (CRTP) in Java, you can create a base template class `MediaItem` encompassing these common methods. Each specific media type (e.g., `Book`, `DVD`, `Magazine`) would inherit from `MediaItem` using itself as a template parameter. This approach allows each media type to customize shared functionality efficiently, avoiding the overhead of virtual methods. + +In plain words + +> The CRTP in Java ensures that certain methods within a type can accept arguments specific to its subtypes, enabling more efficient and type-safe polymorphic behavior at compile time. + +Wikipedia says + +> The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X derives from a class template instantiation using X itself as a template argument. + +## Programmatic example of CRTP in Java + +For a mixed martial arts promotion that is planning an event, ensuring that the fights are organized between athletes of the same weight class is crucial. This prevents mismatches between fighters of significantly different sizes, such as a heavyweight facing off against a bantamweight. + +Let's define the generic interface `Fighter`. + +```java +public interface Fighter { + + void fight(T t); + +} +``` + +The `MMAFighter` class is used to instantiate fighters distinguished by their weight class. + +```java +@Slf4j +@Data +public class MmaFighter> implements Fighter { + + private final String name; + private final String surname; + private final String nickName; + private final String speciality; + + @Override + public void fight(T opponent) { + LOGGER.info("{} is going to fight against {}", this, opponent); + } +} +``` + +The followings are some subtypes of `MmaFighter`. + +```java +class MmaBantamweightFighter extends MmaFighter { + + public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } +} + +public class MmaHeavyweightFighter extends MmaFighter { + + public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } +} +``` + +A fighter is allowed to fight an opponent of the same weight classes. If the opponent is of a different weight class, an error is raised. + +```java +public static void main(String[] args) { + + MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); + MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); + fighter1.fight(fighter2); + + MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); + MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); + fighter3.fight(fighter4); +} +``` + +Program output: + +``` +08:42:34.048 [main] INFO crtp.MmaFighter -- MmaFighter(name=Joe, surname=Johnson, nickName=The Geek, speciality=Muay Thai) is going to fight against MmaFighter(name=Ed, surname=Edwards, nickName=The Problem Solver, speciality=Judo) +08:42:34.054 [main] INFO crtp.MmaFighter -- MmaFighter(name=Dave, surname=Davidson, nickName=The Bug Smasher, speciality=Kickboxing) is going to fight against MmaFighter(name=Jack, surname=Jackson, nickName=The Pragmatic, speciality=Brazilian Jiu-Jitsu) +``` + +## When to Use the Curiously Recurring Template Pattern in Java + +* When you need to extend the functionality of a class through inheritance but prefer compile-time polymorphism to runtime polymorphism for efficiency reasons. +* When you want to avoid the overhead of virtual functions but still achieve polymorphic behavior. +* In template metaprogramming to provide implementations of functions or policies that can be selected at compile time. +* You have type conflicts when chaining methods in an object hierarchy. +* You want to use a parameterized class method that can accept subclasses of the class as arguments, allowing it to be applied to objects that inherit from the class. +* You want certain methods to work only with instances of the same type, such as for achieving mutual comparability. + +## Curiously Recurring Template Pattern Java Tutorials + +* [Curiously Recurring Template Pattern in Java (The NuaH Blog)](https://nuah.livejournal.com/328187.html) + +## Real-World Applications of Curiously Recurring Template Pattern in Java + +* Implementing compile-time polymorphic interfaces in template libraries. +* Enhancing code reuse in libraries where performance is critical, like in mathematical computations, embedded systems, and real-time processing applications. +* Implementation of the `Cloneable` interface in various Java libraries. + +## Benefits and Trade-offs of Curiously Recurring Template Pattern + +Benefits: + +* Elimination of virtual function call overhead, enhancing performance. +* Safe reuse of the base class code without the risks associated with multiple inheritances. +* Greater flexibility and extensibility in compile-time polymorphism scenarios. + +Trade-offs: + +* Increased complexity in understanding and debugging due to the interplay of templates and inheritance. +* Can lead to code bloat because each instantiation of a template results in a new class. +* Less flexibility compared to runtime polymorphism as the behavior must be determined entirely at compile time. + +## Related Java Design Patterns + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Can be used in conjunction with CRTP to instantiate derived classes without knowing their specific types. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): CRTP can implement compile-time strategy selection. +* [Template Method](https://java-design-patterns.com/patterns/template-method/): Similar in structure but differs in that CRTP achieves behavior variation through compile-time polymorphism. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) diff --git a/aggregator-microservices/etc/aggregator-microservices.urm.puml b/curiously-recurring-template-pattern/etc/curiously-recurring-template-pattern.urm.puml similarity index 100% rename from aggregator-microservices/etc/aggregator-microservices.urm.puml rename to curiously-recurring-template-pattern/etc/curiously-recurring-template-pattern.urm.puml diff --git a/crtp/pom.xml b/curiously-recurring-template-pattern/pom.xml similarity index 89% rename from crtp/pom.xml rename to curiously-recurring-template-pattern/pom.xml index 1869f7b37adc..ab7ed0b19a36 100644 --- a/crtp/pom.xml +++ b/curiously-recurring-template-pattern/pom.xml @@ -1,64 +1,72 @@ - - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - crtp - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.crtp.App - - - - - - - - - + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + curiously-recurring-template-pattern + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.crtp.App + + + + + + + + + diff --git a/crtp/src/main/java/crtp/App.java b/curiously-recurring-template-pattern/src/main/java/crtp/App.java similarity index 77% rename from crtp/src/main/java/crtp/App.java rename to curiously-recurring-template-pattern/src/main/java/crtp/App.java index 0013ef1c066b..d592dd717f89 100644 --- a/crtp/src/main/java/crtp/App.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/App.java @@ -40,13 +40,16 @@ public class App { */ public static void main(String[] args) { - MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); - MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); + MmaBantamweightFighter fighter1 = + new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); + MmaBantamweightFighter fighter2 = + new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); fighter1.fight(fighter2); - MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); - MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); + MmaHeavyweightFighter fighter3 = + new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); + MmaHeavyweightFighter fighter4 = + new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); fighter3.fight(fighter4); - } } diff --git a/crtp/src/main/java/crtp/Fighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/Fighter.java similarity index 97% rename from crtp/src/main/java/crtp/Fighter.java rename to curiously-recurring-template-pattern/src/main/java/crtp/Fighter.java index 675a3d97571b..da149229c8ba 100644 --- a/crtp/src/main/java/crtp/Fighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/Fighter.java @@ -1,36 +1,35 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package crtp; - -/** - * Fighter interface. - * - * @param The type of fighter. - */ -public interface Fighter { - - void fight(T t); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package crtp; + +/** + * Fighter interface. + * + * @param The type of fighter. + */ +public interface Fighter { + + void fight(T t); +} diff --git a/crtp/src/main/java/crtp/MmaBantamweightFighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/MmaBantamweightFighter.java similarity index 95% rename from crtp/src/main/java/crtp/MmaBantamweightFighter.java rename to curiously-recurring-template-pattern/src/main/java/crtp/MmaBantamweightFighter.java index 737dcac920da..08886a3b371c 100644 --- a/crtp/src/main/java/crtp/MmaBantamweightFighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/MmaBantamweightFighter.java @@ -1,36 +1,33 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package crtp; - -/** - * MmaBantamweightFighter class. - */ -class MmaBantamweightFighter extends MmaFighter { - - public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { - super(name, surname, nickName, speciality); - } - -} \ No newline at end of file +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package crtp; + +/** MmaBantamweightFighter class. */ +class MmaBantamweightFighter extends MmaFighter { + + public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } +} diff --git a/crtp/src/main/java/crtp/MmaFighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/MmaFighter.java similarity index 97% rename from crtp/src/main/java/crtp/MmaFighter.java rename to curiously-recurring-template-pattern/src/main/java/crtp/MmaFighter.java index 98f37a673756..a62ad2811b38 100644 --- a/crtp/src/main/java/crtp/MmaFighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/MmaFighter.java @@ -1,49 +1,48 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package crtp; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; - -/** - * MmaFighter class. - * - * @param MmaFighter derived class that uses itself as type parameter. - */ -@Slf4j -@Data -public class MmaFighter> implements Fighter { - - private final String name; - private final String surname; - private final String nickName; - private final String speciality; - - @Override - public void fight(T opponent) { - LOGGER.info("{} is going to fight against {}", this, opponent); - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package crtp; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +/** + * MmaFighter class. + * + * @param MmaFighter derived class that uses itself as type parameter. + */ +@Slf4j +@Data +public class MmaFighter> implements Fighter { + + private final String name; + private final String surname; + private final String nickName; + private final String speciality; + + @Override + public void fight(T opponent) { + LOGGER.info("{} is going to fight against {}", this, opponent); + } +} diff --git a/crtp/src/main/java/crtp/MmaHeavyweightFighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/MmaHeavyweightFighter.java similarity index 95% rename from crtp/src/main/java/crtp/MmaHeavyweightFighter.java rename to curiously-recurring-template-pattern/src/main/java/crtp/MmaHeavyweightFighter.java index 4df63ef14d9f..1ed545ede7ec 100644 --- a/crtp/src/main/java/crtp/MmaHeavyweightFighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/MmaHeavyweightFighter.java @@ -1,36 +1,33 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package crtp; - -/** - * MmaHeavyweightFighter. - */ -public class MmaHeavyweightFighter extends MmaFighter { - - public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { - super(name, surname, nickName, speciality); - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package crtp; + +/** MmaHeavyweightFighter. */ +public class MmaHeavyweightFighter extends MmaFighter { + + public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } +} diff --git a/crtp/src/main/java/crtp/MmaLightweightFighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/MmaLightweightFighter.java similarity index 95% rename from crtp/src/main/java/crtp/MmaLightweightFighter.java rename to curiously-recurring-template-pattern/src/main/java/crtp/MmaLightweightFighter.java index c9afd5481ac1..433b1934fa15 100644 --- a/crtp/src/main/java/crtp/MmaLightweightFighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/MmaLightweightFighter.java @@ -1,36 +1,33 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package crtp; - -/** - * MmaLightweightFighter class. - */ -class MmaLightweightFighter extends MmaFighter { - - public MmaLightweightFighter(String name, String surname, String nickName, String speciality) { - super(name, surname, nickName, speciality); - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package crtp; + +/** MmaLightweightFighter class. */ +class MmaLightweightFighter extends MmaFighter { + + public MmaLightweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } +} diff --git a/crtp/src/test/java/crtp/AppTest.java b/curiously-recurring-template-pattern/src/test/java/crtp/AppTest.java similarity index 94% rename from crtp/src/test/java/crtp/AppTest.java rename to curiously-recurring-template-pattern/src/test/java/crtp/AppTest.java index ee3e7978612b..fb19aa31a8d6 100644 --- a/crtp/src/test/java/crtp/AppTest.java +++ b/curiously-recurring-template-pattern/src/test/java/crtp/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/curiously-recurring-template-pattern/src/test/java/crtp/FightTest.java b/curiously-recurring-template-pattern/src/test/java/crtp/FightTest.java new file mode 100644 index 000000000000..a70edd0d674d --- /dev/null +++ b/curiously-recurring-template-pattern/src/test/java/crtp/FightTest.java @@ -0,0 +1,72 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package crtp; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +@Slf4j +public class FightTest { + + /** + * A fighter has signed a contract with a promotion, and he will face some other fighters. A list + * of opponents is ready but for some reason not all of them belong to the same weight class. + * Let's ensure that the fighter will only face opponents in the same weight class. + */ + @Test + void testFighterCanFightOnlyAgainstSameWeightOpponents() { + MmaBantamweightFighter fighter = + new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); + List> opponents = getOpponents(); + List> challenged = new ArrayList<>(); + + opponents.forEach( + challenger -> { + try { + ((MmaBantamweightFighter) challenger).fight(fighter); + challenged.add(challenger); + } catch (ClassCastException e) { + LOGGER.error(e.getMessage()); + } + }); + + assertFalse(challenged.isEmpty()); + assertTrue(challenged.stream().allMatch(c -> c instanceof MmaBantamweightFighter)); + } + + private static List> getOpponents() { + return List.of( + new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"), + new MmaLightweightFighter("Evan", "Evans", "Clean Coder", "Sambo"), + new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"), + new MmaBantamweightFighter("Ray", "Raymond", "Scrum Master", "Karate"), + new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu")); + } +} diff --git a/currying/README.md b/currying/README.md index 9f32c9066e06..22806a50d2e7 100644 --- a/currying/README.md +++ b/currying/README.md @@ -1,119 +1,140 @@ --- -title: Currying -category: Functional +title: "Currying Pattern in Java: Enhancing Function Flexibility and Reusability" +shortTitle: Currying +description: "Learn about currying in Java, a technique to simplify functions by breaking them into a sequence of single-argument functions. Discover its applications, benefits, and examples in this comprehensive guide." +category: Functional language: en tag: -- Decoupling + - Code simplification + - Functional decomposition + - Generic + - Immutable --- -## Name / classification -Currying +## Also known as -## Intent -Currying decomposes a function that takes multiple arguments into a sequence of functions that each take a single argument. -Curried functions are useful since they can be used to create new functions with lower arity to perform more specialised tasks -in a concise and readable manner. This is done via partial application. +* Partial Function Application + +## Intent of Currying Design Pattern + +Currying decomposes a function that takes multiple arguments into a sequence of functions that each take a single argument. This technique is integral in functional programming, enabling the creation of higher-order functions through partial application of its arguments. Using currying in Java can lead to more modular, reusable, and maintainable code. + +## Detailed Explanation of Currying Pattern with Real-World Examples -## Explanation Real-world example -> Consider a librarian who wants to populate their library with books. The librarian wants functions which can create -> books corresponding to specific genres and authors. Currying makes this possible by writing a curried book builder -> function and utilising partial application. + +> Currying in programming can be compared to an assembly line in a factory. Imagine a car manufacturing process where each station on the assembly line performs a specific task, such as installing the engine, painting the car, and adding the wheels. Each station takes a partially completed car and performs a single operation before passing it to the next station. Similarly, in currying, a function that requires multiple arguments is broken down into a series of functions, each taking a single argument and returning another function until all arguments are provided. This step-by-step processing simplifies complex tasks by dividing them into manageable, sequential operations, which is especially useful in Java functional programming. In plain words -> Decompose a function that take multiple arguments into multiple functions that take a single argument. + +> Decompose a function that take multiple arguments into multiple functions that take a single argument. Wikipedia says -> Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that -> each take a single argument. Given a function $f:(X \times Y) \rightarrow Z$, currying constructs a new function -> $h:X \rightarrow (Y\rightarrow Z)$. $h$ takes an argument from $X$ and returns a function which maps $Y$ to $Z$. Hence, -> $h(x)(y) = f(x, y)$. -Programmatic example +> In mathematics and computer science, currying is the technique of translating a function that takes multiple arguments into a sequence of families of functions, each taking a single argument. + +## Programmatic example of Currying Pattern in Java + +Consider a librarian who wants to populate their library with books. The librarian wants functions which can create books corresponding to specific genres and authors. Currying makes this possible by writing a curried book builder function and utilising partial application. + We have a `Book` class and `Genre` enum. + ```java public class Book { - private final Genre genre; - private final String author; - private final String title; - private final LocalDate publicationDate; - - Book(Genre genre, String author, String title, LocalDate publicationDate) { - this.genre = genre; - this.author = author; - this.title = title; - this.publicationDate = publicationDate; - } + private final Genre genre; + private final String author; + private final String title; + private final LocalDate publicationDate; + + Book(Genre genre, String author, String title, LocalDate publicationDate) { + this.genre = genre; + this.author = author; + this.title = title; + this.publicationDate = publicationDate; + } } +``` +```java public enum Genre { - FANTASY, - HORROR, - SCI_FI; + FANTASY, + HORROR, + SCI_FI } ``` + We could easily create a `Book` object with the following method: + ```java Book createBook(Genre genre, String author, String title, LocalDate publicationDate) { return new Book(genre, author, title, publicationDate); } ``` -However, what if we only wanted to create books from the `FANTASY` genre? We could pass in the `FANTASY` parameter on each method call; however, this is repetitive. We could define a new method specifically for creating `FANTASY` books; however, it is infeasible to create a new method for each book genre. The solution is to create a curried function. + +However, what if we only wanted to create books from the `FANTASY` genre? Passing the `FANTASY` parameter with each method call would be repetitive. Alternatively, we could define a new method specifically for creating `FANTASY` books, but it would be impractical to create a separate method for each genre. The solution is to use a curried function. + ```java static Function>>> book_creator - = genre - -> author - -> title - -> publicationDate - -> new Book(genre, author, title, publicationDate); + = bookGenre + -> bookAuthor + -> bookTitle + -> bookPublicationDate + -> new Book(bookGenre, bookAuthor, bookTitle, bookPublicationDate); ``` + Note that the order of the parameters is important. `genre` must come before `author`, `author` must come before `title` and so on. We must be considerate of this when writing curried functions to take full advantage of partial application. Using the above function, we can define a new function `fantasyBookFunc`, to generate `FANTASY` books as follows: + ```java Function>> fantasyBookFunc = Book.book_creator.apply(Genre.FANTASY); ``` -Unfortunately, the type signature of `BOOK_CREATOR` and `fantasyBookFunc` are difficult to read and understand. We can improve this by using the [builder pattern](https://java-design-patterns.com/patterns/builder/) and [functional interfaces](https://www.geeksforgeeks.org/functional-interfaces-java/#:~:text=A%20functional%20interface%20is%20an,any%20number%20of%20default%20methods). + +Unfortunately, the type signature of `BOOK_CREATOR` and `fantasyBookFunc` are difficult to read and understand. We can improve this by using the [builder pattern](https://java-design-patterns.com/patterns/builder/) and functional interfaces. + ```java public static AddGenre builder() { return genre - -> author - -> title - -> publicationDate - -> new Book(genre, author, title, publicationDate); + -> author + -> title + -> publicationDate + -> new Book(genre, author, title, publicationDate); } public interface AddGenre { -Book.AddAuthor withGenre(Genre genre); + Book.AddAuthor withGenre(Genre genre); } public interface AddAuthor { -Book.AddTitle withAuthor(String author); + Book.AddTitle withAuthor(String author); } public interface AddTitle { -Book.AddPublicationDate withTitle(String title); + Book.AddPublicationDate withTitle(String title); } public interface AddPublicationDate { -Book withPublicationDate(LocalDate publicationDate); + Book withPublicationDate(LocalDate publicationDate); } ``` -The semantics of the `builder` function can easily be understood. The `builder` function returns a function `AddGenre`, which adds the genre to the book. Similarity, the `AddGenre` function returns another function `AddTitle`, which adds the title to the book and so on, until the `AddPublicationDate` function returns a `Book`. -For example, we could create a `Book` as follows: + +The semantics of the `builder` function can easily be understood. The `builder` function returns a function `AddGenre`, which adds the genre to the book. Similarity, the `AddGenre` function returns another function `AddTitle`, which adds the title to the book and so on, until the `AddPublicationDate` function returns a `Book`. For example, we could create a `Book` as follows: + ```java Book book = Book.builder().withGenre(Genre.FANTASY) - .withAuthor("Author") - .withTitle("Title") - .withPublicationDate(LocalDate.of(2000, 7, 2)); + .withAuthor("Author") + .withTitle("Title") + .withPublicationDate(LocalDate.of(2000, 7, 2)); ``` + The below example demonstrates how partial application can be used with the `builder` function to create specialised book builder functions. + ```java public static void main(String[] args) { LOGGER.info("Librarian begins their work."); - + // Defining genre book functions Book.AddAuthor fantasyBookFunc = Book.builder().withGenre(Genre.FANTASY); Book.AddAuthor horrorBookFunc = Book.builder().withGenre(Genre.HORROR); - Book.AddAuthor scifiBookFunc = Book.builder().withGenre(Genre.SCI_FI); + Book.AddAuthor scifiBookFunc = Book.builder().withGenre(Genre.SCIFI); // Defining author book functions Book.AddTitle kingFantasyBooksFunc = fantasyBookFunc.withAuthor("Stephen King"); @@ -122,21 +143,21 @@ public static void main(String[] args) { // Creates books by Stephen King (horror and fantasy genres) Book shining = kingHorrorBooksFunc.withTitle("The Shining") - .withPublicationDate(LocalDate.of(1977, 1, 28)); + .withPublicationDate(LocalDate.of(1977, 1, 28)); Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger") - .withPublicationDate(LocalDate.of(1982, 6, 10)); + .withPublicationDate(LocalDate.of(1982, 6, 10)); // Creates fantasy books by J.K. Rowling Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets") - .withPublicationDate(LocalDate.of(1998, 7, 2)); + .withPublicationDate(LocalDate.of(1998, 7, 2)); // Create sci-fi books Book dune = scifiBookFunc.withAuthor("Frank Herbert") - .withTitle("Dune") - .withPublicationDate(LocalDate.of(1965, 8, 1)); + .withTitle("Dune") + .withPublicationDate(LocalDate.of(1965, 8, 1)); Book foundation = scifiBookFunc.withAuthor("Isaac Asimov") - .withTitle("Foundation") - .withPublicationDate(LocalDate.of(1942, 5, 1)); + .withTitle("Foundation") + .withPublicationDate(LocalDate.of(1942, 5, 1)); LOGGER.info("Stephen King Books:"); LOGGER.info(shining.toString()); @@ -150,42 +171,63 @@ public static void main(String[] args) { LOGGER.info(foundation.toString()); } ``` + Program output: + ``` -Librarian begins their work. -Stephen King Books: -Book{genre=HORROR, author='Stephen King', title='The Shining', publicationDate=1977-01-28} -Book{genre=FANTASY, author='Stephen King', title='The Dark Tower: Gunslinger', publicationDate=1982-06-10} -J.K. Rowling Books: -Book{genre=FANTASY, author='J.K. Rowling', title='Harry Potter and the Chamber of Secrets', publicationDate=1998-07-02} -Sci-fi Books: -Book{genre=SCI_FI, author='Frank Herbert', title='Dune', publicationDate=1965-08-01} -Book{genre=SCI_FI, author='Isaac Asimov', title='Foundation', publicationDate=1942-05-01} +09:04:52.499 [main] INFO com.iluwatar.currying.App -- Librarian begins their work. +09:04:52.502 [main] INFO com.iluwatar.currying.App -- Stephen King Books: +09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=HORROR, author='Stephen King', title='The Shining', publicationDate=1977-01-28} +09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=FANTASY, author='Stephen King', title='The Dark Tower: Gunslinger', publicationDate=1982-06-10} +09:04:52.506 [main] INFO com.iluwatar.currying.App -- J.K. Rowling Books: +09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=FANTASY, author='J.K. Rowling', title='Harry Potter and the Chamber of Secrets', publicationDate=1998-07-02} +09:04:52.506 [main] INFO com.iluwatar.currying.App -- Sci-fi Books: +09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=SCIFI, author='Frank Herbert', title='Dune', publicationDate=1965-08-01} +09:04:52.506 [main] INFO com.iluwatar.currying.App -- Book{genre=SCIFI, author='Isaac Asimov', title='Foundation', publicationDate=1942-05-01} ``` -## Class diagram -![currying-uml](./etc/currying.urm.png) +## When to Use the Currying Pattern in Java + +* When functions need to be called with some arguments preset in Java. +* In functional programming languages or paradigms to simplify functions that take multiple arguments. +* To improve code reusability and composability by breaking down functions into simpler, unary functions, enhancing the modularity of Java applications. + +## Currying Pattern Java Tutorials + +* [Currying in Java (Baeldung)](https://www.baeldung.com/java-currying) +* [What Is Currying in Programming (Towards Data Science)](https://towardsdatascience.com/what-is-currying-in-programming-56fd57103431#:~:text=Currying%20is%20helpful%20when%20you,concise%2C%20and%20more%20readable%20solution.) +* [Why the fudge should I use currying? (DailyJS)](https://medium.com/dailyjs/why-the-fudge-should-i-use-currying-84e4000c8743) + +## Real-World Applications of Currying Pattern in Java + +* Functional programming languages like Haskell, Scala, and JavaScript. +* Java programming, especially with lambda expressions and streams introduced in Java 8. +* Event handling in UIs where a function with specific parameters needs to be triggered upon an event. +* APIs that require configuration with multiple parameters. + +## Benefits and Trade-offs of Currying Pattern + +Benefits: + +* Increases function reusability by allowing the creation of specialized functions from more generic ones. +* Enhances code readability and maintainability by breaking complex functions into simpler, single-argument functions. +* Facilitates function composition, leading to more declarative and concise code. -## Applicability -A curried function which has only been passed some of its arguments is called a partial application. Partial application -allows for the creation of functions with some pre-defined data in their scope, since partial application can be used to -create specialised functions with lower arity. This abstraction can help keep code readable and concise. Therefore, currying is useful when frequently calling functions with fixed parameters. +Trade-offs: -## Known uses -Most functional programming languages support curried functions. A popular example is [Haskell](https://www.haskell.org/), in which all functions are considered curried. +* Can lead to performance overhead due to the creation of additional closures. +* May make debugging more challenging, as it introduces additional layers of function calls. +* Can be less intuitive for developers unfamiliar with functional programming concepts. +* As shown in the programmatic example above, curried functions with several parameters have a cumbersome type signature in Java. -## Consequences -Pros -* Currying allows for partial application, which can be used to create specialised functions concisely. +## Related Java Design Patterns -Cons -* The order of the parameters in a curried function is important since we want to take advantage of partial application. It is best to input the most general parameters first and input specific parameters last. -* As shown in the programmatic example above, curried functions with several parameters have a cumbersome type signature (in Java). +* Function Composition: Currying is often used in conjunction with function composition to enable more readable and concise code. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): While not the same, currying shares the decorator pattern's concept of wrapping functionality. +* [Factory](https://java-design-patterns.com/patterns/factory/): Currying can be used to create factory functions that produce variations of a function with certain arguments preset. -## Related patterns -* [Builder pattern](https://java-design-patterns.com/patterns/builder/) +## References and Credits -## Credits -* [Currying in Java](https://www.baeldung.com/java-currying) -* [What Is Currying in Programming](https://towardsdatascience.com/what-is-currying-in-programming-56fd57103431#:~:text=Currying%20is%20helpful%20when%20you,concise%2C%20and%20more%20readable%20solution.) -* [Why the fudge should I use currying?](https://medium.com/dailyjs/why-the-fudge-should-i-use-currying-84e4000c8743) +* [Functional Programming in Java: Harnessing the Power Of Java 8 Lambda Expressions](https://amzn.to/3TKeZPD) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3J6vEaW) +* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://amzn.to/3J6vJLM) diff --git a/currying/pom.xml b/currying/pom.xml index 5b5385c45a59..5244aa2792d8 100644 --- a/currying/pom.xml +++ b/currying/pom.xml @@ -36,6 +36,14 @@ currying + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/currying/src/main/java/com/iluwatar/currying/App.java b/currying/src/main/java/com/iluwatar/currying/App.java index d3ca262b56c4..d919887530a2 100644 --- a/currying/src/main/java/com/iluwatar/currying/App.java +++ b/currying/src/main/java/com/iluwatar/currying/App.java @@ -28,19 +28,17 @@ import lombok.extern.slf4j.Slf4j; /** -* Currying decomposes a function with multiple arguments in multiple functions that -* take a single argument. A curried function which has only been passed some of its -* arguments is called a partial application. Partial application is useful since it can -* be used to create specialised functions in a concise way. -* -*

In this example, a librarian uses a curried book builder function create books belonging to -* desired genres and written by specific authors. -*/ + * Currying decomposes a function with multiple arguments in multiple functions that take a single + * argument. A curried function which has only been passed some of its arguments is called a partial + * application. Partial application is useful since it can be used to create specialised functions + * in a concise way. + * + *

In this example, a librarian uses a curried book builder function create books belonging to + * desired genres and written by specific authors. + */ @Slf4j public class App { - /** - * Main entry point of the program. - */ + /** Main entry point of the program. */ public static void main(String[] args) { LOGGER.info("Librarian begins their work."); @@ -55,20 +53,28 @@ public static void main(String[] args) { Book.AddTitle rowlingFantasyBooksFunc = fantasyBookFunc.withAuthor("J.K. Rowling"); // Creates books by Stephen King (horror and fantasy genres) - Book shining = kingHorrorBooksFunc.withTitle("The Shining") - .withPublicationDate(LocalDate.of(1977, 1, 28)); - Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger") + Book shining = + kingHorrorBooksFunc.withTitle("The Shining").withPublicationDate(LocalDate.of(1977, 1, 28)); + Book darkTower = + kingFantasyBooksFunc + .withTitle("The Dark Tower: Gunslinger") .withPublicationDate(LocalDate.of(1982, 6, 10)); // Creates fantasy books by J.K. Rowling - Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets") + Book chamberOfSecrets = + rowlingFantasyBooksFunc + .withTitle("Harry Potter and the Chamber of Secrets") .withPublicationDate(LocalDate.of(1998, 7, 2)); // Create sci-fi books - Book dune = scifiBookFunc.withAuthor("Frank Herbert") + Book dune = + scifiBookFunc + .withAuthor("Frank Herbert") .withTitle("Dune") .withPublicationDate(LocalDate.of(1965, 8, 1)); - Book foundation = scifiBookFunc.withAuthor("Isaac Asimov") + Book foundation = + scifiBookFunc + .withAuthor("Isaac Asimov") .withTitle("Foundation") .withPublicationDate(LocalDate.of(1942, 5, 1)); @@ -83,4 +89,4 @@ public static void main(String[] args) { LOGGER.info(dune.toString()); LOGGER.info(foundation.toString()); } -} \ No newline at end of file +} diff --git a/currying/src/main/java/com/iluwatar/currying/Book.java b/currying/src/main/java/com/iluwatar/currying/Book.java index f9b7be05138a..7ec4ed8cb448 100644 --- a/currying/src/main/java/com/iluwatar/currying/Book.java +++ b/currying/src/main/java/com/iluwatar/currying/Book.java @@ -29,9 +29,7 @@ import java.util.function.Function; import lombok.AllArgsConstructor; -/** - * Book class. - */ +/** Book class. */ @AllArgsConstructor public class Book { private final Genre genre; @@ -49,9 +47,9 @@ public boolean equals(Object o) { } Book book = (Book) o; return Objects.equals(author, book.author) - && Objects.equals(genre, book.genre) - && Objects.equals(title, book.title) - && Objects.equals(publicationDate, book.publicationDate); + && Objects.equals(genre, book.genre) + && Objects.equals(title, book.title) + && Objects.equals(publicationDate, book.publicationDate); } @Override @@ -61,57 +59,55 @@ public int hashCode() { @Override public String toString() { - return "Book{" + "genre=" + genre + ", author='" + author + '\'' - + ", title='" + title + '\'' + ", publicationDate=" + publicationDate + '}'; + return "Book{" + + "genre=" + + genre + + ", author='" + + author + + '\'' + + ", title='" + + title + + '\'' + + ", publicationDate=" + + publicationDate + + '}'; } - /** - * Curried book builder/creator function. - */ - static Function>>> book_creator - = bookGenre - -> bookAuthor - -> bookTitle - -> bookPublicationDate - -> new Book(bookGenre, bookAuthor, bookTitle, bookPublicationDate); + /** Curried book builder/creator function. */ + static Function>>> + book_creator = + bookGenre -> + bookAuthor -> + bookTitle -> + bookPublicationDate -> + new Book(bookGenre, bookAuthor, bookTitle, bookPublicationDate); /** * Implements the builder pattern using functional interfaces to create a more readable book * creator function. This function is equivalent to the BOOK_CREATOR function. */ public static AddGenre builder() { - return genre - -> author - -> title - -> publicationDate - -> new Book(genre, author, title, publicationDate); + return genre -> + author -> title -> publicationDate -> new Book(genre, author, title, publicationDate); } - /** - * Functional interface which adds the genre to a book. - */ + /** Functional interface which adds the genre to a book. */ public interface AddGenre { Book.AddAuthor withGenre(Genre genre); } - /** - * Functional interface which adds the author to a book. - */ + /** Functional interface which adds the author to a book. */ public interface AddAuthor { Book.AddTitle withAuthor(String author); } - /** - * Functional interface which adds the title to a book. - */ + /** Functional interface which adds the title to a book. */ public interface AddTitle { Book.AddPublicationDate withTitle(String title); } - /** - * Functional interface which adds the publication date to a book. - */ + /** Functional interface which adds the publication date to a book. */ public interface AddPublicationDate { Book withPublicationDate(LocalDate publicationDate); } -} \ No newline at end of file +} diff --git a/currying/src/main/java/com/iluwatar/currying/Genre.java b/currying/src/main/java/com/iluwatar/currying/Genre.java index 58e639f4ce3c..ad41f4004190 100644 --- a/currying/src/main/java/com/iluwatar/currying/Genre.java +++ b/currying/src/main/java/com/iluwatar/currying/Genre.java @@ -24,11 +24,9 @@ */ package com.iluwatar.currying; -/** - * Enum representing different book genres. - */ +/** Enum representing different book genres. */ public enum Genre { FANTASY, HORROR, - SCIFI; + SCIFI } diff --git a/currying/src/test/java/com/iluwatar/currying/AppTest.java b/currying/src/test/java/com/iluwatar/currying/AppTest.java index 83fd99363e52..3074628f29e6 100644 --- a/currying/src/test/java/com/iluwatar/currying/AppTest.java +++ b/currying/src/test/java/com/iluwatar/currying/AppTest.java @@ -28,12 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Tests that the App can be run without throwing any exceptions. - */ +/** Tests that the App can be run without throwing any exceptions. */ class AppTest { @Test void executesWithoutExceptions() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java b/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java index 74ac4ead321b..f22ff62850e5 100644 --- a/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java +++ b/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java @@ -26,36 +26,31 @@ import static org.junit.jupiter.api.Assertions.*; +import java.time.LocalDate; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.time.LocalDate; -/** - * Unit tests for the Book class - */ +/** Unit tests for the Book class */ class BookCurryingTest { private static Book expectedBook; @BeforeAll public static void initialiseBook() { - expectedBook = new Book(Genre.FANTASY, - "Dave", - "Into the Night", - LocalDate.of(2002, 4, 7)); + expectedBook = new Book(Genre.FANTASY, "Dave", "Into the Night", LocalDate.of(2002, 4, 7)); } - /** - * Tests that the expected book can be created via curried functions - */ + /** Tests that the expected book can be created via curried functions */ @Test void createsExpectedBook() { - Book builderCurriedBook = Book.builder() - .withGenre(Genre.FANTASY) - .withAuthor("Dave") - .withTitle("Into the Night") - .withPublicationDate(LocalDate.of(2002, 4, 7)); + Book builderCurriedBook = + Book.builder() + .withGenre(Genre.FANTASY) + .withAuthor("Dave") + .withTitle("Into the Night") + .withPublicationDate(LocalDate.of(2002, 4, 7)); - Book funcCurriedBook = Book.book_creator + Book funcCurriedBook = + Book.book_creator .apply(Genre.FANTASY) .apply("Dave") .apply("Into the Night") @@ -65,17 +60,15 @@ void createsExpectedBook() { assertEquals(expectedBook, funcCurriedBook); } - /** - * Tests that an intermediate curried function can be used to create the expected book - */ + /** Tests that an intermediate curried function can be used to create the expected book */ @Test void functionCreatesExpectedBook() { - Book.AddTitle daveFantasyBookFunc = Book.builder() - .withGenre(Genre.FANTASY) - .withAuthor("Dave"); + Book.AddTitle daveFantasyBookFunc = Book.builder().withGenre(Genre.FANTASY).withAuthor("Dave"); - Book curriedBook = daveFantasyBookFunc.withTitle("Into the Night") - .withPublicationDate(LocalDate.of(2002, 4, 7)); + Book curriedBook = + daveFantasyBookFunc + .withTitle("Into the Night") + .withPublicationDate(LocalDate.of(2002, 4, 7)); assertEquals(expectedBook, curriedBook); } diff --git a/dao/README.md b/dao/README.md deleted file mode 100644 index 3b08987172c5..000000000000 --- a/dao/README.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: Data Access Object -category: Architectural -language: en -tag: - - Data access ---- - -## Intent - -Object provides an abstract interface to some type of database or other persistence mechanism. - -## Explanation - -Real world example - -> There's a set of customers that need to be persisted to database. Additionally we need the whole -> set of CRUD (create/read/update/delete) operations so we can operate on customers easily. - -In plain words - -> DAO is an interface we provide over the base persistence mechanism. - -Wikipedia says - -> In computer software, a data access object (DAO) is a pattern that provides an abstract interface -> to some type of database or other persistence mechanism. - -**Programmatic Example** - -Walking through our customers example, here's the basic `Customer` entity. - -```java -public class Customer { - - private int id; - private String firstName; - private String lastName; - - public Customer(int id, String firstName, String lastName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } - // getters and setters -> - ... -} -``` - -Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao` -keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation. - -```java -public interface CustomerDao { - - Stream getAll() throws Exception; - - Optional getById(int id) throws Exception; - - boolean add(Customer customer) throws Exception; - - boolean update(Customer customer) throws Exception; - - boolean delete(Customer customer) throws Exception; -} - -public class InMemoryCustomerDao implements CustomerDao { - - private final Map idToCustomer = new HashMap<>(); - - // implement the interface using the map - ... -} - -@Slf4j -public class DbCustomerDao implements CustomerDao { - - private final DataSource dataSource; - - public DbCustomerDao(DataSource dataSource) { - this.dataSource = dataSource; - } - - // implement the interface using the data source - ... -``` - -Finally here's how we use our DAO to manage customers. - -```java - final var dataSource = createDataSource(); - createSchema(dataSource); - final var customerDao = new DbCustomerDao(dataSource); - - addCustomers(customerDao); - log.info(ALL_CUSTOMERS); - try (var customerStream = customerDao.getAll()) { - customerStream.forEach((customer) -> log.info(customer.toString())); - } - log.info("customerDao.getCustomerById(2): " + customerDao.getById(2)); - final var customer = new Customer(4, "Dan", "Danson"); - customerDao.add(customer); - log.info(ALL_CUSTOMERS + customerDao.getAll()); - customer.setFirstName("Daniel"); - customer.setLastName("Danielson"); - customerDao.update(customer); - log.info(ALL_CUSTOMERS); - try (var customerStream = customerDao.getAll()) { - customerStream.forEach((cust) -> log.info(cust.toString())); - } - customerDao.delete(customer); - log.info(ALL_CUSTOMERS + customerDao.getAll()); - - deleteSchema(dataSource); -``` - -The program output: - -```java -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@7cef4e59 -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -Customer{id=4, firstName='Daniel', lastName='Danielson'} -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@2db0f6b2 -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@12c8a2c0 -customerDao.getAllCustomers(): -Customer{id=1, firstName='Adam', lastName='Adamson'} -Customer{id=2, firstName='Bob', lastName='Bobson'} -Customer{id=3, firstName='Carl', lastName='Carlson'} -Customer{id=4, firstName='Daniel', lastName='Danielson'} -customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c -``` - -## Class diagram - -![alt text](./etc/dao.png "Data Access Object") - -## Applicability - -Use the Data Access Object in any of the following situations: - -* When you want to consolidate how the data layer is accessed. -* When you want to avoid writing multiple data retrieval/persistence layers. - -## Tutorials - -* [The DAO Pattern in Java](https://www.baeldung.com/java-dao-pattern) -* [Data Access Object Pattern](https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm) - -## Credits - -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) diff --git a/data-access-object/README.md b/data-access-object/README.md new file mode 100644 index 000000000000..1491eacf3299 --- /dev/null +++ b/data-access-object/README.md @@ -0,0 +1,249 @@ +--- +title: "Data Access Object Pattern in Java: Streamlining Database Interaction" +shortTitle: Data Access Object (DAO) +description: "Explore the Java Data Access Object (DAO) pattern to effectively separate business logic from database operations. Learn implementation strategies, real-world examples, and best practices." +category: Structural +language: en +tag: + - Abstraction + - Data access + - Data processing + - Decoupling + - Layered architecture + - Persistence +--- + +## Also known as + +* Data Access Layer +* DAO + +## Intent of Data Access Object Design Pattern + +The Data Access Object (DAO) design pattern aims to separate the application's business logic from the persistence layer, typically a database or any other storage mechanism. By using DAOs, the application can access and manipulate data without being dependent on the specific database implementation details. + +## Detailed Explanation of Data Access Object Pattern with Real-World Examples + +Real-world example + +> Imagine a library system where the main application manages book loans, user accounts, and inventory. The Data Access Object (DAO) pattern in this context would be used to separate the database operations (such as fetching book details, updating user records, and checking inventory) from the business logic of managing loans and accounts. For instance, there would be a `BookDAO` class responsible for all database interactions related to books, such as retrieving a book by its ISBN or updating its availability status. This abstraction allows the library system's main application code to focus on business rules and workflows, while the `BookDAO` handles the complex SQL queries and data management. This separation makes the system easier to maintain and test, as changes to the data source or business logic can be managed independently. + +In plain words + +> DAO is an interface we provide over the base persistence mechanism. + +Wikipedia says + +> In computer software, a data access object (DAO) is a pattern that provides an abstract interface to some type of database or other persistence mechanism. + +## Programmatic Example of DAO Pattern in Java + +There's a set of customers that need to be persisted to database. Additionally, we need the whole set of CRUD (create/read/update/delete) operations, so we can operate on customers easily. + +Walking through our customers example, here's the basic `Customer` entity. + +```java +@Setter +@Getter +@ToString +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@AllArgsConstructor +public class Customer { + + @EqualsAndHashCode.Include + private int id; + private String firstName; + private String lastName; +} +``` + +Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao` keeps a simple map of customers in memory while `DBCustomerDao` is the real RDBMS implementation. + +```java +public interface CustomerDao { + + Stream getAll() throws Exception; + + Optional getById(int id) throws Exception; + + boolean add(Customer customer) throws Exception; + + boolean update(Customer customer) throws Exception; + + boolean delete(Customer customer) throws Exception; +} + +public class InMemoryCustomerDao implements CustomerDao { + + private final Map idToCustomer = new HashMap<>(); + + // implement the interface using the map +} + +@Slf4j +@RequiredArgsConstructor +public class DbCustomerDao implements CustomerDao { + + private final DataSource dataSource; + + // implement the interface using the data source +} +``` + +Finally, here's how we use our DAO to manage customers. + +```java + +@Slf4j +public class App { + private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1"; + private static final String ALL_CUSTOMERS = "customerDao.getAllCustomers(): "; + + public static void main(final String[] args) throws Exception { + final var inMemoryDao = new InMemoryCustomerDao(); + performOperationsUsing(inMemoryDao); + + final var dataSource = createDataSource(); + createSchema(dataSource); + final var dbDao = new DbCustomerDao(dataSource); + performOperationsUsing(dbDao); + deleteSchema(dataSource); + } + + private static void deleteSchema(DataSource dataSource) throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); + } + } + + private static void createSchema(DataSource dataSource) throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); + } + } + + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + return dataSource; + } + + private static void performOperationsUsing(final CustomerDao customerDao) throws Exception { + addCustomers(customerDao); + LOGGER.info(ALL_CUSTOMERS); + try (var customerStream = customerDao.getAll()) { + customerStream.forEach(customer -> LOGGER.info(customer.toString())); + } + LOGGER.info("customerDao.getCustomerById(2): " + customerDao.getById(2)); + final var customer = new Customer(4, "Dan", "Danson"); + customerDao.add(customer); + LOGGER.info(ALL_CUSTOMERS + customerDao.getAll()); + customer.setFirstName("Daniel"); + customer.setLastName("Danielson"); + customerDao.update(customer); + LOGGER.info(ALL_CUSTOMERS); + try (var customerStream = customerDao.getAll()) { + customerStream.forEach(cust -> LOGGER.info(cust.toString())); + } + customerDao.delete(customer); + LOGGER.info(ALL_CUSTOMERS + customerDao.getAll()); + } + + private static void addCustomers(CustomerDao customerDao) throws Exception { + for (var customer : generateSampleCustomers()) { + customerDao.add(customer); + } + } + + public static List generateSampleCustomers() { + final var customer1 = new Customer(1, "Adam", "Adamson"); + final var customer2 = new Customer(2, "Bob", "Bobson"); + final var customer3 = new Customer(3, "Carl", "Carlson"); + return List.of(customer1, customer2, customer3); + } +} +``` + +The program output: + +``` +10:02:09.788 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): +10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson) +10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson) +10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson) +10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getCustomerById(2): Optional[Customer(id=2, firstName=Bob, lastName=Bobson)] +10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@4c3e4790 +10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): +10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson) +10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson) +10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson) +10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=4, firstName=Daniel, lastName=Danielson) +10:02:09.795 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@5679c6c6 +10:02:09.894 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): +10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson) +10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson) +10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson) +10:02:09.895 [main] INFO com.iluwatar.dao.App -- customerDao.getCustomerById(2): Optional[Customer(id=2, firstName=Bob, lastName=Bobson)] +10:02:09.896 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@23282c25 +10:02:09.897 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): +10:02:09.897 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson) +10:02:09.897 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson) +10:02:09.898 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson) +10:02:09.898 [main] INFO com.iluwatar.dao.App -- Customer(id=4, firstName=Daniel, lastName=Danielson) +10:02:09.898 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@f2f2cc1 +``` + +## Detailed Explanation of Data Access Object Pattern with Real-World Examples + +![Data Access Object](./etc/dao.png "Data Access Object") + +## When to Use the Data Access Object Pattern in Java + +Use the Data Access Object in any of the following situations: + +* There is a need to abstract and encapsulate all access to the data source. +* The application needs to support multiple types of databases or storage mechanisms without significant code changes. +* You want to keep the database access clean and simple, and separate from business logic. + +## Data Access Object Pattern Java Tutorials + +* [The DAO Pattern in Java(Baeldung)](https://www.baeldung.com/java-dao-pattern) +* [Data Access Object Pattern (TutorialsPoint)](https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm) + +## Real-World Applications of DAO Pattern in Java + +* Enterprise applications that require database interaction. +* Applications requiring data access to be adaptable to multiple storage types (relational databases, XML files, flat files, etc.). +* Frameworks providing generic data access functionalities. + +## Benefits and Trade-offs of Data Access Object Pattern + +Benefits: + +* Decoupling: Separates the data access logic from the business logic, enhancing modularity and clarity. +* Reusability: DAOs can be reused across different parts of the application or even in different projects. +* Testability: Simplifies testing by allowing business logic to be tested separately from the data access logic. +* Flexibility: Makes it easier to switch underlying storage mechanisms with minimal impact on the application code. + +Trade-offs: + +* Layer Complexity: Introduces additional layers to the application, which can increase complexity and development time. +* Overhead: For simple applications, the DAO pattern might introduce more overhead than necessary. +* Learning Curve: Developers might need time to understand and implement the pattern effectively, especially in complex projects. + +## Related Java Design Patterns + +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Helps in abstracting the creation of DAOs, especially when supporting multiple databases or storage mechanisms. +* [Factory](https://java-design-patterns.com/patterns/factory/): Can be used to instantiate DAOs dynamically, providing flexibility in the choice of implementation. +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/): Often used in conjunction with the DAO pattern to define application's boundaries and its set of available operations. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Might be employed to change the data access strategy at runtime, depending on the context. + +## References and Credits + +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/49u3r91) +* [Expert One-on-One J2EE Design and Development](https://amzn.to/3vK3pfq) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3U5cxEI) +* [Professional Java Development with the Spring Framework](https://amzn.to/49tANF0) diff --git a/dao/etc/dao.png b/data-access-object/etc/dao.png similarity index 100% rename from dao/etc/dao.png rename to data-access-object/etc/dao.png diff --git a/dao/etc/dao.ucls b/data-access-object/etc/dao.ucls similarity index 100% rename from dao/etc/dao.ucls rename to data-access-object/etc/dao.ucls diff --git a/dao/etc/dao.urm.puml b/data-access-object/etc/dao.urm.puml similarity index 100% rename from dao/etc/dao.urm.puml rename to data-access-object/etc/dao.urm.puml diff --git a/data-access-object/etc/data-access-object.urm.puml b/data-access-object/etc/data-access-object.urm.puml new file mode 100644 index 000000000000..5c6e5f703053 --- /dev/null +++ b/data-access-object/etc/data-access-object.urm.puml @@ -0,0 +1,69 @@ +@startuml +package com.iluwatar.dao { + class App { + - ALL_CUSTOMERS : String {static} + - DB_URL : String {static} + - LOGGER : Logger {static} + + App() + - addCustomers(customerDao : CustomerDao) {static} + - createDataSource() : DataSource {static} + - createSchema(dataSource : DataSource) {static} + - deleteSchema(dataSource : DataSource) {static} + + generateSampleCustomers() : List {static} + + main(args : String[]) {static} + - performOperationsUsing(customerDao : CustomerDao) {static} + } + class Customer { + - firstName : String + - id : int + - lastName : String + + Customer(id : int, firstName : String, lastName : String) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getFirstName() : String + + getId() : int + + getLastName() : String + + hashCode() : int + + setFirstName(firstName : String) + + setId(id : int) + + setLastName(lastName : String) + + toString() : String + } + interface CustomerDao { + + add(Customer) : boolean {abstract} + + delete(Customer) : boolean {abstract} + + getAll() : Stream {abstract} + + getById(int) : Optional {abstract} + + update(Customer) : boolean {abstract} + } + class CustomerSchemaSql { + + CREATE_SCHEMA_SQL : String {static} + + DELETE_SCHEMA_SQL : String {static} + - CustomerSchemaSql() + } + class DbCustomerDao { + - LOGGER : Logger {static} + - dataSource : DataSource + + DbCustomerDao(dataSource : DataSource) + + add(customer : Customer) : boolean + - createCustomer(resultSet : ResultSet) : Customer + + delete(customer : Customer) : boolean + + getAll() : Stream + + getById(id : int) : Optional + - getConnection() : Connection + - mutedClose(connection : Connection, statement : PreparedStatement, resultSet : ResultSet) + + update(customer : Customer) : boolean + } + class InMemoryCustomerDao { + - idToCustomer : Map + + InMemoryCustomerDao() + + add(customer : Customer) : boolean + + delete(customer : Customer) : boolean + + getAll() : Stream + + getById(id : int) : Optional + + update(customer : Customer) : boolean + } +} +DbCustomerDao ..|> CustomerDao +InMemoryCustomerDao ..|> CustomerDao +@enduml \ No newline at end of file diff --git a/dao/pom.xml b/data-access-object/pom.xml similarity index 90% rename from dao/pom.xml rename to data-access-object/pom.xml index 1d75f1f83e9b..f96bf54cd818 100644 --- a/dao/pom.xml +++ b/data-access-object/pom.xml @@ -32,8 +32,16 @@ java-design-patterns 1.26.0-SNAPSHOT - dao + data-access-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/dao/src/main/java/com/iluwatar/dao/App.java b/data-access-object/src/main/java/com/iluwatar/dao/App.java similarity index 97% rename from dao/src/main/java/com/iluwatar/dao/App.java rename to data-access-object/src/main/java/com/iluwatar/dao/App.java index f0d19749c64b..106b7458c9cf 100644 --- a/dao/src/main/java/com/iluwatar/dao/App.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/App.java @@ -66,14 +66,14 @@ public static void main(final String[] args) throws Exception { private static void deleteSchema(DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); } } private static void createSchema(DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); } } diff --git a/dao/src/main/java/com/iluwatar/dao/CustomException.java b/data-access-object/src/main/java/com/iluwatar/dao/CustomException.java similarity index 86% rename from dao/src/main/java/com/iluwatar/dao/CustomException.java rename to data-access-object/src/main/java/com/iluwatar/dao/CustomException.java index 4de6d9e43a4a..79470bd810ab 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomException.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/CustomException.java @@ -1,44 +1,37 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.dao; - -/** - * Custom exception. - */ -public class CustomException extends Exception { - - private static final long serialVersionUID = 1L; - - public CustomException() { - } - - public CustomException(String message) { - super(message); - } - - public CustomException(String message, Throwable cause) { - super(message, cause); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dao; + +import java.io.Serial; + +/** Custom exception. */ +public class CustomException extends Exception { + + @Serial private static final long serialVersionUID = 1L; + + public CustomException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dao/src/main/java/com/iluwatar/dao/Customer.java b/data-access-object/src/main/java/com/iluwatar/dao/Customer.java similarity index 92% rename from dao/src/main/java/com/iluwatar/dao/Customer.java rename to data-access-object/src/main/java/com/iluwatar/dao/Customer.java index b5024e86d8f3..17a15fc1b92f 100644 --- a/dao/src/main/java/com/iluwatar/dao/Customer.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/Customer.java @@ -30,9 +30,7 @@ import lombok.Setter; import lombok.ToString; -/** - * A customer POJO that represents the data that will be read from the data source. - */ +/** A customer POJO that represents the data that will be read from the data source. */ @Setter @Getter @ToString @@ -40,9 +38,7 @@ @AllArgsConstructor public class Customer { - @EqualsAndHashCode.Include - private int id; + @EqualsAndHashCode.Include private int id; private String firstName; private String lastName; - } diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerDao.java b/data-access-object/src/main/java/com/iluwatar/dao/CustomerDao.java similarity index 100% rename from dao/src/main/java/com/iluwatar/dao/CustomerDao.java rename to data-access-object/src/main/java/com/iluwatar/dao/CustomerDao.java diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java b/data-access-object/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java similarity index 89% rename from dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java rename to data-access-object/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java index 501b4155a670..aab41423a748 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java @@ -24,18 +24,13 @@ */ package com.iluwatar.dao; -/** - * Customer Schema SQL Class. - */ +/** Customer Schema SQL Class. */ public final class CustomerSchemaSql { - private CustomerSchemaSql() { - } + private CustomerSchemaSql() {} public static final String CREATE_SCHEMA_SQL = - "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " - + "LNAME VARCHAR(100))"; + "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " + "LNAME VARCHAR(100))"; public static final String DELETE_SCHEMA_SQL = "DROP TABLE CUSTOMERS"; - } diff --git a/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java b/data-access-object/src/main/java/com/iluwatar/dao/DbCustomerDao.java similarity index 76% rename from dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java rename to data-access-object/src/main/java/com/iluwatar/dao/DbCustomerDao.java index f369cb69ea62..cb75b195ddc4 100644 --- a/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/DbCustomerDao.java @@ -38,9 +38,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * An implementation of {@link CustomerDao} that persists customers in RDBMS. - */ +/** An implementation of {@link CustomerDao} that persists customers in RDBMS. */ @Slf4j @RequiredArgsConstructor public class DbCustomerDao implements CustomerDao { @@ -60,22 +58,24 @@ public Stream getAll() throws Exception { var connection = getConnection(); var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); // NOSONAR var resultSet = statement.executeQuery(); // NOSONAR - return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, - Spliterator.ORDERED) { - - @Override - public boolean tryAdvance(Consumer action) { - try { - if (!resultSet.next()) { - return false; - } - action.accept(createCustomer(resultSet)); - return true; - } catch (SQLException e) { - throw new RuntimeException(e); // NOSONAR - } - } - }, false).onClose(() -> mutedClose(connection, statement, resultSet)); + return StreamSupport.stream( + new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + + @Override + public boolean tryAdvance(Consumer action) { + try { + if (!resultSet.next()) { + return false; + } + action.accept(createCustomer(resultSet)); + return true; + } catch (SQLException e) { + throw new RuntimeException(e); // NOSONAR + } + } + }, + false) + .onClose(() -> mutedClose(connection, statement, resultSet)); } catch (SQLException e) { throw new CustomException(e.getMessage(), e); } @@ -96,21 +96,18 @@ private void mutedClose(Connection connection, PreparedStatement statement, Resu } private Customer createCustomer(ResultSet resultSet) throws SQLException { - return new Customer(resultSet.getInt("ID"), - resultSet.getString("FNAME"), - resultSet.getString("LNAME")); + return new Customer( + resultSet.getInt("ID"), resultSet.getString("FNAME"), resultSet.getString("LNAME")); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Optional getById(int id) throws Exception { ResultSet resultSet = null; try (var connection = getConnection(); - var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { + var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { statement.setInt(1, id); resultSet = statement.executeQuery(); @@ -128,9 +125,7 @@ public Optional getById(int id) throws Exception { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean add(Customer customer) throws Exception { if (getById(customer.getId()).isPresent()) { @@ -138,7 +133,7 @@ public boolean add(Customer customer) throws Exception { } try (var connection = getConnection(); - var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { + var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { statement.setInt(1, customer.getId()); statement.setString(2, customer.getFirstName()); statement.setString(3, customer.getLastName()); @@ -149,15 +144,12 @@ public boolean add(Customer customer) throws Exception { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean update(Customer customer) throws Exception { try (var connection = getConnection(); - var statement = - connection - .prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { + var statement = + connection.prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { statement.setString(1, customer.getFirstName()); statement.setString(2, customer.getLastName()); statement.setInt(3, customer.getId()); @@ -167,13 +159,11 @@ public boolean update(Customer customer) throws Exception { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean delete(Customer customer) throws Exception { try (var connection = getConnection(); - var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { + var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { statement.setInt(1, customer.getId()); return statement.executeUpdate() > 0; } catch (SQLException ex) { diff --git a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java b/data-access-object/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java similarity index 94% rename from dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java rename to data-access-object/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java index 19979ff666ad..b5b7eb0c4ff7 100644 --- a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java @@ -31,17 +31,14 @@ /** * An in memory implementation of {@link CustomerDao}, which stores the customers in JVM memory and - * data is lost when the application exits. - *
+ * data is lost when the application exits.
* This implementation is useful as temporary database or for testing. */ public class InMemoryCustomerDao implements CustomerDao { private final Map idToCustomer = new HashMap<>(); - /** - * An eagerly evaluated stream of customers stored in memory. - */ + /** An eagerly evaluated stream of customers stored in memory. */ @Override public Stream getAll() { return idToCustomer.values().stream(); diff --git a/dao/src/test/java/com/iluwatar/dao/AppTest.java b/data-access-object/src/test/java/com/iluwatar/dao/AppTest.java similarity index 82% rename from dao/src/test/java/com/iluwatar/dao/AppTest.java rename to data-access-object/src/test/java/com/iluwatar/dao/AppTest.java index ee8f4f4a389a..a43f37353341 100644 --- a/dao/src/test/java/com/iluwatar/dao/AppTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.dao; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that DAO example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that DAO example runs without errors. */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteDaoWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java b/data-access-object/src/test/java/com/iluwatar/dao/CustomerTest.java similarity index 92% rename from dao/src/test/java/com/iluwatar/dao/CustomerTest.java rename to data-access-object/src/test/java/com/iluwatar/dao/CustomerTest.java index 7ceab4eae11f..f53c28935fea 100644 --- a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/CustomerTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests {@link Customer}. - */ +/** Tests {@link Customer}. */ class CustomerTest { private Customer customer; @@ -89,7 +87,10 @@ void equalsWithSameObjects() { @Test void testToString() { - assertEquals(String.format("Customer(id=%s, firstName=%s, lastName=%s)", - customer.getId(), customer.getFirstName(), customer.getLastName()), customer.toString()); + assertEquals( + String.format( + "Customer(id=%s, firstName=%s, lastName=%s)", + customer.getId(), customer.getFirstName(), customer.getLastName()), + customer.toString()); } } diff --git a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java b/data-access-object/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java similarity index 90% rename from dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java rename to data-access-object/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java index cfa7e9882981..2f116d108519 100644 --- a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java @@ -44,9 +44,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -/** - * Tests {@link DbCustomerDao}. - */ +/** Tests {@link DbCustomerDao}. */ class DbCustomerDaoTest { private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1"; @@ -61,14 +59,12 @@ class DbCustomerDaoTest { @BeforeEach void createSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); } } - /** - * Represents the scenario where DB connectivity is present. - */ + /** Represents the scenario where DB connectivity is present. */ @Nested class ConnectionSuccess { @@ -87,7 +83,7 @@ void setUp() throws Exception { } /** - * Represents the scenario when DAO operations are being performed on a non existing customer. + * Represents the scenario when DAO operations are being performed on a non-existing customer. */ @Nested class NonExistingCustomer { @@ -160,8 +156,8 @@ void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception } @Test - void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws - Exception { + void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() + throws Exception { final var newFirstname = "Bernard"; final var newLastname = "Montgomery"; final var customer = new Customer(existingCustomer.getId(), newFirstname, newLastname); @@ -206,41 +202,32 @@ private DataSource mockedDatasource() throws SQLException { @Test void addingACustomerFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.add(new Customer(2, "Bernard", "Montgomery")); - }); + assertThrows(Exception.class, () -> dao.add(new Customer(2, "Bernard", "Montgomery"))); } @Test void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() { - assertThrows(Exception.class, () -> { - dao.delete(existingCustomer); - }); + assertThrows(Exception.class, () -> dao.delete(existingCustomer)); } @Test void updatingACustomerFailsWithFeedbackToTheClient() { final var newFirstname = "Bernard"; final var newLastname = "Montgomery"; - assertThrows(Exception.class, () -> { - dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname)); - }); + assertThrows( + Exception.class, + () -> dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname))); } @Test void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.getById(existingCustomer.getId()); - }); + assertThrows(Exception.class, () -> dao.getById(existingCustomer.getId())); } @Test void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.getAll(); - }); + assertThrows(Exception.class, () -> dao.getAll()); } - } /** @@ -251,7 +238,7 @@ void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { @AfterEach void deleteSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); } } diff --git a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java b/data-access-object/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java similarity index 95% rename from dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java rename to data-access-object/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java index 855dd01d3e0d..93ce2475f2c8 100644 --- a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -/** - * Tests {@link InMemoryCustomerDao}. - */ +/** Tests {@link InMemoryCustomerDao}. */ class InMemoryCustomerDaoTest { private InMemoryCustomerDao dao; @@ -48,8 +46,7 @@ void setUp() { } /** - * Represents the scenario when the DAO operations are being performed on a non existent - * customer. + * Represents the scenario when the DAO operations are being performed on a non-existent customer. */ @Nested class NonExistingCustomer { @@ -78,7 +75,7 @@ void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception { } @Test - void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception { + void updateShouldBeFailureAndNotAffectExistingCustomers() { final var nonExistingId = getNonExistingCustomerId(); final var newFirstname = "Douglas"; final var newLastname = "MacArthur"; @@ -121,8 +118,8 @@ void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception } @Test - void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws - Exception { + void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() + throws Exception { final var newFirstname = "Bernard"; final var newLastname = "Montgomery"; final var customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname); diff --git a/data-bus/README.md b/data-bus/README.md index 23585681b0d9..a033253cfc04 100644 --- a/data-bus/README.md +++ b/data-bus/README.md @@ -1,164 +1,191 @@ --- -title: Data Bus -category: Architectural +title: "Data Bus Pattern in Java: Unifying Component Communication Efficiently" +shortTitle: Data Bus +description: "Explore the Data Bus pattern in Java for centralized communication and event handling. Learn how to decouple components, enhance scalability, and maintainability with practical examples and real-world applications." +category: Messaging language: en tag: - - Decoupling + - Decoupling + - Event-driven + - Messaging + - Publish/subscribe + - Scalability --- -## Intent +## Also known as -Allows send of messages/events between components of an application -without them needing to know about each other. They only need to know -about the type of the message/event being sent. +* Event Bus +* Message Bus -## Explanation +## Intent of Data Bus Design Pattern -Real world example +The Data Bus design pattern aims to provide a centralized communication channel through which various components of a system can exchange data without being directly connected, thus promoting loose coupling and enhancing scalability and maintainability. -> Say you have an app that enables online bookings and participation of events. You want the app to send notifications such as event advertisements to everyone who is an ordinary member of the community or organisation holding the events. However, you do not want to send such notifications like advertisements to the event administrators or organisers but you desire to send them and them only the time whenever a new advertisement is sent to all members of the community. The Data Bus enables you to selectively notify people of a community by type, whether it be ordinary community members or event administrators, by making their classes or components only accept messages of a certain type. Ultimately, there is no need for the components or classes of ordinary community members nor administrators to know anything about you in terms of the classes or components you are using to notify the entire community except for the need to know the type of the messages you are sending. +## Detailed Explanation of Data Bus Pattern with Real-World Examples + +Real-world example + +> Consider a large airport as an analogous real-world example of the Data Bus pattern. In an airport, various airlines, passengers, baggage handlers, and security personnel all need to communicate and share information. Instead of each entity communicating directly with every other entity, the airport uses a centralized announcement system (the Data Bus). Flight information, security alerts, and other critical updates are broadcast over this system, and each entity listens for the messages relevant to them. This setup allows the airport to decouple the communication process, ensuring that each entity only receives the information they need, while allowing the system to scale and integrate new entities without disrupting the existing ones. This is similar to how the Data Bus pattern in Java promotes centralized communication and event handling, enhancing system scalability and maintainability. In plain words -> Data Bus is a design pattern that is able to connect components of an application for communication simply and solely by the type of message or event that may be transferred. +> Data Bus is a design pattern that connects components of an application for communication based on the type of message or event being transferred. This pattern promotes decoupling, making it easier to scale and maintain the system by allowing components to communicate without direct dependencies. + +## Programmatic Example of Data Bus Pattern in Java -Programmatic Example +Say you have an app that enables online bookings and participation in events. You want the app to send notifications, such as event advertisements, to all ordinary members of the community or organization holding the events. However, you do not want to send such advertisements to event administrators or organizers. Instead, you want to send them notifications about the timing of new advertisements sent to all members. The Data Bus enables you to selectively notify community members by type (ordinary members or event administrators) by making their classes or components only accept messages of a certain type. Thus, ordinary members and administrators do not need to know about each other or the specific classes or components used to notify the entire community, except for knowing the type of messages being sent. -Translating the online events app example above, we firstly have our Member interface and its implementations composed of MessageCollectorMember (the ordinary community members) and StatusMember (the event administrators or organisers). +In the online events app example above, we first define our `Member` interface and its implementations: `MessageCollectorMember` (ordinary community members) and `StatusMember` (event administrators or organizers). ```java public interface Member extends Consumer { - void accept(DataType event); + void accept(DataType event); } ``` -Then we have a databus to subscribe someone to be a member or unsubcribe, and also to publish an event so to notify every member in the community. +Next, we implement a data bus to subscribe or unsubscribe members and to publish events to notify all community members. ```java - public class DataBus { - - private static final DataBus INSTANCE = new DataBus(); - - private final Set listeners = new HashSet<>(); - - public static DataBus getInstance() { - return INSTANCE; - } - - /** - * Register a member with the data-bus to start receiving events. - * - * @param member The member to register - */ - public void subscribe(final Member member) { - - this.listeners.add(member); - } - - /** - * Deregister a member to stop receiving events. - * - * @param member The member to deregister - */ - public void unsubscribe(final Member member) { - this.listeners.remove(member); - } - - /** - * Publish and event to all members. - * - * @param event The event - */ - public void publish(final DataType event) { - event.setDataBus(this); - - listeners.forEach( - listener -> listener.accept(event)); - } +public class DataBus { + + private static final DataBus INSTANCE = new DataBus(); + + private final Set listeners = new HashSet<>(); + + public static DataBus getInstance() { + return INSTANCE; + } + + public void subscribe(final Member member) { + this.listeners.add(member); + } + + public void unsubscribe(final Member member) { + this.listeners.remove(member); + } + + public void publish(final DataType event) { + event.setDataBus(this); + + listeners.forEach( + listener -> listener.accept(event)); + } } ``` - -As you can see, the accept method is applied for each member under the publish method. - -Hence, as shown below, the accept method can be used to check the type of message to be published and successfully send/handle that message if the accept method has an instance for that message. Otherwise, the accept method cannot as is for the case of the MessageCollectorMember (the ordinary community members) when the type of message being sent is StartingData or StoppingData (information on the time whenever a new advertisement is sent to all members). + +The `accept` method is applied to each member in the `publish` method. + +For ordinary community members (`MessageCollectorMember`), the `accept` method can handle only `MessageData` type messages. ```java public class MessageCollectorMember implements Member { - private final String name; + private final String name; - private final List messages = new ArrayList<>(); + private final List messages = new ArrayList<>(); - public MessageCollectorMember(String name) { - this.name = name; - } + public MessageCollectorMember(String name) { + this.name = name; + } - @Override - public void accept(final DataType data) { - if (data instanceof MessageData) { - handleEvent((MessageData) data); + @Override + public void accept(final DataType data) { + if (data instanceof MessageData) { + handleEvent((MessageData) data); + } } - } +} ``` -However, the StatusMember(the event administrators or organisers) can accept such types of messages as +For event administrators or organizers (`StatusMember`), the `accept` method can handle `StartingData` and `StoppingData` type messages. ```java public class StatusMember implements Member { - private final int id; + private final int id; - private LocalDateTime started; + private LocalDateTime started; - private LocalDateTime stopped; - @Override - public void accept(final DataType data) { - if (data instanceof StartingData) { - handleEvent((StartingData) data); - } else if (data instanceof StoppingData) { - handleEvent((StoppingData) data); + private LocalDateTime stopped; + + public StatusMember(int id) { + this.id = id; + } + + @Override + public void accept(final DataType data) { + if (data instanceof StartingData) { + handleEvent((StartingData) data); + } else if (data instanceof StoppingData) { + handleEvent((StoppingData) data); + } + } +} +``` + +Here is the `App` class to demonstrate the Data Bus pattern in action: + +```java +class App { + + public static void main(String[] args) { + final var bus = DataBus.getInstance(); + bus.subscribe(new StatusMember(1)); + bus.subscribe(new StatusMember(2)); + final var foo = new MessageCollectorMember("Foo"); + final var bar = new MessageCollectorMember("Bar"); + bus.subscribe(foo); + bus.publish(StartingData.of(LocalDateTime.now())); } - } +} +``` + +When the data bus publishes a message, the output is as follows: + +``` +02:33:57.627 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 2 sees application started at 2022-10-26T02:33:57.613529100 +02:33:57.633 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 1 sees application started at 2022-10-26T02:33:57.613529100 ``` +As shown, `MessageCollectorMembers` only accept messages of type `MessageData`, so they do not see the `StartingData` or `StoppingData` messages, which are only visible to `StatusMember` (the event administrators or organizers). This selective message handling prevents ordinary community members from receiving administrative notifications. + +## When to Use the Data Bus Pattern in Java + +* When multiple components need to share data or events but direct coupling is undesirable. +* In complex, event-driven systems where the flow of information varies dynamically. +* In distributed systems where components might be deployed across different environments. +* In microservices architectures for inter-service communication. + +## Real-World Applications of Data Bus Pattern in Java + +* Event handling systems in large-scale applications. +* Microservices architectures for inter-service communication. +* Real-time data processing systems, such as stock trading platforms. +* In frameworks like Spring, particularly with its application event mechanism. -Thus, the data bus outputs as follows: - - ```java - class App { +## Benefits and Trade-offs of Data Bus Pattern - public static void main(String[] args) { - final var bus = DataBus.getInstance(); - bus.subscribe(new StatusMember(1)); - bus.subscribe(new StatusMember(2)); - final var foo = new MessageCollectorMember("Foo"); - final var bar = new MessageCollectorMember("Bar"); - bus.subscribe(foo); - bus.publish(StartingData.of(LocalDateTime.now())); - ``` - -//OUTPUT: +Benefits: -//02:33:57.627 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 2 sees application started at 2022-10-26T02:33:57.613529100 +* Loose Coupling: Components can interact without having direct dependencies on each other. +* Flexibility: New subscribers or publishers can be added without impacting existing components. +* Scalability: The pattern supports scaling components independently. +* Reusability: The bus and components can be reused across different systems. -//02:33:57.633 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 1 sees application started at 2022-10-26T02:33:57.613529100 +Trade-offs: -Evidently, due to MessageCollectorMembers only accepting messages of type MessageData and none of either StartingData nor StoppingData, the MessageCollectorMembers are prevented from seeing what the StatusMembers (the event administrators or organisers) are shown: information on the time whenever a new advertisement is sent to all members. - -## Class diagram -![data bus pattern uml diagram](./etc/data-bus.urm.png "Data Bus pattern") +* Complexity: Introducing a data bus can add complexity to the system architecture. +* Performance Overhead: The additional layer of communication may introduce latency. +* Debugging Difficulty: Tracing data flow through the bus can be challenging, especially in systems with many events. -## Applicability -Use Data Bus pattern when +## Related Java Design Patterns -* you want your components to decide themselves which messages/events they want to receive -* you want to have many-to-many communication -* you want your components to know nothing about each other +* [Mediator](https://java-design-patterns.com/patterns/mediator/): Facilitates communication between components, but unlike Data Bus, it centralizes control. +* [Observer](https://java-design-patterns.com/patterns/observer/): Similar in nature to the publish-subscribe mechanism used in Data Bus for notifying changes to multiple objects. +* Publish/Subscribe: The Data Bus pattern is often implemented using the publish-subscribe mechanism, where publishers post messages to the bus without knowledge of the subscribers. -## Related Patterns -Data Bus is similar to +## References and Credits -* Mediator pattern with Data Bus Members deciding for themselves if they want to accept any given message -* Observer pattern but supporting many-to-many communication -* Publish/Subscribe pattern with the Data Bus decoupling the publisher and the subscriber +* [Enterprise Integration Patterns](https://amzn.to/3J6WoYS) +* [Pattern-Oriented Software Architecture, Volume 4: A Pattern Language for Distributed Computing](https://amzn.to/3PTRGBM) diff --git a/data-bus/pom.xml b/data-bus/pom.xml index aef3a0cd6e48..ef7c88acb413 100644 --- a/data-bus/pom.xml +++ b/data-bus/pom.xml @@ -34,6 +34,14 @@ data-bus + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java b/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java index ecdd0b7562b2..aa1ce3d45598 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java +++ b/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java @@ -51,11 +51,7 @@ of this software and associated documentation files (the "Software"), to deal import lombok.Getter; import lombok.Setter; -/** - * Base for data to send via the Data-Bus. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** Base for data to send via the Data-Bus. */ @Getter @Setter public class AbstractDataType implements DataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/App.java b/data-bus/src/main/java/com/iluwatar/databus/App.java index a3f8b005b861..2c1d6b5e8a25 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/App.java +++ b/data-bus/src/main/java/com/iluwatar/databus/App.java @@ -34,28 +34,26 @@ /** * The Data Bus pattern. * - * @author Paul Campbell (pcampbell@kemitix.net) * @see http://wiki.c2.com/?DataBusPattern - *

The Data-Bus pattern provides a method where different parts of an application may - * pass messages between each other without needing to be aware of the other's existence.

+ *

The Data-Bus pattern provides a method where different parts of an application may pass + * messages between each other without needing to be aware of the other's existence. *

Similar to the {@code ObserverPattern}, members register themselves with the {@link * DataBus} and may then receive each piece of data that is published to the Data-Bus. The - * member may react to any given message or not.

- *

It allows for Many-to-Many distribution of data, as there may be any number of - * publishers to a Data-Bus, and any number of members receiving the data. All members will - * receive the same data, the order each receives a given piece of data, is an implementation - * detail.

- *

Members may unsubscribe from the Data-Bus to stop receiving data.

- *

This example of the pattern implements a Synchronous Data-Bus, meaning that - * when data is published to the Data-Bus, the publish method will not return until all members - * have received the data and returned.

- *

The {@link DataBus} class is a Singleton.

- *

Members of the Data-Bus must implement the {@link Member} interface.

- *

Data to be published via the Data-Bus must implement the {@link DataType} interface.

- *

The {@code data} package contains example {@link DataType} implementations.

- *

The {@code members} package contains example {@link Member} implementations.

- *

The {@link StatusMember} demonstrates using the DataBus to publish a message - * to the Data-Bus when it receives a message.

+ * member may react to any given message or not. + *

It allows for Many-to-Many distribution of data, as there may be any number of publishers + * to a Data-Bus, and any number of members receiving the data. All members will receive the + * same data, the order each receives a given piece of data, is an implementation detail. + *

Members may unsubscribe from the Data-Bus to stop receiving data. + *

This example of the pattern implements a Synchronous Data-Bus, meaning that when data is + * published to the Data-Bus, the publish method will not return until all members have received + * the data and returned. + *

The {@link DataBus} class is a Singleton. + *

Members of the Data-Bus must implement the {@link Member} interface. + *

Data to be published via the Data-Bus must implement the {@link DataType} interface. + *

The {@code data} package contains example {@link DataType} implementations. + *

The {@code members} package contains example {@link Member} implementations. + *

The {@link StatusMember} demonstrates using the DataBus to publish a message to the + * Data-Bus when it receives a message. */ class App { diff --git a/data-bus/src/main/java/com/iluwatar/databus/DataBus.java b/data-bus/src/main/java/com/iluwatar/databus/DataBus.java index a11172692351..f302d741f776 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/DataBus.java +++ b/data-bus/src/main/java/com/iluwatar/databus/DataBus.java @@ -30,9 +30,7 @@ /** * The Data-Bus implementation. * - *

This implementation uses a Singleton.

- * - * @author Paul Campbell (pcampbell@kemitix.net) + *

This implementation uses a Singleton. */ public class DataBus { diff --git a/data-bus/src/main/java/com/iluwatar/databus/DataType.java b/data-bus/src/main/java/com/iluwatar/databus/DataType.java index 2eac1f22bf9e..b84797bf03ea 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/DataType.java +++ b/data-bus/src/main/java/com/iluwatar/databus/DataType.java @@ -48,12 +48,7 @@ of this software and associated documentation files (the "Software"), to deal package com.iluwatar.databus; -/** - * Events are sent via the Data-Bus. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ - +/** Events are sent via the Data-Bus. */ public interface DataType { /** diff --git a/data-bus/src/main/java/com/iluwatar/databus/Member.java b/data-bus/src/main/java/com/iluwatar/databus/Member.java index 1230dbf9f05c..3d4e976409ac 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/Member.java +++ b/data-bus/src/main/java/com/iluwatar/databus/Member.java @@ -50,11 +50,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.function.Consumer; -/** - * Members receive events from the Data-Bus. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** Members receive events from the Data-Bus. */ public interface Member extends Consumer { void accept(DataType event); diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java b/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java index d8713a9d2b5a..970fc6973050 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java +++ b/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java @@ -29,11 +29,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; -/** - * An event raised when a string message is sent. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** An event raised when a string message is sent. */ @Getter @AllArgsConstructor public class MessageData extends AbstractDataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java b/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java index b166e971312e..554a0ff71780 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java +++ b/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java @@ -30,11 +30,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * An event raised when applications starts, containing the start time of the application. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** An event raised when applications starts, containing the start time of the application. */ @RequiredArgsConstructor @Getter public class StartingData extends AbstractDataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java b/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java index 51def0bedd28..090f312650a4 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java +++ b/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java @@ -30,11 +30,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * An event raised when applications stops, containing the stop time of the application. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** An event raised when applications stops, containing the stop time of the application. */ @RequiredArgsConstructor @Getter public class StoppingData extends AbstractDataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java index 747d40f27a60..8eb81da1ae8b 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java +++ b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java @@ -31,11 +31,7 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -/** - * Receiver of Data-Bus events that collects the messages from each {@link MessageData}. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** Receiver of Data-Bus events that collects the messages from each {@link MessageData}. */ @Slf4j public class MessageCollectorMember implements Member { diff --git a/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java b/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java index f6326677ed90..d6aab7730b02 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java +++ b/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java @@ -34,11 +34,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Receiver of Data-Bus events. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** Receiver of Data-Bus events. */ @Getter @Slf4j @RequiredArgsConstructor @@ -70,5 +66,4 @@ private void handleEvent(StoppingData data) { LOGGER.info("Receiver {} sending goodbye message", id); data.getDataBus().publish(MessageData.of(String.format("Goodbye cruel world from #%d!", id))); } - } diff --git a/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java index f7b6d2f9fdca..a948201b4d76 100644 --- a/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java +++ b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java @@ -32,18 +32,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Tests for {@link DataBus}. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** Tests for {@link DataBus}. */ class DataBusTest { - @Mock - private Member member; + @Mock private Member member; - @Mock - private DataType event; + @Mock private DataType event; @BeforeEach void setUp() { @@ -52,25 +46,24 @@ void setUp() { @Test void publishedEventIsReceivedBySubscribedMember() { - //given + // given final var dataBus = DataBus.getInstance(); dataBus.subscribe(member); - //when + // when dataBus.publish(event); - //then + // then then(member).should().accept(event); } @Test void publishedEventIsNotReceivedByMemberAfterUnsubscribing() { - //given + // given final var dataBus = DataBus.getInstance(); dataBus.subscribe(member); dataBus.unsubscribe(member); - //when + // when dataBus.publish(event); - //then + // then then(member).should(never()).accept(event); } - } diff --git a/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java b/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java index f0336a672855..e0f881108df7 100644 --- a/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java +++ b/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java @@ -32,34 +32,29 @@ import java.time.LocalDateTime; import org.junit.jupiter.api.Test; -/** - * Tests for {@link MessageCollectorMember}. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** Tests for {@link MessageCollectorMember}. */ class MessageCollectorMemberTest { @Test void collectMessageFromMessageData() { - //given + // given final var message = "message"; final var messageData = new MessageData(message); final var collector = new MessageCollectorMember("collector"); - //when + // when collector.accept(messageData); - //then + // then assertTrue(collector.getMessages().contains(message)); } @Test void collectIgnoresMessageFromOtherDataTypes() { - //given + // given final var startingData = new StartingData(LocalDateTime.now()); final var collector = new MessageCollectorMember("collector"); - //when + // when collector.accept(startingData); - //then + // then assertEquals(0, collector.getMessages().size()); } - } diff --git a/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java b/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java index cac4a63e8cf2..b7aa8b826084 100644 --- a/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java +++ b/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java @@ -35,48 +35,43 @@ import java.time.Month; import org.junit.jupiter.api.Test; -/** - * Tests for {@link StatusMember}. - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ +/** Tests for {@link StatusMember}. */ class StatusMemberTest { @Test void statusRecordsTheStartTime() { - //given + // given final var startTime = LocalDateTime.of(2017, Month.APRIL, 1, 19, 9); final var startingData = new StartingData(startTime); final var statusMember = new StatusMember(1); - //when + // when statusMember.accept(startingData); - //then + // then assertEquals(startTime, statusMember.getStarted()); } @Test void statusRecordsTheStopTime() { - //given + // given final var stop = LocalDateTime.of(2017, Month.APRIL, 1, 19, 12); final var stoppingData = new StoppingData(stop); stoppingData.setDataBus(DataBus.getInstance()); final var statusMember = new StatusMember(1); - //when + // when statusMember.accept(stoppingData); - //then + // then assertEquals(stop, statusMember.getStopped()); } @Test void statusIgnoresMessageData() { - //given + // given final var messageData = new MessageData("message"); final var statusMember = new StatusMember(1); - //when + // when statusMember.accept(messageData); - //then + // then assertNull(statusMember.getStarted()); assertNull(statusMember.getStopped()); } - } diff --git a/data-locality/README.md b/data-locality/README.md index 212bd663a08d..9056b2025b3c 100644 --- a/data-locality/README.md +++ b/data-locality/README.md @@ -1,29 +1,169 @@ --- -title: Data Locality -category: Behavioral +title: "Data Locality Pattern in Java: Boosting Performance with Efficient Data Management" +shortTitle: Data Locality +description: "Learn about the Data Locality pattern in Java, a technique to optimize performance by improving cache utilization and minimizing data access times. Ideal for game development and high-performance computing." +category: Performance optimization language: en tag: - - Game programming - - Performance + - Caching + - Data access + - Game programming + - Memory management + - Performance --- -## Intent -Accelerate memory access by arranging data to take advantage of CPU caching. +## Also known as -Modern CPUs have caches to speed up memory access. These can access memory adjacent to recently accessed memory much quicker. Take advantage of that to improve performance by increasing data locality keeping data in contiguous memory in the order that you process it. +* Cache-Friendly Design +* Data-Oriented Design -## Class diagram -![alt text](./etc/data-locality.urm.png "Data Locality pattern class diagram") +## Intent of Data Locality Design Pattern -## Applicability +The Data Locality design pattern aims to minimize data access times and improve cache utilization by arranging data in memory to take advantage of spatial locality. This pattern is particularly useful in high-performance computing, real-time data processing, and game development where access speed is crucial. -* Like most optimizations, the first guideline for using the Data Locality pattern is when you have a performance problem. -* With this pattern specifically, you’ll also want to be sure your performance problems are caused by cache misses. +## Detailed Explanation of Data Locality Pattern with Real-World Examples -## Real world example +Real-world example -* The [Artemis](http://gamadu.com/artemis/) game engine is one of the first and better-known frameworks that uses simple IDs for game entities. +> Consider a supermarket where items are arranged based on purchase patterns and categories for efficiency. Just like the Data Locality pattern organizes data in memory for quick access, the supermarket places frequently bought items together and in easily accessible areas. This layout minimizes the time shoppers spend searching for items, enhancing their shopping experience by ensuring that related and popular items are close at hand, much like how data locality improves cache utilization and reduces access latency in computing. -## Credits +In plain words -* [Game Programming Patterns Optimization Patterns: Data Locality](http://gameprogrammingpatterns.com/data-locality.html) \ No newline at end of file +> The Data Locality pattern organizes data in memory to reduce access times and improve performance by ensuring that data frequently accessed together is stored close together. This is crucial for high-performance applications like game engines and real-time data processing systems. + +## Programmatic Example of Data Locality Pattern in Java + +The Data Locality pattern is a design pattern that aims to improve performance by arranging data in memory to take advantage of spatial locality. This pattern is particularly useful in high-performance computing and game development where access speed is crucial. + +In the data-locality module, the pattern is demonstrated using a game loop that processes a bunch of game entities. These entities are decomposed into different domains: AI, physics, and rendering. + +The `GameEntity` class is the main class that represents a game entity. It contains an array of `AiComponent`, `PhysicsComponent`, and `RenderComponent` objects. These components represent different aspects of a game entity. + +```java +public class GameEntity { + private final AiComponent[] aiComponents; + private final PhysicsComponent[] physicsComponents; + private final RenderComponent[] renderComponents; + // Other properties and methods... +} +``` + +The `GameEntity` class has a start method that initializes all the components. + +```java +public void start() { + for (int i = 0; i < numEntities; i++) { + aiComponents[i] = new AiComponent(); + physicsComponents[i] = new PhysicsComponent(); + renderComponents[i] = new RenderComponent(); + } +} +``` + +The `GameEntity` class also has an update method that updates all the components. This method demonstrates the data locality pattern. Instead of updating all aspects of a single entity at a time (AI, physics, and rendering), it updates the same aspect (e.g., AI) for all entities first, then moves on to the next aspect (e.g., physics). This approach improves cache utilization because it's more likely that the data needed for the update is already in the cache. + +```java +public void update() { + for (int i = 0; i < numEntities; i++) { + aiComponents[i].update(); + } + for (int i = 0; i < numEntities; i++) { + physicsComponents[i].update(); + } + for (int i = 0; i < numEntities; i++) { + renderComponents[i].update(); + } +} +``` + +The `Application` class contains the main method that creates a `GameEntity` object and starts the game loop. + +```java +public class Application { + public static void main(String[] args) { + var gameEntity = new GameEntity(NUM_ENTITIES); + gameEntity.start(); + gameEntity.update(); + } +} +``` + +The console output: + +``` +10:19:52.155 [main] INFO com.iluwatar.data.locality.Application -- Start Game Application using Data-Locality pattern +10:19:52.157 [main] INFO com.iluwatar.data.locality.game.GameEntity -- Init Game with #Entity : 5 +10:19:52.158 [main] INFO com.iluwatar.data.locality.game.GameEntity -- Start Game +10:19:52.158 [main] INFO com.iluwatar.data.locality.game.component.manager.AiComponentManager -- Start AI Game Component +10:19:52.158 [main] INFO com.iluwatar.data.locality.game.component.manager.PhysicsComponentManager -- Start Physics Game Component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.manager.RenderComponentManager -- Start Render Game Component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.GameEntity -- Update Game Component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.manager.AiComponentManager -- Update AI Game Component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.AiComponent -- update AI component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.AiComponent -- update AI component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.AiComponent -- update AI component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.AiComponent -- update AI component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.AiComponent -- update AI component +10:19:52.159 [main] INFO com.iluwatar.data.locality.game.component.manager.PhysicsComponentManager -- Update Physics Game Component +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.PhysicsComponent -- Update physics component of game +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.PhysicsComponent -- Update physics component of game +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.PhysicsComponent -- Update physics component of game +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.PhysicsComponent -- Update physics component of game +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.PhysicsComponent -- Update physics component of game +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.manager.RenderComponentManager -- Update Render Game Component +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.RenderComponent -- Render Component +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.RenderComponent -- Render Component +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.RenderComponent -- Render Component +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.RenderComponent -- Render Component +10:19:52.160 [main] INFO com.iluwatar.data.locality.game.component.RenderComponent -- Render Component +``` + +In this way, the data-locality module demonstrates the Data Locality pattern. By updating all components of the same type together, it increases the likelihood that the data needed for the update is already in the cache, thereby improving performance. + +## Detailed Explanation of Data Locality Pattern with Real-World Examples + +![Data Locality](./etc/data-locality.urm.png "Data Locality pattern class diagram") + +## When to Use the Data Locality Pattern in Java + +This pattern is applicable in scenarios where large datasets are processed and performance is critical. It's particularly useful in: + +* Game development for efficient rendering and physics calculations. +* High-performance computing tasks that require rapid access to large data sets. +* Real-time data processing systems where latency is a critical factor. +* Scientific computing applications needing optimized matrix operations. +* Data-intensive applications requiring enhanced memory access patterns. + +## Real-World Applications of Data Locality Pattern in Java + +* Game engines (e.g., Unity, Unreal Engine) to optimize entity and component data access. +* High-performance matrix libraries in scientific computing to optimize matrix operations. +* Real-time streaming data processing systems for efficient data manipulation and access. + +## Benefits and Trade-offs of Data Locality Pattern + +Benefits: + +* Improved Cache Utilization: By enhancing spatial locality, data frequently accessed together is stored close together in memory, improving cache hit rates. +* Reduced Access Latency: Minimizes the time taken to fetch data from memory, leading to performance improvements. +* Enhanced Performance: Overall system performance is improved due to reduced memory access times and increased efficiency in data processing. + +Trade-offs: + +* Complexity in Implementation: Managing data layout can add complexity to the system design and implementation. +* Maintenance Overhead: As data access patterns evolve, the layout may need to be re-evaluated, adding to the maintenance overhead. +* Less Flexibility: The tight coupling of data layout to access patterns can reduce flexibility in how data structures are used and evolved over time. + +## Related Java Design Patterns + +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Can be used in conjunction with Data Locality to share data efficiently among multiple objects. +* [Object Pool](https://java-design-patterns.com/patterns/object-pool/): Often used together to manage a group of initialized objects that can be reused, further optimizing memory usage and access. +* [Iterator](https://java-design-patterns.com/patterns/iterator/): Facilitates navigation through a collection of data laid out with data locality in mind. + +## References and Credits + +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Game Programming Patterns](https://amzn.to/3vK8c0d) +* [High-Performance Java Persistence](https://amzn.to/3TMc8Wd) +* [Java Performance: The Definitive Guide](https://amzn.to/3Ua392J) +* [Game Programming Patterns Optimization Patterns: Data Locality](http://gameprogrammingpatterns.com/data-locality.html) diff --git a/data-locality/pom.xml b/data-locality/pom.xml index c5ba5531f1b5..593f6793473a 100644 --- a/data-locality/pom.xml +++ b/data-locality/pom.xml @@ -34,6 +34,14 @@ data-locality + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/Application.java b/data-locality/src/main/java/com/iluwatar/data/locality/Application.java index 1e87c8e3cc3e..862fa11732bf 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/Application.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/Application.java @@ -32,17 +32,15 @@ * improve performance by increasing data locality — keeping data in contiguous memory in the order * that you process it. * - *

Example: Game loop that processes a bunch of game entities. Those entities are decomposed - * into different domains  — AI, physics, and rendering — using the Component pattern. + *

Example: Game loop that processes a bunch of game entities. Those entities are decomposed into + * different domains  — AI, physics, and rendering — using the Component pattern. */ @Slf4j public class Application { private static final int NUM_ENTITIES = 5; - /** - * Start game loop with each component have NUM_ENTITIES instance. - */ + /** Start game loop with each component have NUM_ENTITIES instance. */ public static void main(String[] args) { LOGGER.info("Start Game Application using Data-Locality pattern"); var gameEntity = new GameEntity(NUM_ENTITIES); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java index 06989e6f68e0..92ce918a7345 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java @@ -46,9 +46,7 @@ public class GameEntity { private final PhysicsComponentManager physicsComponentManager; private final RenderComponentManager renderComponentManager; - /** - * Init components. - */ + /** Init components. */ public GameEntity(int numEntities) { LOGGER.info("Init Game with #Entity : {}", numEntities); aiComponentManager = new AiComponentManager(numEntities); @@ -56,9 +54,7 @@ public GameEntity(int numEntities) { renderComponentManager = new RenderComponentManager(numEntities); } - /** - * start all component. - */ + /** start all component. */ public void start() { LOGGER.info("Start Game"); aiComponentManager.start(); @@ -66,9 +62,7 @@ public void start() { renderComponentManager.start(); } - /** - * update all component. - */ + /** update all component. */ public void update() { LOGGER.info("Update Game Component"); // Process AI. @@ -80,5 +74,4 @@ public void update() { // Draw to screen. renderComponentManager.render(); } - } diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java index efa8a948da59..2f3cb043fcb1 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of AI component for Game. - */ +/** Implementation of AI component for Game. */ @Slf4j public class AiComponent implements Component { - /** - * Update ai component. - */ + /** Update ai component. */ @Override public void update() { LOGGER.info("update AI component"); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java index 263d09281af5..5775804fbc05 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java @@ -24,9 +24,7 @@ */ package com.iluwatar.data.locality.game.component; -/** - * Implement different Game component update and render process. - */ +/** Implement different Game component update and render process. */ public interface Component { void update(); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java index 632836ea366a..43b762aaf4ed 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of Physics Component of Game. - */ +/** Implementation of Physics Component of Game. */ @Slf4j public class PhysicsComponent implements Component { - /** - * update physics component of game. - */ + /** update physics component of game. */ @Override public void update() { LOGGER.info("Update physics component of game"); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java index 45f61eba1285..c04b2bc8fa80 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of Render Component of Game. - */ +/** Implementation of Render Component of Game. */ @Slf4j public class RenderComponent implements Component { @@ -37,9 +35,7 @@ public void update() { // do nothing } - /** - * render. - */ + /** render. */ @Override public void render() { LOGGER.info("Render Component"); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java index 90c234a97202..a69d77b13da4 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java @@ -29,9 +29,7 @@ import java.util.stream.IntStream; import lombok.extern.slf4j.Slf4j; -/** - * AI component manager for Game. - */ +/** AI component manager for Game. */ @Slf4j public class AiComponentManager { @@ -45,17 +43,13 @@ public AiComponentManager(int numEntities) { this.numEntities = numEntities; } - /** - * start AI component of Game. - */ + /** start AI component of Game. */ public void start() { LOGGER.info("Start AI Game Component"); IntStream.range(0, numEntities).forEach(i -> aiComponents[i] = new AiComponent()); } - /** - * Update AI component of Game. - */ + /** Update AI component of Game. */ public void update() { LOGGER.info("Update AI Game Component"); IntStream.range(0, numEntities) diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java index 58c85f2fd05f..c9b1a9bbeb10 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java @@ -29,9 +29,7 @@ import java.util.stream.IntStream; import lombok.extern.slf4j.Slf4j; -/** - * Physics component Manager for Game. - */ +/** Physics component Manager for Game. */ @Slf4j public class PhysicsComponentManager { @@ -45,18 +43,13 @@ public PhysicsComponentManager(int numEntities) { this.numEntities = numEntities; } - /** - * Start physics component of Game. - */ + /** Start physics component of Game. */ public void start() { LOGGER.info("Start Physics Game Component "); IntStream.range(0, numEntities).forEach(i -> physicsComponents[i] = new PhysicsComponent()); } - - /** - * Update physics component of Game. - */ + /** Update physics component of Game. */ public void update() { LOGGER.info("Update Physics Game Component "); // Process physics. diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java index 40cd0815b2b1..cff4bc3ac40a 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java @@ -29,9 +29,7 @@ import java.util.stream.IntStream; import lombok.extern.slf4j.Slf4j; -/** - * Render component manager for Game. - */ +/** Render component manager for Game. */ @Slf4j public class RenderComponentManager { @@ -45,18 +43,13 @@ public RenderComponentManager(int numEntities) { this.numEntities = numEntities; } - /** - * Start render component. - */ + /** Start render component. */ public void start() { LOGGER.info("Start Render Game Component "); IntStream.range(0, numEntities).forEach(i -> renderComponents[i] = new RenderComponent()); } - - /** - * render component. - */ + /** render component. */ public void render() { LOGGER.info("Update Render Game Component "); // Process Render. diff --git a/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java index 1f5986c9f2bc..badf5aadd027 100644 --- a/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java +++ b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java @@ -24,25 +24,20 @@ */ package com.iluwatar.data.locality; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Test Game Application - */ +/** Test Game Application */ class ApplicationTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link Application#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link Application#main(String[])} throws an + * exception. */ - @Test void shouldExecuteGameApplicationWithoutException() { - assertDoesNotThrow(() -> Application.main(new String[]{})); + assertDoesNotThrow(() -> Application.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/data-mapper/README.md b/data-mapper/README.md index 7f8b141ee71e..92c8e30211ea 100644 --- a/data-mapper/README.md +++ b/data-mapper/README.md @@ -1,201 +1,167 @@ --- -title: Data Mapper -category: Architectural +title: "Data Mapper Pattern in Java: Decoupling Data Storage from Business Logic" +shortTitle: Data Mapper +description: "Explore the Data Mapper pattern in Java, which decouples database operations from business logic. Learn how to implement and utilize this pattern to enhance maintainability and flexibility in your Java applications." +category: Behavioral language: en tag: -- Decoupling + - Data access + - Decoupling + - Domain + - Object mapping + - Persistence --- -## Intent ->Data Mapper is the software layer that separates the in-memory objects from the database. ->Its responsibility is to transfer data between the objects and database and isolate them from each other. ->If we obtain a Data Mapper, it is not necessary for the in-memory object to know if the database exists or not. ->The user could directly manipulate the objects via Java command without having knowledge of SQL or database. +## Also known as -## Explanation +* Object-Relational Mapping (ORM) -Real world example ->When a user accesses a specific web page through a browser, he only needs to do several operations to the browser. -> The browser and the server will take the responsibility of saving data separately. -> You don't need to know the existence of the server or how to operate the server. +## Intent of the Data Mapper Pattern -In plain words ->A layer of mappers that moves data between objects and a database while keeping them independent of each other. - -Wikipedia says ->A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store -> (often a relational database) and an in-memory data representation (the domain layer). The goal of the pattern is to -> keep the in-memory representation and the persistent data store independent of each other and the data mapper itself. -> This is useful when one needs to model and enforce strict business processes on the data in the domain layer that do -> not map neatly to the persistent data store. +The Data Mapper pattern aims to create an abstraction layer between the database and the business logic, allowing them to evolve independently. It maps data from the database objects to in-memory data structures and vice versa, minimizing direct dependencies between the application's core logic and the underlying database structure. This decoupling is essential in promoting maintainability and flexibility in Java programming. -**Programmatic Example** +## Detailed Explanation of Data Mapper Pattern with Real-World Examples ->We have the student class to defining Students' attributes includes studentId, name and grade. ->We have an interface of StudentDataMapper to lists out the possible behaviour for all possible student mappers. ->And StudentDataMapperImpl class for the implementation of actions on Students Data. - -```java +Real-world example -public final class Student implements Serializable { +> Consider a library system where books are stored in a database with complex relationships and various attributes such as title, author, publication year, and genre. In this scenario, a Data Mapper pattern can be used to map the database records of books to in-memory objects in the application. The Data Mapper would handle the retrieval, storage, and update of book objects in the database, allowing the application's business logic to work directly with the book objects without concern for the underlying database structure. This separation makes it easier to change the database schema without affecting the business logic, and vice versa. - private static final long serialVersionUID = 1L; +In plain words - @EqualsAndHashCode.Include - private int studentId; - private String name; - private char grade; +> A layer of mappers that moves data between objects and a database while keeping them independent of each other. +Wikipedia says - public interface StudentDataMapper { +> A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in-memory data representation (the domain layer). The goal of the pattern is to keep the in-memory representation and the persistent data store independent of each other and the data mapper itself. This is useful when one needs to model and enforce strict business processes on the data in the domain layer that do not map neatly to the persistent data store. - Optional find(int studentId); +## Programmatic Example of Data Mapper Pattern in Java - void insert(Student student) throws DataMapperException; +The Data Mapper is a design pattern that separates the in-memory objects from the database. Its responsibility is to transfer data between the two and also to isolate them from each other. This pattern promotes the [Single Responsibility Principle](https://java-design-patterns.com/principles/#single-responsibility-principle) and [Separation of Concerns](https://java-design-patterns.com/principles/#separation-of-concerns). - void update(Student student) throws DataMapperException; +In the data-mapper module, the pattern is demonstrated using a `Student` class and a `StudentDataMapper` interface. - void delete(Student student) throws DataMapperException; - } +The `Student` class is a simple POJO (Plain Old Java Object) that represents a student. It has properties like studentId, name, and grade. - public final class StudentDataMapperImpl implements StudentDataMapper { - @Override - public Optional find(int studentId) { - return this.getStudents().stream().filter(x -> x.getStudentId() == studentId).findFirst(); - } - - @Override - public void update(Student studentToBeUpdated) throws DataMapperException { - String name = studentToBeUpdated.getName(); - Integer index = Optional.of(studentToBeUpdated) - .map(Student::getStudentId) - .flatMap(this::find) - .map(students::indexOf) - .orElseThrow(() -> new DataMapperException("Student [" + name + "] is not found")); - students.set(index, studentToBeUpdated); - } - - @Override - public void insert(Student studentToBeInserted) throws DataMapperException { - Optional student = find(studentToBeInserted.getStudentId()); - if (student.isPresent()) { - String name = studentToBeInserted.getName(); - throw new DataMapperException("Student already [" + name + "] exists"); - } - - students.add(studentToBeInserted); - } - - @Override - public void delete(Student studentToBeDeleted) throws DataMapperException { - if (!students.remove(studentToBeDeleted)) { - String name = studentToBeDeleted.getName(); - throw new DataMapperException("Student [" + name + "] is not found"); - } - } - - public List getStudents() { - return this.students; - } - } +```java +public class Student { + private int studentId; + private String name; + private char grade; + // ... } - ``` ->The below example demonstrates basic CRUD operations: Create, Read, Update, and Delete between the in-memory objects -> and the database. +The `StudentDataMapper` interface defines the operations that can be performed on `Student` objects. These operations include `insert`, `update`, `delete`, and `find`. ```java -@Slf4j -public final class App { - - private static final String STUDENT_STRING = "App.main(), student : "; - - public static void main(final String... args) { +public interface StudentDataMapper { + void insert(final Student student); - final var mapper = new StudentDataMapperImpl(); + void update(final Student student); - var student = new Student(1, "Adam", 'A'); + void delete(final Student student); - mapper.insert(student); - - LOGGER.debug(STUDENT_STRING + student + ", is inserted"); + Optional find(final int studentId); + // ... +} +``` - final var studentToBeFound = mapper.find(student.getStudentId()); +The `StudentDataMapperImpl` class implements the `StudentDataMapper` interface. It contains the actual logic for interacting with the database. - LOGGER.debug(STUDENT_STRING + studentToBeFound + ", is searched"); +```java +public class StudentDataMapperImpl implements StudentDataMapper { + // ... + @Override + public void insert(final Student student) { + // Insert student into the database + } - student = new Student(student.getStudentId(), "AdamUpdated", 'A'); + @Override + public void update(final Student student) { + // Update student in the database + } - mapper.update(student); + @Override + public void delete(final Student student) { + // Delete student from the database + } - LOGGER.debug(STUDENT_STRING + student + ", is updated"); - LOGGER.debug(STUDENT_STRING + student + ", is going to be deleted"); + @Override + public Optional find(final int studentId) { + // Find student in the database + } + // ... +} +``` - mapper.delete(student); - } +The `App` class contains the main method that demonstrates the use of the `StudentDataMapper`. It creates a `Student` object, inserts it into the database, finds it, updates it, and finally deletes it. - private App() { - } +```java +public class App { + public static void main(final String... args) { + final var mapper = new StudentDataMapperImpl(); + var student = new Student(1, "Adam", 'A'); + mapper.insert(student); + final var studentToBeFound = mapper.find(student.getStudentId()); + student = new Student(student.getStudentId(), "AdamUpdated", 'A'); + mapper.update(student); + mapper.delete(student); + } } ``` Program output: -15:05:00.264 [main] DEBUG com.iluwatar.datamapper.App - App.main(), student : Student(studentId=1, name=Adam, grade=A), is inserted -15:05:00.267 [main] DEBUG com.iluwatar.datamapper.App - App.main(), student : Optional[Student(studentId=1, name=Adam, grade=A)], is searched -15:05:00.268 [main] DEBUG com.iluwatar.datamapper.App - App.main(), student : Student(studentId=1, name=AdamUpdated, grade=A), is updated -15:05:00.268 [main] DEBUG com.iluwatar.datamapper.App - App.main(), student : Student(studentId=1, name=AdamUpdated, grade=A), is going to be deleted - - - -This layer consists of one or more mappers (or data access objects) that perform data transfer. The scope of mapper implementations varies. -A generic mapper will handle many different domain entity types, a dedicated mapper will handle one or a few. - -## Explanation - -Real-world example +``` +13:54:29.234 [main] DEBUG com.iluwatar.datamapper.App -- App.main(), student : Student(studentId=1, name=Adam, grade=A), is inserted +13:54:29.237 [main] DEBUG com.iluwatar.datamapper.App -- App.main(), student : Optional[Student(studentId=1, name=Adam, grade=A)], is searched +13:54:29.237 [main] DEBUG com.iluwatar.datamapper.App -- App.main(), student : Student(studentId=1, name=AdamUpdated, grade=A), is updated +13:54:29.238 [main] DEBUG com.iluwatar.datamapper.App -- App.main(), student : Student(studentId=1, name=AdamUpdated, grade=A), is going to be deleted +``` -> When accessing web resources through a browser, there is generally no need to interact with the server directly; -> the browser and the proxy server will complete the data acquisition operation, and the three will remain independent. +## When to Use the Data Mapper Pattern in Java -In plain words +Use the Data Mapper in any of the following situations -> The data mapper will help complete the bi-directional transfer of persistence layer and in-memory data. +* When there's a need to decouple the in-memory objects from the database entities to promote the [Single Responsibility Principle](https://java-design-patterns.com/principles/#single-responsibility-principle) and [Separation of Concerns](https://java-design-patterns.com/principles/#separation-of-concerns). +* In applications requiring an ORM tool to bridge the gap between object-oriented models and relational databases. +* When working with complex database schemas where direct data manipulation and object creation lead to cumbersome and error-prone code. -Wikipedia says +## Data Mapper Pattern Java Tutorials -> A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a -> persistent data store (often a relational database) and an in-memory data representation (the domain layer). +* [Spring Boot RowMapper (ZetCode)](https://zetcode.com/springboot/rowmapper/) +* [Spring BeanPropertyRowMapper tutorial (ZetCode)](https://zetcode.com/spring/beanpropertyrowmapper/) +* [Data Transfer Object Pattern in Java - Implementation and Mapping (StackAbuse)](https://stackabuse.com/data-transfer-object-pattern-in-java-implementation-and-mapping/) -Programmatic example +## Real-World Applications of Data Mapper Pattern in Java -## Class diagram -![alt text](./etc/data-mapper.png "Data Mapper") +* ORM frameworks such as Hibernate in Java. +* Data access layers in enterprise applications where business logic and database management are kept separate. +* Applications requiring database interactions without tying the code to a specific database implementation. -## Applicability -Use the Data Mapper in any of the following situations +## Benefits and Trade-offs of Data Mapper Pattern -* when you want to decouple data objects from DB access layer -* when you want to write multiple data retrieval/persistence implementations +Benefits: -## Tutorials +* Promotes [Single Responsibility Principle](https://java-design-patterns.com/principles/#single-responsibility-principle) by separating persistence logic from business logic. +* Enhances maintainability and readability by centralizing data interaction logic. +* Improves application's ability to adapt to changes in the database schema with minimal changes to the business logic. -* [Spring Boot RowMapper](https://zetcode.com/springboot/rowmapper/) -* [Spring BeanPropertyRowMapper tutorial](https://zetcode.com/spring/beanpropertyrowmapper/) -* [Data Transfer Object Pattern in Java - Implementation and Mapping (Tutorial for Model Mapper & MapStruct)](https://stackabuse.com/data-transfer-object-pattern-in-java-implementation-and-mapping/) +Trade-offs: -## Known uses -* [SqlSession.getMapper()](https://mybatis.org/mybatis-3/java-api.html) +* Introduces complexity through the additional abstraction layer. +* Might lead to performance overhead due to the abstraction layer, especially in large-scale applications or with complex queries. +* Requires developers to learn and understand the abstraction layer in addition to the database and ORM framework being used. -## Consequences +## Related Java Design Patterns -> Neatly mapped persistence layer data -> Data model follows the single function principle +* Active Record: Combines data access logic and business logic in the domain entities themselves, contrary to Data Mapper's separation of concerns. +* Object–Relational Mapping (ORM): A technique to map object-oriented programming language data to a relational database. +* [Repository](https://java-design-patterns.com/patterns/repository/): Provides an abstraction of the data layer, acting as a collection of domain objects in memory. +* [Unit of Work](https://java-design-patterns.com/patterns/unit-of-work/): Manages transactions and keeps track of the objects affected by a business transaction to ensure changes are consistent and transactional. -## Related patterns -* [Active Record Pattern](https://en.wikipedia.org/wiki/Active_record_pattern) -* [Object–relational Mapping](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) +## References and Credits -## Credits -* [Data Mapper](http://richard.jp.leguen.ca/tutoring/soen343-f2010/tutorials/implementing-data-mapper/) +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3xyEFag) +* [Java Persistence with Hibernate](https://amzn.to/3VNzlKe) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/data-mapper/pom.xml b/data-mapper/pom.xml index 0c7907cb53cf..40e8c3cee51d 100644 --- a/data-mapper/pom.xml +++ b/data-mapper/pom.xml @@ -34,6 +34,14 @@ data-mapper + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java index 5969ee2f2a46..3a7ed69f9711 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java @@ -77,6 +77,5 @@ public static void main(final String... args) { mapper.delete(student); } - private App() { - } + private App() {} } diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java index 3e5419286f70..f02dcef33dfe 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java @@ -24,22 +24,22 @@ */ package com.iluwatar.datamapper; +import java.io.Serial; + /** * Using Runtime Exception for avoiding dependency on implementation exceptions. This helps in * decoupling. - * - * @author amit.dixit */ public final class DataMapperException extends RuntimeException { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; /** * Constructs a new runtime exception with the specified detail message. The cause is not * initialized, and may subsequently be initialized by a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for later retrieval by the - * {@link #getMessage()} method. + * {@link #getMessage()} method. */ public DataMapperException(final String message) { super(message); diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java index a2c1007854bf..8d8a42116a06 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java @@ -1,51 +1,48 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.datamapper; - -import java.io.Serializable; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -/** - * Class defining Student. - */ -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@ToString -@Getter -@Setter -@AllArgsConstructor -public final class Student implements Serializable { - - private static final long serialVersionUID = 1L; - - @EqualsAndHashCode.Include - private int studentId; - private String name; - private char grade; - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.datamapper; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** Class defining Student. */ +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +@Getter +@Setter +@AllArgsConstructor +public final class Student implements Serializable { + + @Serial private static final long serialVersionUID = 1L; + + @EqualsAndHashCode.Include private int studentId; + private String name; + private char grade; +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java index 2700e30f0bc4..ab28bd3dc4e7 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java @@ -1,41 +1,39 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.datamapper; - -import java.util.Optional; - -/** - * Interface lists out the possible behaviour for all possible student mappers. - */ -public interface StudentDataMapper { - - Optional find(int studentId); - - void insert(Student student) throws DataMapperException; - - void update(Student student) throws DataMapperException; - - void delete(Student student) throws DataMapperException; -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.datamapper; + +import java.util.Optional; + +/** Interface lists out the possible behaviour for all possible student mappers. */ +public interface StudentDataMapper { + + Optional find(int studentId); + + void insert(Student student) throws DataMapperException; + + void update(Student student) throws DataMapperException; + + void delete(Student student) throws DataMapperException; +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java index 9f7d3c9d967a..67ddc0ce6f1a 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java @@ -27,10 +27,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import lombok.Getter; -/** - * Implementation of Actions on Students Data. - */ +/** Implementation of Actions on Students Data. */ +@Getter public final class StudentDataMapperImpl implements StudentDataMapper { /* Note: Normally this would be in the form of an actual database */ @@ -44,11 +44,12 @@ public Optional find(int studentId) { @Override public void update(Student studentToBeUpdated) throws DataMapperException { String name = studentToBeUpdated.getName(); - Integer index = Optional.of(studentToBeUpdated) - .map(Student::getStudentId) - .flatMap(this::find) - .map(students::indexOf) - .orElseThrow(() -> new DataMapperException("Student [" + name + "] is not found")); + Integer index = + Optional.of(studentToBeUpdated) + .map(Student::getStudentId) + .flatMap(this::find) + .map(students::indexOf) + .orElseThrow(() -> new DataMapperException("Student [" + name + "] is not found")); students.set(index, studentToBeUpdated); } @@ -70,8 +71,4 @@ public void delete(Student studentToBeDeleted) throws DataMapperException { throw new DataMapperException("Student [" + name + "] is not found"); } } - - public List getStudents() { - return this.students; - } } diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java index 1f954ff811c8..a7118721dc36 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java @@ -1,49 +1,44 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.datamapper; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Tests that Data-Mapper example runs without errors. - */ -final class AppTest { - - /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. - */ - - @Test - void shouldExecuteApplicationWithoutException() { - - assertDoesNotThrow((Executable) App::main); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.datamapper; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +/** Tests that Data-Mapper example runs without errors. */ +final class AppTest { + + /** + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. + */ + @Test + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow((Executable) App::main); + } +} diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java index ee85252eef43..53782b7eb6eb 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java @@ -36,13 +36,12 @@ * present; they need no SQL interface code, and certainly no knowledge of the database schema. (The * database schema is always ignorant of the objects that use it.) Since it's a form of Mapper , * Data Mapper itself is even unknown to the domain layer. + * *

*/ class DataMapperTest { - /** - * This test verify that first data mapper is able to perform all CRUD operations on Student - */ + /** This test verify that first data mapper is able to perform all CRUD operations on Student */ @Test void testFirstDataMapper() { diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java index e1c3e21edead..237ea7a3cad6 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests {@link Student}. - */ +/** Tests {@link Student}. */ final class StudentTest { /** @@ -41,7 +39,7 @@ final class StudentTest { * @throws Exception if any execution error during test */ @Test - void testEquality() throws Exception { + void testEquality() { /* Create some students */ final var firstStudent = new Student(1, "Adam", 'A'); diff --git a/data-transfer-object/README.md b/data-transfer-object/README.md index 87252898b5da..40d2c1eb07c8 100644 --- a/data-transfer-object/README.md +++ b/data-transfer-object/README.md @@ -1,120 +1,214 @@ --- -title: Data Transfer Object -category: Architectural +title: "Data Transfer Object Pattern in Java: Simplifying Data Exchange Between Subsystems" +shortTitle: Data Transfer Object (DTO) +description: "Learn about the Data Transfer Object (DTO) pattern, its implementation, and practical uses in Java applications. Optimize data transfer between layers with this structural design pattern." +category: Structural language: en tag: - - Performance + - Client-server + - Data transfer + - Decoupling + - Layered architecture + - Optimization --- -## Intent +## Also known as -Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to -remote server. +* Transfer Object +* Value Object -## Explanation +## Intent of Data Transfer Object Design Pattern -Real world example +The Data Transfer Object (DTO) pattern is used to transfer data between software application subsystems or layers, particularly in the context of network calls or database retrieval in Java applications. It reduces the number of method calls by aggregating the data in a single transfer. -> We need to fetch information about customers from remote database. Instead of querying the -> attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot. +## Detailed Explanation of Data Transfer Object Pattern with Real-World Examples + +Real-world example + +> Imagine a large company with several departments (e.g., Sales, HR, and IT) needing to share employee information efficiently. Instead of each department querying and retrieving data like name, address, and role individually, they use a courier service to bundle this data into a single package. This package, representing a Data Transfer Object (DTO), allows the departments to easily receive and process comprehensive employee data without making multiple requests. This simplifies data handling, reduces communication overhead, and ensures a standardized format across the company. In plain words -> Using DTO relevant information can be fetched with a single backend query. +> Using DTO, relevant information can be fetched with a single backend query. Wikipedia says -> In the field of programming a data transfer object (DTO) is an object that carries data between -> processes. The motivation for its use is that communication between processes is usually done -> resorting to remote interfaces (e.g. web services), where each call is an expensive operation. -> Because the majority of the cost of each call is related to the round-trip time between the client -> and the server, one way of reducing the number of calls is to use an object (the DTO) that -> aggregates the data that would have been transferred by the several calls, but that is served by -> one call only. +> In the field of programming a data transfer object (DTO) is an object that carries data between processes. The motivation for its use is that communication between processes is usually done resorting to remote interfaces (e.g. web services), where each call is an expensive operation. Because the majority of the cost of each call is related to the round-trip time between the client and the server, one way of reducing the number of calls is to use an object (the DTO) that aggregates the data that would have been transferred by the several calls, but that is served by one call only. -**Programmatic Example** +## Programmatic Example of DTO Pattern in Java -Let's first introduce our simple `CustomerDTO` class. +Let's first introduce our simple `CustomerDTO` record. ```java -public class CustomerDto { - private final String id; - private final String firstName; - private final String lastName; - - public CustomerDto(String id, String firstName, String lastName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } - - public String getId() { - return id; - } +public record CustomerDto(String id, String firstName, String lastName) {} +``` - public String getFirstName() { - return firstName; - } +`CustomerResource` record acts as the server for customer information. - public String getLastName() { - return lastName; - } +```java +public record CustomerResource(List customers) { + + public void save(CustomerDto customer) { + customers.add(customer); + } + + public void delete(String customerId) { + customers.removeIf(customer -> customer.id().equals(customerId)); + } } ``` -`CustomerResource` class acts as the server for customer information. +Now fetching customer information is easy since we have the DTOs. The 2nd example uses `ProductDTO` similarly. ```java -public class CustomerResource { - private final List customers; - - public CustomerResource(List customers) { - this.customers = customers; +public class App { + + public static void main(String[] args) { + + // Example 1: Customer DTO + var customerOne = new CustomerDto("1", "Kelly", "Brown"); + var customerTwo = new CustomerDto("2", "Alfonso", "Bass"); + var customers = new ArrayList<>(List.of(customerOne, customerTwo)); + + var customerResource = new CustomerResource(customers); + + LOGGER.info("All customers:"); + var allCustomers = customerResource.customers(); + printCustomerDetails(allCustomers); + + LOGGER.info("----------------------------------------------------------"); + + LOGGER.info("Deleting customer with id {1}"); + customerResource.delete(customerOne.id()); + allCustomers = customerResource.customers(); + printCustomerDetails(allCustomers); + + LOGGER.info("----------------------------------------------------------"); + + LOGGER.info("Adding customer three}"); + var customerThree = new CustomerDto("3", "Lynda", "Blair"); + customerResource.save(customerThree); + allCustomers = customerResource.customers(); + printCustomerDetails(allCustomers); + + // Example 2: Product DTO + + Product tv = Product.builder().id(1L).name("TV").supplier("Sony").price(1000D).cost(1090D).build(); + Product microwave = + Product.builder() + .id(2L) + .name("microwave") + .supplier("Delonghi") + .price(1000D) + .cost(1090D).build(); + Product refrigerator = + Product.builder() + .id(3L) + .name("refrigerator") + .supplier("Botsch") + .price(1000D) + .cost(1090D).build(); + Product airConditioner = + Product.builder() + .id(4L) + .name("airConditioner") + .supplier("LG") + .price(1000D) + .cost(1090D).build(); + List products = + new ArrayList<>(Arrays.asList(tv, microwave, refrigerator, airConditioner)); + ProductResource productResource = new ProductResource(products); + + LOGGER.info( + "####### List of products including sensitive data just for admins: \n {}", + Arrays.toString(productResource.getAllProductsForAdmin().toArray())); + LOGGER.info( + "####### List of products for customers: \n {}", + Arrays.toString(productResource.getAllProductsForCustomer().toArray())); + + LOGGER.info("####### Going to save Sony PS5 ..."); + ProductDto.Request.Create createProductRequestDto = + new ProductDto.Request.Create() + .setName("PS5") + .setCost(1000D) + .setPrice(1220D) + .setSupplier("Sony"); + productResource.save(createProductRequestDto); + LOGGER.info( + "####### List of products after adding PS5: {}", + Arrays.toString(productResource.products().toArray())); } - public List getAllCustomers() { - return customers; - } - - public void save(CustomerDto customer) { - customers.add(customer); - } - - public void delete(String customerId) { - customers.removeIf(customer -> customer.getId().equals(customerId)); + private static void printCustomerDetails(List allCustomers) { + allCustomers.forEach(customer -> LOGGER.info(customer.firstName())); } } ``` -Now fetching customer information is easy since we have the DTOs. +The console output: -```java - var allCustomers = customerResource.getAllCustomers(); - allCustomers.forEach(customer -> LOGGER.info(customer.getFirstName())); - // Kelly - // Alfonso +``` +11:10:51.838 [main] INFO com.iluwatar.datatransfer.App -- All customers: +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- Kelly +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- Alfonso +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- ---------------------------------------------------------- +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- Deleting customer with id {1} +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- Alfonso +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- ---------------------------------------------------------- +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- Adding customer three} +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- Alfonso +11:10:51.840 [main] INFO com.iluwatar.datatransfer.App -- Lynda +11:10:51.848 [main] INFO com.iluwatar.datatransfer.App -- ####### List of products including sensitive data just for admins: + [Private{id=1, name='TV', price=1000.0, cost=1090.0}, Private{id=2, name='microwave', price=1000.0, cost=1090.0}, Private{id=3, name='refrigerator', price=1000.0, cost=1090.0}, Private{id=4, name='airConditioner', price=1000.0, cost=1090.0}] +11:10:51.852 [main] INFO com.iluwatar.datatransfer.App -- ####### List of products for customers: + [Public{id=1, name='TV', price=1000.0}, Public{id=2, name='microwave', price=1000.0}, Public{id=3, name='refrigerator', price=1000.0}, Public{id=4, name='airConditioner', price=1000.0}] +11:10:51.852 [main] INFO com.iluwatar.datatransfer.App -- ####### Going to save Sony PS5 ... +11:10:51.856 [main] INFO com.iluwatar.datatransfer.App -- ####### List of products after adding PS5: [Product{id=1, name='TV', price=1000.0, cost=1090.0, supplier='Sony'}, Product{id=2, name='microwave', price=1000.0, cost=1090.0, supplier='Delonghi'}, Product{id=3, name='refrigerator', price=1000.0, cost=1090.0, supplier='Botsch'}, Product{id=4, name='airConditioner', price=1000.0, cost=1090.0, supplier='LG'}, Product{id=5, name='PS5', price=1220.0, cost=1000.0, supplier='Sony'}] ``` -## Class diagram +## When to Use the Data Transfer Object Pattern in Java -![alt text](./etc/data-transfer-object.urm.png "data-transfer-object") +Use the Data Transfer Object pattern when: -## Applicability +* When you need to optimize network traffic by reducing the number of calls, especially in a client-server architecture. +* In scenarios where batch processing of data is preferred over individual processing. +* When working with remote interfaces, to encapsulate the data transfer in a serializable object that can be easily transmitted. -Use the Data Transfer Object pattern when: +## Data Transfer Object Pattern Java Tutorials + +* [Data Transfer Object Pattern in Java - Implementation and Mapping (StackAbuse)](https://stackabuse.com/data-transfer-object-pattern-in-java-implementation-and-mapping/) +* [Design Pattern - Transfer Object Pattern (TutorialsPoint)](https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm) +* [The DTO Pattern (Baeldung)](https://www.baeldung.com/java-dto-pattern) + +## Real-World Applications of DTO Pattern in Java + +* Remote Method Invocation (RMI) in Java, where DTOs are used to pass data across network. +* Enterprise JavaBeans (EJB), particularly when data needs to be transferred from EJBs to clients. +* Various web service frameworks where DTOs encapsulate request and response data. + +## Benefits and Trade-offs of Data Transfer Object Pattern + +Benefits: + +* Reduces network calls, thereby improving application performance. +* Decouples the client from the server, leading to more modular and maintainable code. +* Simplifies data transmission over the network by aggregating data into single objects. + +Trade-offs: -* The client is asking for multiple information. And the information is related. -* When you want to boost the performance to get resources. -* You want reduced number of remote calls. +* Introduces additional classes into the application, which may increase complexity. +* Can lead to redundant data structures that mirror domain models, potentially causing synchronization issues. +* May encourage design that leads to an anemic domain model, where business logic is separated from data. -## Tutorials +## Related Patterns -* [Data Transfer Object Pattern in Java - Implementation and Mapping](https://stackabuse.com/data-transfer-object-pattern-in-java-implementation-and-mapping/) -* [The DTO Pattern (Data Transfer Object)](https://www.baeldung.com/java-dto-pattern) +* [Composite Entity](https://java-design-patterns.com/patterns/composite-entity/): DTOs may be used to represent composite entities, particularly in persistence mechanisms. +* [Facade](https://java-design-patterns.com/patterns/facade/): Similar to DTO, a Facade may aggregate multiple calls into one, improving efficiency. +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/): Often involves using DTOs to transfer data across the boundary between the service layer and its clients. -## Credits +## References and Credits -* [Design Pattern - Transfer Object Pattern](https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm) -* [Data Transfer Object](https://msdn.microsoft.com/en-us/library/ff649585.aspx) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=014237a67c9d46f384b35e10151956bd) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cKndQp) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Data Transfer Object (Microsoft)](https://msdn.microsoft.com/en-us/library/ff649585.aspx) diff --git a/data-transfer-object/pom.xml b/data-transfer-object/pom.xml index b6404194b41e..5370250f6959 100644 --- a/data-transfer-object/pom.xml +++ b/data-transfer-object/pom.xml @@ -34,6 +34,14 @@ data-transfer-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java index e9164600ce7e..0a6fc9f041d5 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java @@ -38,17 +38,16 @@ * The Data Transfer Object pattern is a design pattern in which an data transfer object is used to * serve related information together to avoid multiple call for each piece of information. * - *

In the first example, {@link App} is a customer details consumer i.e. client to - * request for customer details to server. {@link CustomerResource} act as server to serve customer - * information. {@link CustomerDto} is data transfer object to share customer information. + *

In the first example, {@link App} is a customer details consumer i.e. client to request for + * customer details to server. {@link CustomerResource} act as server to serve customer information. + * {@link CustomerDto} is data transfer object to share customer information. * - *

In the second example, {@link App} is a product details consumer i.e. client to - * request for product details to server. {@link ProductResource} acts as server to serve - * product information. {@link ProductDto} is data transfer object to share product information. + *

In the second example, {@link App} is a product details consumer i.e. client to request for + * product details to server. {@link ProductResource} acts as server to serve product information. + * {@link ProductDto} is data transfer object to share product information. * *

The pattern implementation is a bit different in each of the examples. The first can be * thought as a traditional example and the second is an enum based implementation. - * */ @Slf4j public class App { @@ -67,15 +66,15 @@ public static void main(String[] args) { var customerResource = new CustomerResource(customers); - LOGGER.info("All customers:-"); - var allCustomers = customerResource.getAllCustomers(); + LOGGER.info("All customers:"); + var allCustomers = customerResource.customers(); printCustomerDetails(allCustomers); LOGGER.info("----------------------------------------------------------"); LOGGER.info("Deleting customer with id {1}"); - customerResource.delete(customerOne.getId()); - allCustomers = customerResource.getAllCustomers(); + customerResource.delete(customerOne.id()); + allCustomers = customerResource.customers(); printCustomerDetails(allCustomers); LOGGER.info("----------------------------------------------------------"); @@ -83,33 +82,37 @@ public static void main(String[] args) { LOGGER.info("Adding customer three}"); var customerThree = new CustomerDto("3", "Lynda", "Blair"); customerResource.save(customerThree); - allCustomers = customerResource.getAllCustomers(); + allCustomers = customerResource.customers(); printCustomerDetails(allCustomers); // Example 2: Product DTO - Product tv = Product.builder().id(1L).name("TV").supplier("Sony").price(1000D).cost(1090D).build(); + Product tv = + Product.builder().id(1L).name("TV").supplier("Sony").price(1000D).cost(1090D).build(); Product microwave = Product.builder() .id(2L) .name("microwave") .supplier("Delonghi") .price(1000D) - .cost(1090D).build(); + .cost(1090D) + .build(); Product refrigerator = Product.builder() .id(3L) .name("refrigerator") .supplier("Botsch") .price(1000D) - .cost(1090D).build(); + .cost(1090D) + .build(); Product airConditioner = Product.builder() .id(4L) .name("airConditioner") .supplier("LG") .price(1000D) - .cost(1090D).build(); + .cost(1090D) + .build(); List products = new ArrayList<>(Arrays.asList(tv, microwave, refrigerator, airConditioner)); ProductResource productResource = new ProductResource(products); @@ -131,10 +134,10 @@ public static void main(String[] args) { productResource.save(createProductRequestDto); LOGGER.info( "####### List of products after adding PS5: {}", - Arrays.toString(productResource.getProducts().toArray())); + Arrays.toString(productResource.products().toArray())); } private static void printCustomerDetails(List allCustomers) { - allCustomers.forEach(customer -> LOGGER.info(customer.getFirstName())); + allCustomers.forEach(customer -> LOGGER.info(customer.firstName())); } } diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java index 556adae0e60d..64e44f64dfe4 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java @@ -24,20 +24,10 @@ */ package com.iluwatar.datatransfer.customer; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - /** * {@link CustomerDto} is a data transfer object POJO. Instead of sending individual information to * client We can send related information together in POJO. * *

Dto will not have any business logic in it. */ -@Getter -@RequiredArgsConstructor -public class CustomerDto { - private final String id; - private final String firstName; - private final String lastName; - -} +public record CustomerDto(String id, String firstName, String lastName) {} diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java index f45509b008e6..427c87521af1 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java @@ -25,25 +25,12 @@ package com.iluwatar.datatransfer.customer; import java.util.List; -import lombok.RequiredArgsConstructor; /** * The resource class which serves customer information. This class act as server in the demo. Which * has all customer details. */ -@RequiredArgsConstructor -public class CustomerResource { - private final List customers; - - /** - * Get all customers. - * - * @return : all customers in list. - */ - public List getAllCustomers() { - return customers; - } - +public record CustomerResource(List customers) { /** * Save new customer. * @@ -59,6 +46,6 @@ public void save(CustomerDto customer) { * @param customerId delete customer with id {@code customerId} */ public void delete(String customerId) { - customers.removeIf(customer -> customer.getId().equals(customerId)); + customers.removeIf(customer -> customer.id().equals(customerId)); } -} \ No newline at end of file +} diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java index 633c0b82997d..a29fda1589cd 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java @@ -29,9 +29,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -/** - * {@link Product} is a entity class for product entity. This class act as entity in the demo. - */ +/** {@link Product} is a entity class for product entity. This class act as entity in the demo. */ @Data @Builder @NoArgsConstructor @@ -46,11 +44,18 @@ public final class Product { @Override public String toString() { return "Product{" - + "id=" + id - + ", name='" + name + '\'' - + ", price=" + price - + ", cost=" + cost - + ", supplier='" + supplier + '\'' - + '}'; + + "id=" + + id + + ", name='" + + name + + '\'' + + ", price=" + + price + + ", cost=" + + cost + + ", supplier='" + + supplier + + '\'' + + '}'; } } diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java index 9f13398a7b1d..d254e2fc1ce3 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java @@ -25,8 +25,7 @@ package com.iluwatar.datatransfer.product; /** - * {@link ProductDto} is a data transfer object POJO. - * Instead of sending individual information to + * {@link ProductDto} is a data transfer object POJO. Instead of sending individual information to * client We can send related information together in POJO. * *

Dto will not have any business logic in it. @@ -35,15 +34,13 @@ public enum ProductDto { ; /** - * This is Request class which consist of Create or any other request DTO's - * you might want to use in your API. + * This is Request class which consist of Create or any other request DTO's you might want to use + * in your API. */ public enum Request { ; - /** - * This is Create dto class for requesting create new product. - */ + /** This is Create dto class for requesting create new product. */ public static final class Create implements Name, Price, Cost, Supplier { private String name; private Double price; @@ -93,15 +90,13 @@ public Create setSupplier(String supplier) { } /** - * This is Response class which consist of any response DTO's - * you might want to provide to your clients. + * This is Response class which consist of any response DTO's you might want to provide to your + * clients. */ public enum Response { ; - /** - * This is Public dto class for API response with the lowest data security. - */ + /** This is Public dto class for API response with the lowest data security. */ public static final class Public implements Id, Name, Price { private Long id; private String name; @@ -139,21 +134,11 @@ public Public setPrice(Double price) { @Override public String toString() { - return "Public{" - + "id=" - + id - + ", name='" - + name - + '\'' - + ", price=" - + price - + '}'; + return "Public{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}'; } } - /** - * This is Private dto class for API response with the highest data security. - */ + /** This is Private dto class for API response with the highest data security. */ public static final class Private implements Id, Name, Price, Cost { private Long id; private String name; @@ -203,28 +188,21 @@ public Private setCost(Double cost) { @Override public String toString() { return "Private{" - + - "id=" + + "id=" + id - + - ", name='" + + ", name='" + name + '\'' - + - ", price=" + + ", price=" + price - + - ", cost=" + + ", cost=" + cost - + - '}'; + + '}'; } } } - /** - * Use this interface whenever you want to provide the product Id in your DTO. - */ + /** Use this interface whenever you want to provide the product Id in your DTO. */ private interface Id { /** * Unique identifier of the product. @@ -234,9 +212,7 @@ private interface Id { Long getId(); } - /** - * Use this interface whenever you want to provide the product Name in your DTO. - */ + /** Use this interface whenever you want to provide the product Name in your DTO. */ private interface Name { /** * The name of the product. @@ -246,40 +222,32 @@ private interface Name { String getName(); } - /** - * Use this interface whenever you want to provide the product Price in your DTO. - */ + /** Use this interface whenever you want to provide the product Price in your DTO. */ private interface Price { /** - * The amount we sell a product for. - * This data is not confidential + * The amount we sell a product for. This data is not confidential * * @return : price of the product. */ Double getPrice(); } - /** - * Use this interface whenever you want to provide the product Cost in your DTO. - */ + /** Use this interface whenever you want to provide the product Cost in your DTO. */ private interface Cost { /** - * The amount that it costs us to purchase this product - * For the amount we sell a product for, see the {@link Price Price} parameter. - * This data is confidential + * The amount that it costs us to purchase this product For the amount we sell a product for, + * see the {@link Price Price} parameter. This data is confidential * * @return : cost of the product. */ Double getCost(); } - /** - * Use this interface whenever you want to provide the product Supplier in your DTO. - */ + /** Use this interface whenever you want to provide the product Supplier in your DTO. */ private interface Supplier { /** - * The name of supplier of the product or its manufacturer. - * This data is highly confidential + * The name of supplier of the product or its manufacturer. This data is highly + * confidential * * @return : supplier of the product. */ diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java index 9adafc8be4d6..d0b3fcbd7932 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java @@ -30,30 +30,22 @@ * The resource class which serves product information. This class act as server in the demo. Which * has all product details. */ -public class ProductResource { - private final List products; - - /** - * Initialise resource with existing products. - * - * @param products initialize resource with existing products. Act as database. - */ - public ProductResource(final List products) { - this.products = products; - } - +public record ProductResource(List products) { /** * Get all products. * * @return : all products in list but in the scheme of private dto. */ public List getAllProductsForAdmin() { - return products - .stream() - .map(p -> new ProductDto.Response.Private().setId(p.getId()).setName(p.getName()) + return products.stream() + .map( + p -> + new ProductDto.Response.Private() + .setId(p.getId()) + .setName(p.getName()) .setCost(p.getCost()) .setPrice(p.getPrice())) - .toList(); + .toList(); } /** @@ -62,11 +54,14 @@ public List getAllProductsForAdmin() { * @return : all products in list but in the scheme of public dto. */ public List getAllProductsForCustomer() { - return products - .stream() - .map(p -> new ProductDto.Response.Public().setId(p.getId()).setName(p.getName()) + return products.stream() + .map( + p -> + new ProductDto.Response.Public() + .setId(p.getId()) + .setName(p.getName()) .setPrice(p.getPrice())) - .toList(); + .toList(); } /** @@ -75,7 +70,8 @@ public List getAllProductsForCustomer() { * @param createProductDto save new product to list. */ public void save(ProductDto.Request.Create createProductDto) { - products.add(Product.builder() + products.add( + Product.builder() .id((long) (products.size() + 1)) .name(createProductDto.getName()) .supplier(createProductDto.getSupplier()) @@ -83,13 +79,4 @@ public void save(ProductDto.Request.Create createProductDto) { .cost(createProductDto.getCost()) .build()); } - - /** - * List of all products in an entity representation. - * - * @return : all the products entity that stored in the products list - */ - public List getProducts() { - return products; - } } diff --git a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java index 49edb5c73888..d0e32976c4f7 100644 --- a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java +++ b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java @@ -24,21 +24,20 @@ */ package com.iluwatar.datatransfer; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java index cc14c059a2a1..006fd0b23bf8 100644 --- a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java +++ b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java @@ -29,25 +29,21 @@ import java.util.ArrayList; import java.util.List; -import com.iluwatar.datatransfer.customer.CustomerDto; -import com.iluwatar.datatransfer.customer.CustomerResource; import org.junit.jupiter.api.Test; -/** - * tests {@link CustomerResource}. - */ +/** tests {@link CustomerResource}. */ class CustomerResourceTest { @Test void shouldGetAllCustomers() { var customers = List.of(new CustomerDto("1", "Melody", "Yates")); var customerResource = new CustomerResource(customers); - var allCustomers = customerResource.getAllCustomers(); + var allCustomers = customerResource.customers(); assertEquals(1, allCustomers.size()); - assertEquals("1", allCustomers.get(0).getId()); - assertEquals("Melody", allCustomers.get(0).getFirstName()); - assertEquals("Yates", allCustomers.get(0).getLastName()); + assertEquals("1", allCustomers.get(0).id()); + assertEquals("Melody", allCustomers.get(0).firstName()); + assertEquals("Yates", allCustomers.get(0).lastName()); } @Test @@ -57,10 +53,10 @@ void shouldSaveCustomer() { customerResource.save(customer); - var allCustomers = customerResource.getAllCustomers(); - assertEquals("1", allCustomers.get(0).getId()); - assertEquals("Rita", allCustomers.get(0).getFirstName()); - assertEquals("Reynolds", allCustomers.get(0).getLastName()); + var allCustomers = customerResource.customers(); + assertEquals("1", allCustomers.get(0).id()); + assertEquals("Rita", allCustomers.get(0).firstName()); + assertEquals("Reynolds", allCustomers.get(0).lastName()); } @Test @@ -69,10 +65,9 @@ void shouldDeleteCustomer() { var customers = new ArrayList<>(List.of(customer)); var customerResource = new CustomerResource(customers); - customerResource.delete(customer.getId()); + customerResource.delete(customer.id()); - var allCustomers = customerResource.getAllCustomers(); + var allCustomers = customerResource.customers(); assertTrue(allCustomers.isEmpty()); } - -} \ No newline at end of file +} diff --git a/decorator/README.md b/decorator/README.md index 3df72d6671e8..ddd8a61a0c94 100644 --- a/decorator/README.md +++ b/decorator/README.md @@ -1,72 +1,72 @@ --- -title: Decorator +title: "Decorator Pattern in Java: Extending Classes Dynamically" +shortTitle: Decorator +description: "Learn how the Decorator Design Pattern enhances flexibility in Java programming by allowing dynamic addition of responsibilities to objects without modifying their existing code. Explore real-world examples and implementation." category: Structural language: en tag: - - Gang of Four - - Extensibility + - Enhancement + - Extensibility + - Gang of Four + - Object composition + - Wrapping --- ## Also known as -Wrapper +* Smart Proxy +* Wrapper -## Intent +## Intent of Decorator Design Pattern -Attach additional responsibilities to an object dynamically. Decorators provide a flexible -alternative to subclassing for extending functionality. +The Decorator pattern allows for the dynamic addition of responsibilities to objects without modifying their existing code. It achieves this by providing a way to "wrap" objects within objects of similar interface, enhancing Java design patterns flexibility. -## Explanation +## Detailed Explanation of Decorator Pattern with Real-World Examples Real-world example -> There is an angry troll living in the nearby hills. Usually, it goes bare-handed but sometimes it -> has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it -> dynamically with a suitable weapon. +> Imagine a coffee shop where you can customize your coffee order. You start with a basic coffee, and you can add different ingredients like milk, sugar, whipped cream, and so on. Each addition is like a decorator in the Decorator design pattern. The base coffee object can be decorated with additional functionality (flavors, toppings) dynamically. For example, you can start with a plain coffee object, then wrap it with a milk decorator, followed by a sugar decorator, and finally a whipped cream decorator. Each decorator adds new features or modifies the behavior of the coffee object, similar to how the Decorator pattern works in software design. In plain words -> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping -> them in an object of a decorator class. +> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class. Wikipedia says -> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to -> be added to an individual object, either statically or dynamically, without affecting the behavior -> of other objects from the same class. The decorator pattern is often useful for adhering to the -> Single Responsibility Principle, as it allows functionality to be divided between classes with -> unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality -> of a class to be extended without being modified. +> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. -**Programmatic Example** +## Programmatic Example of Decorator Pattern in Java -Let's take the troll example. First of all we have a `SimpleTroll` implementing the `Troll` -interface: +There is an angry troll living in the nearby hills. Usually, it goes bare-handed, but sometimes it has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it dynamically with a suitable weapon. + +First, we have a `SimpleTroll` implementing the `Troll` interface: ```java public interface Troll { - void attack(); - int getAttackPower(); - void fleeBattle(); + void attack(); + + int getAttackPower(); + + void fleeBattle(); } @Slf4j public class SimpleTroll implements Troll { - @Override - public void attack() { - LOGGER.info("The troll tries to grab you!"); - } + @Override + public void attack() { + LOGGER.info("The troll tries to grab you!"); + } - @Override - public int getAttackPower() { - return 10; - } + @Override + public int getAttackPower() { + return 10; + } - @Override - public void fleeBattle() { - LOGGER.info("The troll shrieks in horror and runs away!"); - } + @Override + public void fleeBattle() { + LOGGER.info("The troll shrieks in horror and runs away!"); + } } ``` @@ -74,98 +74,112 @@ Next, we want to add a club for the troll. We can do it dynamically by using a d ```java @Slf4j +@RequiredArgsConstructor public class ClubbedTroll implements Troll { - private final Troll decorated; - - public ClubbedTroll(Troll decorated) { - this.decorated = decorated; - } + private final Troll decorated; - @Override - public void attack() { - decorated.attack(); - LOGGER.info("The troll swings at you with a club!"); - } + @Override + public void attack() { + decorated.attack(); + LOGGER.info("The troll swings at you with a club!"); + } - @Override - public int getAttackPower() { - return decorated.getAttackPower() + 10; - } + @Override + public int getAttackPower() { + return decorated.getAttackPower() + 10; + } - @Override - public void fleeBattle() { - decorated.fleeBattle(); - } + @Override + public void fleeBattle() { + decorated.fleeBattle(); + } } ``` Here's the troll in action: ```java -// simple troll -LOGGER.info("A simple looking troll approaches."); -var troll = new SimpleTroll(); -troll.attack(); -troll.fleeBattle(); -LOGGER.info("Simple troll power: {}.\n", troll.getAttackPower()); - -// change the behavior of the simple troll by adding a decorator -LOGGER.info("A troll with huge club surprises you."); -var clubbedTroll = new ClubbedTroll(troll); -clubbedTroll.attack(); -clubbedTroll.fleeBattle(); -LOGGER.info("Clubbed troll power: {}.\n", clubbedTroll.getAttackPower()); + public static void main(String[] args) { + + // simple troll + LOGGER.info("A simple looking troll approaches."); + var troll = new SimpleTroll(); + troll.attack(); + troll.fleeBattle(); + LOGGER.info("Simple troll power: {}.\n", troll.getAttackPower()); + + // change the behavior of the simple troll by adding a decorator + LOGGER.info("A troll with huge club surprises you."); + var clubbedTroll = new ClubbedTroll(troll); + clubbedTroll.attack(); + clubbedTroll.fleeBattle(); + LOGGER.info("Clubbed troll power: {}.\n", clubbedTroll.getAttackPower()); +} ``` Program output: -```java -A simple looking troll approaches. -The troll tries to grab you! -The troll shrieks in horror and runs away! -Simple troll power: 10. - -A troll with huge club surprises you. -The troll tries to grab you! -The troll swings at you with a club! -The troll shrieks in horror and runs away! -Clubbed troll power: 20. +``` +11:34:18.098 [main] INFO com.iluwatar.decorator.App -- A simple looking troll approaches. +11:34:18.100 [main] INFO com.iluwatar.decorator.SimpleTroll -- The troll tries to grab you! +11:34:18.100 [main] INFO com.iluwatar.decorator.SimpleTroll -- The troll shrieks in horror and runs away! +11:34:18.100 [main] INFO com.iluwatar.decorator.App -- Simple troll power: 10. + +11:34:18.100 [main] INFO com.iluwatar.decorator.App -- A troll with huge club surprises you. +11:34:18.101 [main] INFO com.iluwatar.decorator.SimpleTroll -- The troll tries to grab you! +11:34:18.101 [main] INFO com.iluwatar.decorator.ClubbedTroll -- The troll swings at you with a club! +11:34:18.101 [main] INFO com.iluwatar.decorator.SimpleTroll -- The troll shrieks in horror and runs away! +11:34:18.101 [main] INFO com.iluwatar.decorator.App -- Clubbed troll power: 20. ``` -## Class diagram - -![alt text](./etc/decorator.urm.png "Decorator pattern class diagram") - -## Applicability +## When to Use the Decorator Pattern in Java Decorator is used to: -* Add responsibilities to individual objects dynamically and transparently, that is, without -affecting other objects. +* Add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects, a key feature of Java design patterns. * For responsibilities that can be withdrawn. -* When extension by subclassing is impractical. Sometimes a large number of independent extensions -are possible and would produce an explosion of subclasses to support every combination. Or a class -definition may be hidden or otherwise unavailable for subclassing. +* When extending a class is impractical due to the proliferation of subclasses that could result. +* For when a class definition might be hidden or otherwise unavailable for subclassing. + +## Decorator Pattern Java Tutorials + +* [Decorator Design Pattern in Java Example (DigitalOcean)](https://www.digitalocean.com/community/tutorials/decorator-design-pattern-in-java-example) + +## Real-World Applications of Decorator Pattern in Java + +* GUI toolkits often use decorators to dynamically add behaviors like scrolling, borders, or layout management to components. +* The [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) classes in Java are well-known examples utilizing the Decorator pattern. +* [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) +* [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) +* [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) + +## Benefits and Trade-offs of Decorator Pattern + +Benefits: -## Tutorials +* Greater flexibility than static inheritance. +* Avoids feature-laden classes high up in the hierarchy, showcasing the power of Java design patterns. +* A decorator and its component aren't identical. +* Responsibilities can be added or removed at runtime. -* [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) +Trade-offs: -## Known uses +* A decorator and its component aren't identical, so tests for object type will fail. +* Decorators can lead to a system with lots of small objects that look alike to the programmer, making the desired configuration hard to achieve. +* Overuse can complicate the code structure due to the introduction of numerous small classes. - * [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), - [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) - * [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) - * [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) - * [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) +## Related Java Design Patterns +* [Adapter](https://java-design-patterns.com/patterns/adapter/): A decorator changes an object's responsibilities, while an adapter changes an object's interface. +* [Composite](https://java-design-patterns.com/patterns/composite/): Decorators can be viewed as a degenerate composite with only one component. However, a decorator adds additional responsibilities—it isn't intended for object aggregation. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Decorator lets you change the skin of an object, while Strategy lets you change the guts. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Functional Programming in Java](https://amzn.to/3JUIc5Q) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/4aKFTgS) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/decorator/pom.xml b/decorator/pom.xml index 60e8f493e7bc..380e3a6347e1 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -34,6 +34,14 @@ decorator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java b/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java index 127fd8d23ea0..10110facdacb 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java @@ -27,9 +27,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Decorator that adds a club for the troll. - */ +/** Decorator that adds a club for the troll. */ @Slf4j @RequiredArgsConstructor public class ClubbedTroll implements Troll { diff --git a/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java b/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java index cc13c95dad43..2c178961190b 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SimpleTroll implements {@link Troll} interface directly. - */ +/** SimpleTroll implements {@link Troll} interface directly. */ @Slf4j public class SimpleTroll implements Troll { diff --git a/decorator/src/main/java/com/iluwatar/decorator/Troll.java b/decorator/src/main/java/com/iluwatar/decorator/Troll.java index aba900a9c238..8c2ff391309e 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/Troll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/Troll.java @@ -1,38 +1,35 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.decorator; - -/** - * Interface for trolls. - */ -public interface Troll { - - void attack(); - - int getAttackPower(); - - void fleeBattle(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.decorator; + +/** Interface for trolls. */ +public interface Troll { + + void attack(); + + int getAttackPower(); + + void fleeBattle(); +} diff --git a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java index 32609d3b9003..9170574468c0 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.decorator; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java index 1dd6cb188dbf..8f06a971236c 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java @@ -32,9 +32,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for {@link ClubbedTroll} - */ +/** Tests for {@link ClubbedTroll} */ class ClubbedTrollTest { @Test diff --git a/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java index e0bde46db5ad..fd14fb9da10e 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java @@ -36,9 +36,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Tests for {@link SimpleTroll} - */ +/** Tests for {@link SimpleTroll} */ class SimpleTrollTest { private InMemoryAppender appender; @@ -67,7 +65,7 @@ void testTrollActions() { assertEquals(2, appender.getLogSize()); } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/delegation/README.md b/delegation/README.md index def547ec6def..8c90d8ee8497 100644 --- a/delegation/README.md +++ b/delegation/README.md @@ -1,119 +1,157 @@ --- -title: Delegation -category: Structural +title: "Delegation Pattern in Java: Mastering Efficient Task Assignment" +shortTitle: Delegation +description: "Explore the Delegation Design Pattern in Java with real-world examples, class diagrams, and its benefits. Learn how to enhance your code flexibility and reuse." +category: Behavioral language: en tag: - - Decoupling + - Decoupling + - Delegation + - Object composition --- ## Also known as -Proxy Pattern -## Intent -It is a technique where an object expresses certain behavior to the outside but in -reality delegates responsibility for implementing that behaviour to an associated object. +* Helper +* Surrogate -## Explanation +## Intent of Delegation Design Pattern + +To allow an object to delegate responsibility for a task to another helper object. + +## Detailed Explanation of Delegation Pattern with Real-World Examples Real-world example -> Imagine that we have adventurers who fight monsters with different weapons depending on their -> abilities and skills. We must be able to equip them with different ones without having to -> modify their source code for each one. The delegation pattern makes it possible by delegating -> the dynamic work to a specific object implementing an interface with relevant methods. +> In a restaurant, the head chef delegates tasks to sous-chefs: one manages grilling, another handles salads, and a third is in charge of desserts. Each sous-chef specializes in their area, allowing the head chef to focus on overall kitchen management. This mirrors the Delegation design pattern, where a main object delegates specific tasks to helper objects, each expert in their domain. + +In plain words + +> Delegation is a design pattern where an object passes on a task to a helper object. Wikipedia says -> In object-oriented programming, delegation refers to evaluating a member (property or method) of -> one object (the receiver) in the context of another original object (the sender). Delegation can -> be done explicitly, by passing the sending object to the receiving object, which can be done in -> any object-oriented language; or implicitly, by the member lookup rules of the language, which -> requires language support for the feature. +> In object-oriented programming, delegation refers to evaluating a member (property or method) of one object (the receiver) in the context of another original object (the sender). Delegation can be done explicitly, by passing the sending object to the receiving object, which can be done in any object-oriented language; or implicitly, by the member lookup rules of the language, which requires language support for the feature. + +## Programmatic Example of Delegation Pattern in Java -**Programmatic Example** +Let's consider a printing example. We have an interface `Printer` and three implementations `CanonPrinter`, `EpsonPrinter` and `HpPrinter`. ```java public interface Printer { - void print(final String message); + void print(final String message); } @Slf4j public class CanonPrinter implements Printer { - @Override - public void print(String message) { - LOGGER.info("Canon Printer : {}", message); - } + @Override + public void print(String message) { + LOGGER.info("Canon Printer : {}", message); + } } @Slf4j public class EpsonPrinter implements Printer { - @Override - public void print(String message) { - LOGGER.info("Epson Printer : {}", message); - } + @Override + public void print(String message) { + LOGGER.info("Epson Printer : {}", message); + } } @Slf4j public class HpPrinter implements Printer { - @Override - public void print(String message) { - LOGGER.info("HP Printer : {}", message); - } + @Override + public void print(String message) { + LOGGER.info("HP Printer : {}", message); + } } ``` -The `PrinterController` can be used as a `Printer` by delegating any work handled by this -interface to an object implementing it. + +The `PrinterController` can be used as a `Printer` by delegating any work handled by this interface to an object implementing it. + ```java public class PrinterController implements Printer { - - private final Printer printer; - - public PrinterController(Printer printer) { - this.printer = printer; - } - - @Override - public void print(String message) { - printer.print(message); - } + + private final Printer printer; + + public PrinterController(Printer printer) { + this.printer = printer; + } + + @Override + public void print(String message) { + printer.print(message); + } } ``` -Now on the client code printer controllers can print messages differently depending on the -object they're delegating that work to. +In the client code, printer controllers can print messages differently depending on the object they're delegating that work to. ```java -private static final String MESSAGE_TO_PRINT = "hello world"; +public class App { + + private static final String MESSAGE_TO_PRINT = "hello world"; -var hpPrinterController = new PrinterController(new HpPrinter()); -var canonPrinterController = new PrinterController(new CanonPrinter()); -var epsonPrinterController = new PrinterController(new EpsonPrinter()); + public static void main(String[] args) { + var hpPrinterController = new PrinterController(new HpPrinter()); + var canonPrinterController = new PrinterController(new CanonPrinter()); + var epsonPrinterController = new PrinterController(new EpsonPrinter()); -hpPrinterController.print(MESSAGE_TO_PRINT); -canonPrinterController.print(MESSAGE_TO_PRINT); -epsonPrinterController.print(MESSAGE_TO_PRINT) + hpPrinterController.print(MESSAGE_TO_PRINT); + canonPrinterController.print(MESSAGE_TO_PRINT); + epsonPrinterController.print(MESSAGE_TO_PRINT); + } +} ``` Program output: -```java -HP Printer : hello world -Canon Printer : hello world -Epson Printer : hello world ``` +HP Printer:hello world +Canon Printer:hello world +Epson Printer:hello world +``` + +## Detailed Explanation of Delegation Pattern with Real-World Examples + +![Delegate class diagram](./etc/delegation.png "Delegate") + +## When to Use the Delegation Pattern in Java + +* When you want to pass responsibility from one class to another without inheritance. +* To achieve composition-based reuse instead of inheritance-based. +* When you need to use several interchangeable helper classes at runtime. + +## Real-World Applications of Delegation Pattern in Java + +* Java's java.awt.event package, where listeners are often used to handle events. +* Wrapper classes in Java's Collections Framework (java.util.Collections), which delegate to other collection objects. +* In Spring Framework, delegation is used extensively in the IoC container where beans delegate tasks to other beans. + +## Benefits and Trade-offs of Delegation Pattern + +Benefits: + +* Reduces subclassing: Objects can delegate operations to different objects and change them at runtime, reducing the need for subclassing. +* Encourages reuse: Delegation promotes the reuse of the helper object's code. +* Increases flexibility: By delegating tasks to helper objects, you can change the behavior of your classes at runtime. + +Trade-offs: -## Class diagram -![alt text](./etc/delegation.png "Delegate") +* Runtime Overhead: Delegation can introduce additional layers of indirection, which may result in slight performance costs. +* Complexity: The design can become more complicated since it involves additional classes and interfaces to manage delegation. -## Applicability -Use the Delegate pattern in order to achieve the following +## Related Java Design Patterns -* Reduce the coupling of methods to their class -* Components that behave identically, but realize that this situation can change in the future. +* [Composite](https://java-design-patterns.com/patterns/composite/): Delegation can be used within a composite pattern to delegate component-specific behavior to child components. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Delegation is often used in the strategy pattern where a context object delegates tasks to a strategy object. +* https://java-design-patterns.com/patterns/proxy/: The proxy pattern is a form of delegation where a proxy object controls access to another object, which it delegates work to. -## Credits +## References and Credits +* [Effective Java](https://amzn.to/4aGE7gX) +* [Head First Design Patterns](https://amzn.to/3J9tuaB) +* [Refactoring: Improving the Design of Existing Code](https://amzn.to/3VOcRsw) * [Delegate Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Delegation_pattern) -* [Proxy Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Proxy_pattern) diff --git a/delegation/pom.xml b/delegation/pom.xml index d1f18027eacd..ec9c1c76a1cb 100644 --- a/delegation/pom.xml +++ b/delegation/pom.xml @@ -34,6 +34,14 @@ 4.0.0 delegation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java index f81e033bd968..a7c6dff3a0fd 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java @@ -38,9 +38,9 @@ * *

In this example the delegates are {@link EpsonPrinter}, {@link HpPrinter} and {@link * CanonPrinter} they all implement {@link Printer}. The {@link PrinterController} class also - * implements {@link Printer}. However neither provide the functionality of {@link Printer} by + * implements {@link Printer}. However, neither provide the functionality of {@link Printer} by * printing to the screen, they actually call upon the instance of {@link Printer} that they were - * instantiated with. Therefore delegating the behaviour to another class. + * instantiated with. Therefore, delegating the behaviour to another class. */ public class App { @@ -60,5 +60,4 @@ public static void main(String[] args) { canonPrinterController.print(MESSAGE_TO_PRINT); epsonPrinterController.print(MESSAGE_TO_PRINT); } - } diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java index 8e9a3896dbc4..28488d4855db 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java @@ -27,7 +27,7 @@ /** * Delegator Class to delegate the implementation of the Printer. This ensures two things: - when * the actual implementation of the Printer class changes the delegation will still be operational - - * the actual benefit is observed when there are more than one implementors and they share a + * the actual benefit is observed when there are more than one implementor, and they share a * delegation control */ public class PrinterController implements Printer { @@ -41,7 +41,7 @@ public PrinterController(Printer printer) { /** * This method is implemented from {@link Printer} however instead on providing an implementation, * it instead calls upon the class passed through the constructor. This is the delegate, hence the - * pattern. Therefore meaning that the caller does not care of the implementing class only the + * pattern. Therefore, meaning that the caller does not care of the implementing class only the * owning controller. * * @param message to be printed to the screen diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java index 1490c1768cf4..7447522569f3 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java @@ -36,12 +36,9 @@ @Slf4j public class CanonPrinter implements Printer { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void print(String message) { LOGGER.info("Canon Printer : {}", message); } - } diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java index 88240a806cf5..9ede17dedf15 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java @@ -28,20 +28,17 @@ import lombok.extern.slf4j.Slf4j; /** - * Specialised Implementation of {@link Printer} for a Epson Printer, in this case the message to be - * printed is appended to "Epson Printer : ". + * Specialised Implementation of {@link Printer} for an Epson Printer, in this case the message to + * be printed is appended to "Epson Printer : ". * * @see Printer */ @Slf4j public class EpsonPrinter implements Printer { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void print(String message) { LOGGER.info("Epson Printer : {}", message); } - } diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java index 63a4ccbae52c..a7f210bcab44 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java @@ -28,7 +28,7 @@ import lombok.extern.slf4j.Slf4j; /** - * Specialised Implementation of {@link Printer} for a HP Printer, in this case the message to be + * Specialised Implementation of {@link Printer} for an HP Printer, in this case the message to be * printed is appended to "HP Printer : ". * * @see Printer @@ -36,12 +36,9 @@ @Slf4j public class HpPrinter implements Printer { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void print(String message) { LOGGER.info("HP Printer : {}", message); } - } diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java index e0dcf31c42ee..8ad967c9d63f 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java @@ -24,25 +24,19 @@ */ package com.iluwatar.delegation.simple; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Test Entry - */ +import org.junit.jupiter.api.Test; + +/** Application Test Entry */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java index 10d896cfa3ed..7160eb56a273 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java @@ -39,9 +39,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Test for Delegation Pattern - */ +/** Test for Delegation Pattern */ class DelegateTest { private InMemoryAppender appender; @@ -59,7 +57,7 @@ void tearDown() { private static final String MESSAGE = "Test Message Printed"; @Test - void testCanonPrinter() throws Exception { + void testCanonPrinter() { var printerController = new PrinterController(new CanonPrinter()); printerController.print(MESSAGE); @@ -67,7 +65,7 @@ void testCanonPrinter() throws Exception { } @Test - void testHpPrinter() throws Exception { + void testHpPrinter() { var printerController = new PrinterController(new HpPrinter()); printerController.print(MESSAGE); @@ -75,16 +73,14 @@ void testHpPrinter() throws Exception { } @Test - void testEpsonPrinter() throws Exception { + void testEpsonPrinter() { var printerController = new PrinterController(new EpsonPrinter()); printerController.print(MESSAGE); assertEquals("Epson Printer : Test Message Printed", appender.getLastMessage()); } - /** - * Logging Appender - */ + /** Logging Appender */ private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); @@ -107,5 +103,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/dependency-injection/README.md b/dependency-injection/README.md index 0f7a6c8ff011..51b138c20e9e 100644 --- a/dependency-injection/README.md +++ b/dependency-injection/README.md @@ -1,47 +1,55 @@ --- -title: Dependency Injection +title: "Dependency Injection Pattern in Java: Boosting Maintainability with Loose Coupling" +shortTitle: Dependency Injection +description: "Learn about the Dependency Injection design pattern. Explore its benefits, real-world examples, class diagrams, and best practices for implementation in Java." category: Creational language: en tag: - - Decoupling + - Decoupling + - Dependency management + - Inversion of control --- -## Intent +## Also known as -Dependency Injection is a software design pattern in which one or more dependencies (or services) -are injected, or passed by reference, into a dependent object (or client) and are made part of the -client's state. The pattern separates the creation of a client's dependencies from its own behavior, -which allows program designs to be loosely coupled and to follow the inversion of control and single -responsibility principles. +* Inversion of Control (IoC) +* Dependency Inversion -## Explanation +## Intent of Dependency Injection Design Pattern -Real world example +To decouple the creation of object dependencies from their usage, allowing for more flexible and testable code. -> The old wizard likes to fill his pipe and smoke tobacco once in a while. However, he doesn't want -> to depend on a single tobacco brand only but likes to be able to enjoy them all interchangeably. +## Detailed Explanation of Dependency Injection Pattern with Real-World Examples + +Real-world example + +> Imagine a high-end restaurant where the chef needs various ingredients to prepare dishes. Instead of the chef personally going to different suppliers for each ingredient, a trusted supplier delivers all the required fresh ingredients daily. This allows the chef to focus on cooking without worrying about sourcing the ingredients. +> +> In the Dependency Injection design pattern, the trusted supplier acts as the "injector," providing the necessary dependencies (ingredients) to the chef (object). The chef can then use these dependencies without knowing where they came from, ensuring a clean separation between the creation and use of dependencies. This setup enhances efficiency, flexibility, and maintainability in the kitchen, much like in a software system. In plain words -> Dependency Injection separates creation of client's dependencies from its own behavior. +> Dependency Injection separates the creation of the client's dependencies from its own behavior. Wikipedia says -> In software engineering, dependency injection is a technique in which an object receives other -> objects that it depends on. These other objects are called dependencies. +> In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. + +## Programmatic Example of Dependency Injection Pattern in Java -**Programmatic Example** +The old wizard likes to fill his pipe and smoke tobacco once in a while. However, he doesn't want to depend on a single tobacco brand only but likes to be able to enjoy them all interchangeably. Let's first introduce the `Tobacco` interface and the concrete brands. ```java + @Slf4j public abstract class Tobacco { - public void smoke(Wizard wizard) { - LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), - this.getClass().getSimpleName()); - } + public void smoke(Wizard wizard) { + LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), + this.getClass().getSimpleName()); + } } public class SecondBreakfastTobacco extends Tobacco { @@ -59,45 +67,96 @@ Next here's the `Wizard` class hierarchy. ```java public interface Wizard { - void smoke(); + void smoke(); } public class AdvancedWizard implements Wizard { - private final Tobacco tobacco; + private final Tobacco tobacco; - public AdvancedWizard(Tobacco tobacco) { - this.tobacco = tobacco; - } + public AdvancedWizard(Tobacco tobacco) { + this.tobacco = tobacco; + } - @Override - public void smoke() { - tobacco.smoke(this); - } + @Override + public void smoke() { + tobacco.smoke(this); + } } ``` -And lastly we can show how easy it is to give the old wizard any brand of tobacco. +Finally, we can show how easy it is to give the old wizard any brand of tobacco. ```java +public static void main(String[] args) { + var simpleWizard = new SimpleWizard(); + simpleWizard.smoke(); + var advancedWizard = new AdvancedWizard(new SecondBreakfastTobacco()); advancedWizard.smoke(); + + var advancedSorceress = new AdvancedSorceress(); + advancedSorceress.setTobacco(new SecondBreakfastTobacco()); + advancedSorceress.smoke(); + + var injector = Guice.createInjector(new TobaccoModule()); + var guiceWizard = injector.getInstance(GuiceWizard.class); + guiceWizard.smoke(); +} +``` + +The program output: + +``` +11:54:05.205 [main] INFO com.iluwatar.dependency.injection.Tobacco -- SimpleWizard smoking OldTobyTobacco +11:54:05.207 [main] INFO com.iluwatar.dependency.injection.Tobacco -- AdvancedWizard smoking SecondBreakfastTobacco +11:54:05.207 [main] INFO com.iluwatar.dependency.injection.Tobacco -- AdvancedSorceress smoking SecondBreakfastTobacco +11:54:05.308 [main] INFO com.iluwatar.dependency.injection.Tobacco -- GuiceWizard smoking RivendellTobacco ``` -## Class diagram +## Detailed Explanation of Dependency Injection Pattern with Real-World Examples + +![Dependency Injection](./etc/dependency-injection.png "Dependency Injection") + +## When to Use the Dependency Injection Pattern in Java + +* When aiming to reduce the coupling between classes and increase the modularity of the application. +* In scenarios where the object creation process is complex or should be separated from the class usage. +* In applications requiring easier unit testing by allowing dependencies to be mocked or stubbed. +* Within frameworks or libraries that manage object lifecycles and dependencies, such as Spring or Jakarta EE (formerly Java EE). + +## Real-World Applications of Dependency Injection Pattern in Java + +* Frameworks like Spring, Jakarta EE, and Google Guice use Dependency Injection (DI) extensively to manage component lifecycles and dependencies. +* Desktop and web applications that require flexible architecture with easily interchangeable components. + +## Benefits and Trade-offs of Dependency Injection Pattern + +Benefits: + +* Enhances modularity and separation of concerns. +* Simplifies unit testing by allowing for easy mocking of dependencies. +* Increases flexibility and maintainability by promoting loose coupling. -![alt text](./etc/dependency-injection.png "Dependency Injection") +Trade-offs: -## Applicability +* Can introduce complexity in the configuration, especially in large projects. +* Might increase the learning curve for developers unfamiliar with Dependency Injection patterns or frameworks. +* Requires careful management of object lifecycles and scopes. -Use the Dependency Injection pattern when: +## Related Java Design Patterns -* When you need to remove knowledge of concrete implementation from object. -* To enable unit testing of classes in isolation using mock objects or stubs. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) and [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Used to create instances that the DI mechanism will inject. +* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): An alternative to DI for locating services or components, though it does not decouple the lookup process as effectively as DI. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used in conjunction with DI to provide a single instance of a service across the application. -## Credits +## References and Credits -* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd) -* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871) -* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1) -* [Google Guice: Agile Lightweight Dependency Injection Framework](https://www.amazon.com/gp/product/1590599977/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1590599977&linkId=3b10c90b7ba480a1b7777ff38000f956) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/3wRnjp5) +* [Dependency Injection: Design patterns using Spring and Guice](https://amzn.to/4aMyHkI) +* [Dependency Injection Principles, Practices, and Patterns](https://amzn.to/4aupmxe) +* [Google Guice: Agile Lightweight Dependency Injection Framework](https://amzn.to/4bTDbX0) +* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://amzn.to/4ayCtxp) +* [Java Design Pattern Essentials](https://amzn.to/3xtPPxa) +* [Pro Java EE Spring Patterns: Best Practices and Design Strategies Implementing Java EE Patterns with the Spring Framework](https://amzn.to/3J6Teoh) +* [Spring in Action](https://amzn.to/4asnpSG) diff --git a/dependency-injection/pom.xml b/dependency-injection/pom.xml index 71d18bd2e251..f0626f2cd3a6 100644 --- a/dependency-injection/pom.xml +++ b/dependency-injection/pom.xml @@ -34,6 +34,14 @@ dependency-injection + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java index 95f2e08570f6..0ebf875c2120 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java @@ -1,42 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.dependency.injection; - -import lombok.Setter; - -/** - * AdvancedSorceress implements inversion of control. It depends on abstraction that can be injected - * through its setter. - */ -@Setter -public class AdvancedSorceress implements Wizard { - - private Tobacco tobacco; - - @Override - public void smoke() { - tobacco.smoke(this); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dependency.injection; + +import lombok.Setter; + +/** + * AdvancedSorceress implements inversion of control. It depends on abstraction that can be injected + * through its setter. + */ +@Setter +public class AdvancedSorceress implements Wizard { + + private Tobacco tobacco; + + @Override + public void smoke() { + tobacco.smoke(this); + } +} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java index f4d060a104e4..2f8ecf07fcfa 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java @@ -28,20 +28,19 @@ /** * Dependency Injection pattern deals with how objects handle their dependencies. The pattern - * implements so called inversion of control principle. Inversion of control has two specific rules: + * implements so-called inversion of control principle. Inversion of control has two specific rules: * - High-level modules should not depend on low-level modules. Both should depend on abstractions. * - Abstractions should not depend on details. Details should depend on abstractions. * - *

In this example we show you three different wizards. The first one ({@link SimpleWizard}) is - * a naive implementation violating the inversion of control principle. It depends directly on a + *

In this example we show you three different wizards. The first one ({@link SimpleWizard}) is a + * naive implementation violating the inversion of control principle. It depends directly on a * concrete implementation which cannot be changed. * *

The second and third wizards({@link AdvancedWizard} and {@link AdvancedSorceress}) are more - * flexible. They do not depend on any concrete implementation but abstraction. They utilizes + * flexible. They do not depend on any concrete implementation but abstraction. They utilize * Dependency Injection pattern allowing their {@link Tobacco} dependency to be injected through * constructor ({@link AdvancedWizard}) or setter ({@link AdvancedSorceress}). This way, handling - * the dependency is no longer the wizard's responsibility. It is resolved outside the wizard - * class. + * the dependency is no longer the wizard's responsibility. It is resolved outside the wizard class. * *

The fourth example takes the pattern a step further. It uses Guice framework for Dependency * Injection. {@link TobaccoModule} binds a concrete implementation to abstraction. Injector is then diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java index 6538415de3b7..0bdd5479cdee 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java @@ -24,8 +24,5 @@ */ package com.iluwatar.dependency.injection; -/** - * OldTobyTobacco concrete {@link Tobacco} implementation. - */ -public class OldTobyTobacco extends Tobacco { -} +/** OldTobyTobacco concrete {@link Tobacco} implementation. */ +public class OldTobyTobacco extends Tobacco {} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java index 1f971d3cfdcc..1238a759427e 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java @@ -24,8 +24,5 @@ */ package com.iluwatar.dependency.injection; -/** - * RivendellTobacco concrete {@link Tobacco} implementation. - */ -public class RivendellTobacco extends Tobacco { -} +/** RivendellTobacco concrete {@link Tobacco} implementation. */ +public class RivendellTobacco extends Tobacco {} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java index 951df91e6bbd..34cf201f4862 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java @@ -24,8 +24,5 @@ */ package com.iluwatar.dependency.injection; -/** - * SecondBreakfastTobacco concrete {@link Tobacco} implementation. - */ -public class SecondBreakfastTobacco extends Tobacco { -} +/** SecondBreakfastTobacco concrete {@link Tobacco} implementation. */ +public class SecondBreakfastTobacco extends Tobacco {} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java index b62fc6f76220..b1aa7e87cebe 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java @@ -26,14 +26,12 @@ import lombok.extern.slf4j.Slf4j; -/** - * Tobacco abstraction. - */ +/** Tobacco abstraction. */ @Slf4j public abstract class Tobacco { public void smoke(Wizard wizard) { - LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), - this.getClass().getSimpleName()); + LOGGER.info( + "{} smoking {}", wizard.getClass().getSimpleName(), this.getClass().getSimpleName()); } } diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java index 4880878f5bd7..0dc6a6d3bb1f 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java @@ -26,9 +26,7 @@ import com.google.inject.AbstractModule; -/** - * Guice module for binding certain concrete {@link Tobacco} implementation. - */ +/** Guice module for binding certain concrete {@link Tobacco} implementation. */ public class TobaccoModule extends AbstractModule { @Override diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java index 55ceead1210d..4ba6417275a9 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java @@ -24,11 +24,8 @@ */ package com.iluwatar.dependency.injection; -/** - * Wizard interface. - */ +/** Wizard interface. */ public interface Wizard { void smoke(); - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java index 6680985c19a2..f2cf32db0038 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java @@ -1,82 +1,74 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.dependency.injection; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.iluwatar.dependency.injection.utils.InMemoryAppender; -import java.util.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - - -/** - * Date: 28/04/17 - 7:40 AM - * - * @author Stanislav Kapinus - */ - -class AdvancedSorceressTest { - - private InMemoryAppender appender; - - @BeforeEach - void setUp() { - appender = new InMemoryAppender(Tobacco.class); - } - - @AfterEach - void tearDown() { - appender.stop(); - } - - /** - * Test if the {@link AdvancedSorceress} smokes whatever instance of {@link Tobacco} is passed to - * her through the setter's parameter - */ - @Test - void testSmokeEveryThing() throws Exception { - - List tobaccos = List.of( - new OldTobyTobacco(), - new RivendellTobacco(), - new SecondBreakfastTobacco() - ); - - // Verify if the sorceress is smoking the correct tobacco ... - tobaccos.forEach(tobacco -> { - final var advancedSorceress = new AdvancedSorceress(); - advancedSorceress.setTobacco(tobacco); - advancedSorceress.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("AdvancedSorceress smoking " + tobacco.getClass().getSimpleName(), lastMessage); - }); - - // ... and nothing else is happening. - assertEquals(tobaccos.size(), appender.getLogSize()); - - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dependency.injection; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.iluwatar.dependency.injection.utils.InMemoryAppender; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** AdvancedSorceressTest */ +class AdvancedSorceressTest { + + private InMemoryAppender appender; + + @BeforeEach + void setUp() { + appender = new InMemoryAppender(Tobacco.class); + } + + @AfterEach + void tearDown() { + appender.stop(); + } + + /** + * Test if the {@link AdvancedSorceress} smokes whatever instance of {@link Tobacco} is passed to + * her through the setter's parameter + */ + @Test + void testSmokeEveryThing() { + + List tobaccos = + List.of(new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco()); + + // Verify if the sorceress is smoking the correct tobacco ... + tobaccos.forEach( + tobacco -> { + final var advancedSorceress = new AdvancedSorceress(); + advancedSorceress.setTobacco(tobacco); + advancedSorceress.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals( + "AdvancedSorceress smoking " + tobacco.getClass().getSimpleName(), lastMessage); + }); + + // ... and nothing else is happening. + assertEquals(tobaccos.size(), appender.getLogSize()); + } +} diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java index bad5ee804199..7a1692db6784 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java @@ -32,12 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -/** - * Date: 12/10/15 - 8:40 PM - * - * @author Jeroen Meulemeester - */ +/** AdvancedWizardTest */ class AdvancedWizardTest { private InMemoryAppender appender; @@ -57,25 +52,21 @@ void tearDown() { * through the constructor parameter */ @Test - void testSmokeEveryThing() throws Exception { + void testSmokeEveryThing() { - List tobaccos = List.of( - new OldTobyTobacco(), - new RivendellTobacco(), - new SecondBreakfastTobacco() - ); + List tobaccos = + List.of(new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco()); // Verify if the wizard is smoking the correct tobacco ... - tobaccos.forEach(tobacco -> { - final AdvancedWizard advancedWizard = new AdvancedWizard(tobacco); - advancedWizard.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("AdvancedWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); - }); + tobaccos.forEach( + tobacco -> { + final AdvancedWizard advancedWizard = new AdvancedWizard(tobacco); + advancedWizard.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals("AdvancedWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); + }); // ... and nothing else is happening. assertEquals(tobaccos.size(), appender.getLogSize()); - } - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java index 2684cfcd2c47..1894227ec1d0 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.dependency.injection; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java index 5d4c991c1190..400a079d9845 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java @@ -34,11 +34,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/10/15 - 8:57 PM - * - * @author Jeroen Meulemeester - */ +/** GuiceWizardTest */ class GuiceWizardTest { private InMemoryAppender appender; @@ -58,21 +54,19 @@ void tearDown() { * through the constructor parameter */ @Test - void testSmokeEveryThingThroughConstructor() throws Exception { + void testSmokeEveryThingThroughConstructor() { - List tobaccos = List.of( - new OldTobyTobacco(), - new RivendellTobacco(), - new SecondBreakfastTobacco() - ); + List tobaccos = + List.of(new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco()); // Verify if the wizard is smoking the correct tobacco ... - tobaccos.forEach(tobacco -> { - final GuiceWizard guiceWizard = new GuiceWizard(tobacco); - guiceWizard.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("GuiceWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); - }); + tobaccos.forEach( + tobacco -> { + final GuiceWizard guiceWizard = new GuiceWizard(tobacco); + guiceWizard.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals("GuiceWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); + }); // ... and nothing else is happening. assertEquals(tobaccos.size(), appender.getLogSize()); @@ -83,32 +77,31 @@ void testSmokeEveryThingThroughConstructor() throws Exception { * through the Guice google inject framework */ @Test - void testSmokeEveryThingThroughInjectionFramework() throws Exception { + void testSmokeEveryThingThroughInjectionFramework() { - List> tobaccos = List.of( - OldTobyTobacco.class, - RivendellTobacco.class, - SecondBreakfastTobacco.class - ); + List> tobaccos = + List.of(OldTobyTobacco.class, RivendellTobacco.class, SecondBreakfastTobacco.class); // Configure the tobacco in the injection framework ... // ... and create a new wizard with it // Verify if the wizard is smoking the correct tobacco ... - tobaccos.forEach(tobaccoClass -> { - final var injector = Guice.createInjector(new AbstractModule() { - @Override - protected void configure() { - bind(Tobacco.class).to(tobaccoClass); - } - }); - final var guiceWizard = injector.getInstance(GuiceWizard.class); - guiceWizard.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("GuiceWizard smoking " + tobaccoClass.getSimpleName(), lastMessage); - }); + tobaccos.forEach( + tobaccoClass -> { + final var injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(Tobacco.class).to(tobaccoClass); + } + }); + final var guiceWizard = injector.getInstance(GuiceWizard.class); + guiceWizard.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals("GuiceWizard smoking " + tobaccoClass.getSimpleName(), lastMessage); + }); // ... and nothing else is happening. assertEquals(tobaccos.size(), appender.getLogSize()); } - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java index c4a80e6b2e33..f9354b49b9d7 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java @@ -31,11 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/10/15 - 8:26 PM - * - * @author Jeroen Meulemeester - */ +/** SimpleWizardTest */ class SimpleWizardTest { private InMemoryAppender appender; @@ -61,5 +57,4 @@ void testSmoke() { assertEquals("SimpleWizard smoking OldTobyTobacco", appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java index f39877810afb..319216e187c4 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java @@ -27,14 +27,11 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; -import org.slf4j.LoggerFactory; import java.util.LinkedList; import java.util.List; +import org.slf4j.LoggerFactory; - -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/dirty-flag/README.md b/dirty-flag/README.md index 5eb6c052b8ff..6abcedad18b1 100644 --- a/dirty-flag/README.md +++ b/dirty-flag/README.md @@ -1,28 +1,150 @@ --- -title: Dirty Flag +title: "Dirty Flag Pattern in Java: Optimizing Performance with Change Tracking" +shortTitle: Dirty Flag +description: "Learn about the Dirty Flag design pattern in Java for efficient state tracking and resource management. Avoid unnecessary computations with practical examples and use cases." category: Behavioral language: en tag: - - Game programming - - Performance + - Game programming + - Performance + - Resource management + - State tracking --- ## Also known as -* IsDirty pattern -## Intent -To avoid expensive re-acquisition of resources. The resources retain their identity, are kept in some -fast-access storage, and are re-used to avoid having to acquire them again. +* Change Tracking +* Is-Modified Flag -## Class diagram -![alt text](./etc/dirty-flag.png "Dirty Flag") +## Intent of Dirty Flag Design Pattern -## Applicability -Use the Dirty Flag pattern when +The Dirty Flag design pattern is employed to avoid unnecessary computations or resource-heavy operations by maintaining a boolean flag that tracks whether the state of an object has changed ('dirty') or remains unchanged ('clean'). This flag, when set, indicates that a particular operation, such as recalculating or refreshing data, needs to be performed again to reflect the updated state. -* Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead. +## Detailed Explanation of Dirty Flag Pattern with Real-World Examples -## Credits +Real-world example -* [Design Patterns: Dirty Flag](https://www.takeupcode.com/podcast/89-design-patterns-dirty-flag/) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +> Imagine a library with an electronic catalog system that tracks which books are checked out and returned. Each book record has a "dirty flag" that gets marked whenever a book is checked out or returned. At the end of each day, the library staff reviews only those records marked as "dirty" to update the physical inventory, instead of checking every single book in the library. This system significantly reduces the effort and time required for daily inventory checks by focusing only on the items that have changed status, analogous to how the Dirty Flag design pattern minimizes resource-intensive operations by performing them only when necessary. + +In plain words + +> The Dirty Flag design pattern minimizes unnecessary operations by using a flag to track when an object's state has changed and an update is needed. + +Wikipedia says + +> A dirty bit or modified bit is a bit that is associated with a block of computer memory and indicates whether the corresponding block of memory has been modified. The dirty bit is set when the processor writes to (modifies) this memory. The bit indicates that its associated block of memory has been modified and has not been saved to storage yet. When a block of memory is to be replaced, its corresponding dirty bit is checked to see if the block needs to be written back to secondary memory before being replaced or if it can simply be removed. Dirty bits are used by the CPU cache and in the page replacement algorithms of an operating system. + +## Programmatic Example of Dirty Flag Pattern in Java + +The `DataFetcher` class is responsible for fetching data from a file. It has a dirty flag that indicates whether the data in the file has changed since the last fetch. + +```java +public class DataFetcher { + private long lastFetched; + private boolean isDirty = true; + // Other properties and methods... +} +``` + +The `DataFetcher` class has a fetch method that checks the dirty flag before fetching data. If the flag is true, it fetches the data from the file and sets the flag to false. If the flag is false, it returns the previously fetched data. + +```java +public List fetch() { + if (!isDirty) { + return data; + } + data = fetchFromDatabase(); + isDirty = false; + return data; +} +``` + +The `World` class uses the `DataFetcher` to fetch data. It has a `fetch` method that calls the `fetch` method of the `DataFetcher`. + +```java +public class World { + private final DataFetcher fetcher = new DataFetcher(); + + public List fetch() { + return fetcher.fetch(); + } +} +``` + +The `App` class contains the `main` method that demonstrates the use of the Dirty Flag pattern. It creates a `World` object and fetches data from it in a loop. The `World` object uses the `DataFetcher` to fetch data, and the `DataFetcher` only fetches data from the file if the dirty flag is true. + +```java +@Slf4j +public class App { + + public void run() { + final var executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleAtFixedRate(new Runnable() { + final World world = new World(); + + @Override + public void run() { + var countries = world.fetch(); + LOGGER.info("Our world currently has the following countries:-"); + countries.stream().map(country -> "\t" + country).forEach(LOGGER::info); + } + }, 0, 15, TimeUnit.SECONDS); // Run at every 15 seconds. + } + + public static void main(String[] args) { + var app = new App(); + app.run(); + } +} +``` + +The program output is as follows: + +``` +12:06:02.612 [pool-1-thread-1] INFO com.iluwatar.dirtyflag.DataFetcher -- world.txt is dirty! Re-fetching file content... +12:06:02.615 [pool-1-thread-1] INFO com.iluwatar.dirtyflag.App -- Our world currently has the following countries:- +12:06:02.616 [pool-1-thread-1] INFO com.iluwatar.dirtyflag.App -- UNITED_KINGDOM +12:06:02.616 [pool-1-thread-1] INFO com.iluwatar.dirtyflag.App -- MALAYSIA +12:06:02.616 [pool-1-thread-1] INFO com.iluwatar.dirtyflag.App -- UNITED_STATES +``` + +## When to Use the Dirty Flag Pattern in Java + +* When an operation is resource-intensive and only necessary after certain changes have occurred. +* In scenarios where checking for changes is significantly cheaper than performing the operation itself, enhancing cost-effectiveness. +* Within systems where objects maintain state that is expensive to update and the updates are infrequent, promoting performance efficiency. + +## Dirty Flag Pattern Java Tutorials + +* [89: Design Patterns: Dirty Flag (TakeUpCode)](https://www.takeupcode.com/podcast/89-design-patterns-dirty-flag/) + +## Real-World Applications of Dirty Flag Pattern in Java + +* Graphic rendering engines to update only parts of the scene that have changed, utilizing the Dirty Flag pattern for efficient rendering. +* Web applications for partial page rendering or caching strategies. +* Database applications for tracking changes in datasets to minimize write operations, ensuring efficient database management. + +## Benefits and Trade-offs of Dirty Flag Pattern + +Benefits: + +* Reduces computational and resource overhead by avoiding unnecessary operations, leading to performance gains. +* Can significantly improve performance in systems where operations are costly and changes are infrequent, fostering system optimization. +* Simplifies the decision-making process about when to perform certain operations, aiding in effective resource allocation. + +Trade-offs: + +* Introduces complexity by adding state management responsibility to the system. +* Requires diligent management of the flag to ensure it accurately reflects the state changes, avoiding stale or incorrect data. +* Potentially increases the risk of bugs related to improper flag resetting, impacting system reliability. + +## Related Java Design Patterns + +* [Observer](https://java-design-patterns.com/patterns/observer/): Can be used in conjunction to notify interested parties when the dirty flag is set or cleared. +* [Memento](https://java-design-patterns.com/patterns/memento/): Useful for storing the previous state of an object, which can work hand in hand with dirty flag logic to revert to clean states. +* [Command](https://java-design-patterns.com/patterns/command/): Commands can set the dirty flag when executed, indicating a change in state that requires attention. + +## References and Credits + +* [Game Programming Patterns](https://amzn.to/3PUzbgu) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) diff --git a/dirty-flag/pom.xml b/dirty-flag/pom.xml index 2bbbf285dbc6..d6032c589320 100644 --- a/dirty-flag/pom.xml +++ b/dirty-flag/pom.xml @@ -40,6 +40,14 @@ UTF-8 + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java index 391554cb36be..ed1ff06bba95 100644 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java +++ b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java @@ -36,16 +36,16 @@ * calculated will need to be calculated again when they’re requested. Once the results are * re-calculated, then the bool value can be cleared. * - *

There are some points that need to be considered before diving into using this pattern:- - * there are some things you’ll need to consider:- (1) Do you need it? This design pattern works - * well when the results to be calculated are difficult or resource intensive to compute. You want - * to save them. You also don’t want to be calculating them several times in a row when only the - * last one counts. (2) When do you set the dirty flag? Make sure that you set the dirty flag within - * the class itself whenever an important property changes. This property should affect the result - * of the calculated result and by changing the property, that makes the last result invalid. (3) - * When do you clear the dirty flag? It might seem obvious that the dirty flag should be cleared - * whenever the result is calculated with up-to-date information but there are other times when you - * might want to clear the flag. + *

There are some points that need to be considered before diving into using this pattern:- there + * are some things you’ll need to consider:- (1) Do you need it? This design pattern works well when + * the results to be calculated are difficult or resource intensive to compute. You want to save + * them. You also don’t want to be calculating them several times in a row when only the last one + * counts. (2) When do you set the dirty flag? Make sure that you set the dirty flag within the + * class itself whenever an important property changes. This property should affect the result of + * the calculated result and by changing the property, that makes the last result invalid. (3) When + * do you clear the dirty flag? It might seem obvious that the dirty flag should be cleared whenever + * the result is calculated with up-to-date information but there are other times when you might + * want to clear the flag. * *

In this example, the {@link DataFetcher} holds the dirty flag. It fetches and * re-fetches from world.txt when needed. {@link World} mainly serves the data to the @@ -54,21 +54,33 @@ @Slf4j public class App { - /** - * Program execution point. - */ + /** Program execution point. */ public void run() { final var executorService = Executors.newSingleThreadScheduledExecutor(); - executorService.scheduleAtFixedRate(new Runnable() { - final World world = new World(); + try { + executorService.scheduleAtFixedRate( + new Runnable() { + final World world = new World(); - @Override - public void run() { - var countries = world.fetch(); - LOGGER.info("Our world currently has the following countries:-"); - countries.stream().map(country -> "\t" + country).forEach(LOGGER::info); - } - }, 0, 15, TimeUnit.SECONDS); // Run at every 15 seconds. + @Override + public void run() { + var countries = world.fetch(); + LOGGER.info("Our world currently has the following countries:-"); + countries.stream().map(country -> "\t" + country).forEach(LOGGER::info); + } + }, + 0, + 15, + TimeUnit.SECONDS); + + // Keep running for 45 seconds before shutdown (for demo purpose) + TimeUnit.SECONDS.sleep(45); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.error("Thread was interrupted", e); + } finally { + executorService.shutdown(); + } } /** @@ -80,5 +92,4 @@ public static void main(String[] args) { var app = new App(); app.run(); } - } diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java index 81bd3b48aba5..0d902b3c058b 100644 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java +++ b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java @@ -32,15 +32,11 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -/** - * A mock database manager -- Fetches data from a raw file. - * - * @author swaisuan - */ +/** A mock database manager -- Fetches data from a raw file. */ @Slf4j public class DataFetcher { - private final String filename = "world.txt"; + private static final String FILENAME = "world.txt"; private long lastFetched; public DataFetcher() { @@ -62,14 +58,14 @@ private boolean isDirty(long fileLastModified) { */ public List fetch() { var classLoader = getClass().getClassLoader(); - var file = new File(classLoader.getResource(filename).getFile()); + var file = new File(classLoader.getResource(FILENAME).getFile()); if (isDirty(file.lastModified())) { - LOGGER.info(filename + " is dirty! Re-fetching file content..."); + LOGGER.info(FILENAME + " is dirty! Re-fetching file content..."); try (var br = new BufferedReader(new FileReader(file))) { return br.lines().collect(Collectors.collectingAndThen(Collectors.toList(), List::copyOf)); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("An error occurred: ", e); } } diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java index ef66d6ee5775..bc876814b4d6 100644 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java +++ b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java @@ -27,11 +27,7 @@ import java.util.ArrayList; import java.util.List; -/** - * A middle-layer app that calls/passes along data from the back-end. - * - * @author swaisuan - */ +/** A middle-layer app that calls/passes along data from the back-end. */ public class World { private List countries; diff --git a/dirty-flag/src/main/resources/world.txt b/dirty-flag/src/main/resources/world.txt index 0c25bc5c53f0..280ea370239c 100644 --- a/dirty-flag/src/main/resources/world.txt +++ b/dirty-flag/src/main/resources/world.txt @@ -1,26 +1,3 @@ -==== - The MIT License - Copyright © 2014-2021 Ilkka Seppälä - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -==== - UNITED_KINGDOM MALAYSIA UNITED_STATES \ No newline at end of file diff --git a/dirty-flag/src/test/java/org/dirty/flag/AppTest.java b/dirty-flag/src/test/java/org/dirty/flag/AppTest.java index c2d3f86aed37..8e09b01928c3 100644 --- a/dirty-flag/src/test/java/org/dirty/flag/AppTest.java +++ b/dirty-flag/src/test/java/org/dirty/flag/AppTest.java @@ -24,25 +24,20 @@ */ package org.dirty.flag; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.dirtyflag.App; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Tests that Dirty-Flag example runs without errors. - */ +/** Tests that Dirty-Flag example runs without errors. */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java b/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java index de0c62903103..7d512deafe7b 100644 --- a/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java +++ b/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class DirtyFlagTest { @Test diff --git a/domain-model/README.md b/domain-model/README.md index aa7e32bd0a21..5a914c7efdc3 100644 --- a/domain-model/README.md +++ b/domain-model/README.md @@ -1,323 +1,276 @@ --- -title: Domain Model -category: Architectural +title: "Domain Model Pattern in Java: Building Robust Business Logic" +shortTitle: Domain Model +description: "Learn about the Domain Model pattern in Java design with detailed explanations, examples, and applications. Improve your software's modularity, maintainability, and scalability." +category: Structural language: en tag: - - Domain + - Abstraction + - Business + - Data processing + - Domain + - Encapsulation + - Layered architecture + - Persistence + - Polymorphism --- -## Intent +## Also known as -Domain model pattern provides an object-oriented way of dealing with complicated logic. Instead of having one procedure that handles all business logic for a user action there are multiple objects and each of them handles a slice of domain logic that is relevant to it. +* Conceptual Model +* Domain Object Model -## Explanation +## Intent of Domain Model Design Pattern -Real world example +The Domain Model pattern aims to create a conceptual model in your software that matches the real-world system it's designed to represent. It involves using rich domain objects that encapsulate both data and behavior relevant to the application domain, ensuring business logic is centralized. -> Let's assume that we need to build an e-commerce web application. While analyzing requirements you will notice that there are few nouns you talk about repeatedly. It’s your Customer, and a Product the customer looks for. These two are your domain-specific classes and each of that classes will include some business logic specific to its domain. +## Detailed Explanation of Domain Model Pattern with Real-World Examples + +Real-world example + +> Consider an online bookstore system that uses the Domain Model design pattern. In this system, various domain objects such as `Book`, `Author`, `Customer`, and `Order` encapsulate the core business logic and rules. For instance, the `Book` object contains attributes like title, author, price, and stock quantity, along with methods to manage these attributes. The `Order` object manages order details, calculates the total price, and verifies stock availability. This approach ensures that the business logic is centralized within the domain objects, making the system more modular, easier to maintain, and scalable as new features are added. In plain words > The Domain Model is an object model of the domain that incorporates both behavior and data. -Programmatic Example +## Programmatic Example of Domain Model Pattern in Java -In the example of the e-commerce app, we need to deal with the domain logic of customers who want to buy products and return them if they want. We can use the domain model pattern and create classes `Customer` and `Product` where every single instance of that class incorporates both behavior and data and represents only one record in the underlying table. +Let's assume that we need to build an e-commerce web application. While analyzing requirements you will notice that there are few nouns you talk about repeatedly. It’s your Customer, and a Product the customer looks for. These two are your domain-specific classes and each of that classes will include some business logic specific to its domain. -Here is the `Product` domain class with fields `name`, `price`, `expirationDate` which is specific for each product, `productDao` for working with DB, `save` method for saving product and `getSalePrice` method which return price for this product with discount. +In the example of the e-commerce app, we need to deal with the domain logic of customers who want to buy products and return them if they want. We can use the domain model pattern and create classes `Customer` and `Product` where every single instance of that class incorporates both behavior and data and represents only one record in the underlying table. ```java -@Slf4j -@Getter -@Setter -@Builder -@AllArgsConstructor -public class Product { - - private static final int DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE = 4; - private static final double DISCOUNT_RATE = 0.2; - - @NonNull private final ProductDao productDao; - @NonNull private String name; - @NonNull private Money price; - @NonNull private LocalDate expirationDate; - - /** - * Save product or update if product already exist. - */ - public void save() { - try { - Optional product = productDao.findByName(name); - if (product.isPresent()) { - productDao.update(this); - } else { - productDao.save(this); - } - } catch (SQLException ex) { - LOGGER.error(ex.getMessage()); - } - } +public class Customer { + // Customer properties and methods +} - /** - * Calculate sale price of product with discount. - */ - public Money getSalePrice() { - return price.minus(calculateDiscount()); - } +public class Product { + // Product properties and methods +} +``` - private Money calculateDiscount() { - if (ChronoUnit.DAYS.between(LocalDate.now(), expirationDate) - < DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) { +Data Access Objects (DAOs): These objects provide an abstract interface to the database. They are used to retrieve domain entities and save changes back to the database. In the provided code, `CustomerDaoImpl` and `ProductDaoImpl` are the DAOs. - return price.multipliedBy(DISCOUNT_RATE, RoundingMode.DOWN); - } +```java +public class CustomerDaoImpl implements CustomerDao { + // Implementation of the methods defined in the CustomerDao interface +} - return Money.zero(USD); - } +public class ProductDaoImpl implements ProductDao { + // Implementation of the methods defined in the ProductDao interface } ``` -Here is the `Customer` domain class with fields `name`, `money` which is specific for each customer, `customerDao` for working with DB, `save` for saving customer, `buyProduct` which add a product to purchases and withdraw money, `returnProduct` which remove product from purchases and return money, `showPurchases` and `showBalance` methods for printing customer's purchases and money balance. +Domain Logic: This is encapsulated within the domain entities. For example, the `Customer` class has methods like `buyProduct` and `returnProduct` which represent the actions a customer can perform. ```java -@Slf4j -@Getter -@Setter -@Builder public class Customer { + + // Other properties and methods... - @NonNull private final CustomerDao customerDao; - @Builder.Default private List purchases = new ArrayList<>(); - @NonNull private String name; - @NonNull private Money money; - - /** - * Save customer or update if customer already exist. - */ - public void save() { - try { - Optional customer = customerDao.findByName(name); - if (customer.isPresent()) { - customerDao.update(this); - } else { - customerDao.save(this); - } - } catch (SQLException ex) { - LOGGER.error(ex.getMessage()); - } - } - - /** - * Add product to purchases, save to db and withdraw money. - * - * @param product to buy. - */ public void buyProduct(Product product) { - LOGGER.info( - String.format( - "%s want to buy %s($%.2f)...", - name, product.getName(), product.getSalePrice().getAmount())); - try { - withdraw(product.getSalePrice()); - } catch (IllegalArgumentException ex) { - LOGGER.error(ex.getMessage()); - return; - } - try { - customerDao.addProduct(product, this); - purchases.add(product); - LOGGER.info(String.format("%s bought %s!", name, product.getName())); - } catch (SQLException exception) { - receiveMoney(product.getSalePrice()); - LOGGER.error(exception.getMessage()); - } + // Implementation of buying a product } - /** - * Remove product from purchases, delete from db and return money. - * - * @param product to return. - */ public void returnProduct(Product product) { - LOGGER.info( - String.format( - "%s want to return %s($%.2f)...", - name, product.getName(), product.getSalePrice().getAmount())); - if (purchases.contains(product)) { - try { - customerDao.deleteProduct(product, this); - purchases.remove(product); - receiveMoney(product.getSalePrice()); - LOGGER.info(String.format("%s returned %s!", name, product.getName())); - } catch (SQLException ex) { - LOGGER.error(ex.getMessage()); - } - } else { - LOGGER.error(String.format("%s didn't buy %s...", name, product.getName())); - } + // Implementation of returning a product } +} +``` - /** - * Print customer's purchases. - */ - public void showPurchases() { - Optional purchasesToShow = - purchases.stream() - .map(p -> p.getName() + " - $" + p.getSalePrice().getAmount()) - .reduce((p1, p2) -> p1 + ", " + p2); - - if (purchasesToShow.isPresent()) { - LOGGER.info(name + " bought: " + purchasesToShow.get()); - } else { - LOGGER.info(name + " didn't bought anything"); - } +Application: The `App` class uses the domain entities and their methods to implement the business logic of the application. + +```java +public class App { + + public static final String H2_DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; + + public static final String CREATE_SCHEMA_SQL = + "CREATE TABLE CUSTOMERS (name varchar primary key, money decimal);" + + "CREATE TABLE PRODUCTS (name varchar primary key, price decimal, expiration_date date);" + + "CREATE TABLE PURCHASES (" + + "product_name varchar references PRODUCTS(name)," + + "customer_name varchar references CUSTOMERS(name));"; + + public static final String DELETE_SCHEMA_SQL = + "DROP TABLE PURCHASES IF EXISTS;" + + "DROP TABLE CUSTOMERS IF EXISTS;" + + "DROP TABLE PRODUCTS IF EXISTS;"; + + public static void main(String[] args) throws Exception { + + // Create data source and create the customers, products and purchases tables + final var dataSource = createDataSource(); + deleteSchema(dataSource); + createSchema(dataSource); + + // create customer + var customerDao = new CustomerDaoImpl(dataSource); + + var tom = + Customer.builder() + .name("Tom") + .money(Money.of(USD, 30)) + .customerDao(customerDao) + .build(); + + tom.save(); + + // create products + var productDao = new ProductDaoImpl(dataSource); + + var eggs = + Product.builder() + .name("Eggs") + .price(Money.of(USD, 10.0)) + .expirationDate(LocalDate.now().plusDays(7)) + .productDao(productDao) + .build(); + + var butter = + Product.builder() + .name("Butter") + .price(Money.of(USD, 20.00)) + .expirationDate(LocalDate.now().plusDays(9)) + .productDao(productDao) + .build(); + + var cheese = + Product.builder() + .name("Cheese") + .price(Money.of(USD, 25.0)) + .expirationDate(LocalDate.now().plusDays(2)) + .productDao(productDao) + .build(); + + eggs.save(); + butter.save(); + cheese.save(); + + // show money balance of customer after each purchase + tom.showBalance(); + tom.showPurchases(); + + // buy eggs + tom.buyProduct(eggs); + tom.showBalance(); + + // buy butter + tom.buyProduct(butter); + tom.showBalance(); + + // trying to buy cheese, but receive a refusal + // because he didn't have enough money + tom.buyProduct(cheese); + tom.showBalance(); + + // return butter and get money back + tom.returnProduct(butter); + tom.showBalance(); + + // Tom can buy cheese now because he has enough money + // and there is a discount on cheese because it expires in 2 days + tom.buyProduct(cheese); + + tom.save(); + + // show money balance and purchases after shopping + tom.showBalance(); + tom.showPurchases(); } - /** - * Print customer's money balance. - */ - public void showBalance() { - LOGGER.info(name + " balance: " + money); + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setUrl(H2_DB_URL); + return dataSource; } - private void withdraw(Money amount) throws IllegalArgumentException { - if (money.compareTo(amount) < 0) { - throw new IllegalArgumentException("Not enough money!"); + private static void deleteSchema(DataSource dataSource) throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(DELETE_SCHEMA_SQL); } - money = money.minus(amount); } - private void receiveMoney(Money amount) { - money = money.plus(amount); + private static void createSchema(DataSource dataSource) throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(CREATE_SCHEMA_SQL); + } } } ``` -In the class `App`, we create a new instance of class Customer which represents customer Tom and handle data and actions of that customer and creating three products that Tom wants to buy. - +The program output: ```java -// Create data source and create the customers, products and purchases tables -final var dataSource = createDataSource(); -deleteSchema(dataSource); -createSchema(dataSource); - -// create customer -var customerDao = new CustomerDaoImpl(dataSource); - -var tom = - Customer.builder() - .name("Tom") - .money(Money.of(USD, 30)) - .customerDao(customerDao) - .build(); - -tom.save(); - -// create products -var productDao = new ProductDaoImpl(dataSource); - -var eggs = - Product.builder() - .name("Eggs") - .price(Money.of(USD, 10.0)) - .expirationDate(LocalDate.now().plusDays(7)) - .productDao(productDao) - .build(); - -var butter = - Product.builder() - .name("Butter") - .price(Money.of(USD, 20.00)) - .expirationDate(LocalDate.now().plusDays(9)) - .productDao(productDao) - .build(); - -var cheese = - Product.builder() - .name("Cheese") - .price(Money.of(USD, 25.0)) - .expirationDate(LocalDate.now().plusDays(2)) - .productDao(productDao) - .build(); - -eggs.save(); -butter.save(); -cheese.save(); - -// show money balance of customer after each purchase -tom.showBalance(); -tom.showPurchases(); - -// buy eggs -tom.buyProduct(eggs); -tom.showBalance(); - -// buy butter -tom.buyProduct(butter); -tom.showBalance(); - -// trying to buy cheese, but receive a refusal -// because he didn't have enough money -tom.buyProduct(cheese); -tom.showBalance(); - -// return butter and get money back -tom.returnProduct(butter); -tom.showBalance(); - -// Tom can buy cheese now because he has enough money -// and there is a discount on cheese because it expires in 2 days -tom.buyProduct(cheese); - -tom.save(); - -// show money balance and purchases after shopping -tom.showBalance(); -tom.showPurchases(); +12:17:23.834 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 30.00 +12:17:23.836 [main] INFO com.iluwatar.domainmodel.Customer -- Tom didn't bought anything +12:17:23.841 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Eggs($10,00)... +12:17:23.842 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought Eggs! +12:17:23.842 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 20.00 +12:17:23.842 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Butter($20,00)... +12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought Butter! +12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 0.00 +12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Cheese($20,00)... +12:17:23.843 [main] ERROR com.iluwatar.domainmodel.Customer -- Not enough money! +12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 0.00 +12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to return Butter($20,00)... +12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom returned Butter! +12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 20.00 +12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Cheese($20,00)... +12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought Cheese! +12:17:23.846 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 0.00 +12:17:23.846 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought: Eggs - $10.00, Cheese - $20.00 ``` -The program output: +## Detailed Explanation of Domain Model Pattern with Real-World Examples -```java -17:52:28.690 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 30.00 -17:52:28.695 [main] INFO com.iluwatar.domainmodel.Customer - Tom didn't bought anything -17:52:28.699 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Eggs($10.00)... -17:52:28.705 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought Eggs! -17:52:28.705 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 20.00 -17:52:28.705 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Butter($20.00)... -17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought Butter! -17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 0.00 -17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Cheese($20.00)... -17:52:28.712 [main] ERROR com.iluwatar.domainmodel.Customer - Not enough money! -17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 0.00 -17:52:28.712 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to return Butter($20.00)... -17:52:28.721 [main] INFO com.iluwatar.domainmodel.Customer - Tom returned Butter! -17:52:28.721 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 20.00 -17:52:28.721 [main] INFO com.iluwatar.domainmodel.Customer - Tom want to buy Cheese($20.00)... -17:52:28.726 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought Cheese! -17:52:28.737 [main] INFO com.iluwatar.domainmodel.Customer - Tom balance: USD 0.00 -17:52:28.738 [main] INFO com.iluwatar.domainmodel.Customer - Tom bought: Eggs - $10.00, Cheese - $20.00 -``` +![Domain Model class diagram](./etc/domain-model.urm.png "Domain Model") + +## When to Use the Domain Model Pattern in Java + +* Appropriate in complex applications with rich business logic. +* When the business logic or domain complexity is high and requires a model that closely represents real-world entities and their relationships. +* Suitable for applications where domain experts are involved in the development process to ensure the model accurately reflects domain concepts. + +## Domain Model Pattern Java Tutorials + +* [Architecture patterns: domain model and friends (Inviqa)](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends) + +## Real-World Applications of Domain Model Pattern in Java + +* Enterprise applications (ERP, CRM systems) +* Financial systems (banking, trading platforms) +* Healthcare applications (patient records management) +* E-commerce platforms (product catalogs, shopping carts) -## Class diagram +## Benefits and Trade-offs of Domain Model Pattern -![](./etc/domain-model.urm.png "domain model") +Benefits: -## Applicability +* Improved Communication: Provides a common language for developers and domain experts, enhancing understanding and collaboration. +* Flexibility: Encapsulates business logic within domain entities, making it easier to modify and extend without affecting other system parts. +* Maintainability: A well-structured domain model can simplify maintenance and evolution of the application over time. +* Reusability: Domain classes can often be reused across different projects within the same domain. -Use a Domain model pattern when your domain logic is complex and that complexity can rapidly grow because this pattern handles increasing complexity very well. Otherwise, it's a more complex solution for organizing domain logic, so shouldn't use Domain Model pattern for systems with simple domain logic, because the cost of understanding it and complexity of data source exceeds the benefit of this pattern. +Trade-offs: -## Related patterns +* Complexity: Can introduce complexity, especially in simple applications where a domain model might be overkill. +* Performance Concerns: Rich domain objects with complex behaviors might lead to performance bottlenecks, requiring careful optimization. +* Learning Curve: Requires a good understanding of the domain and may involve a steep learning curve for developers unfamiliar with the domain concepts. -- [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/) +## Related Java Design Patterns -- [Table Module](https://java-design-patterns.com/patterns/table-module/) - -- [Service Layer](https://java-design-patterns.com/patterns/service-layer/) +* [Data Access Object (DAO)](https://java-design-patterns.com/patterns/dao/): For abstracting and encapsulating all access to the data source. +* [Repository](https://java-design-patterns.com/patterns/repository/): Mediates between the domain and data mapping layers, acting like an in-memory domain object collection. +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/): Defines an application's boundary with a layer of services that establishes a set of available operations and coordinates the application's response in each operation. +* [Unit of Work](https://java-design-patterns.com/patterns/unit-of-work/): Maintains a list of objects affected by a business transaction and coordinates the writing out of changes. -## Credits +## References and Credits -* [Domain Model Pattern](https://martinfowler.com/eaaCatalog/domainModel.html) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04) -* [Architecture patterns: domain model and friends](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3vMCjnP) +* [Implementing Domain-Driven Design](https://amzn.to/4cUX4OL) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Domain Model (Martin Fowler)](https://martinfowler.com/eaaCatalog/domainModel.html) diff --git a/domain-model/pom.xml b/domain-model/pom.xml index 19353550275b..933c186790b0 100644 --- a/domain-model/pom.xml +++ b/domain-model/pom.xml @@ -34,6 +34,14 @@ 4.0.0 domain-model + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.h2database h2 @@ -51,7 +59,7 @@ org.joda joda-money - 1.0.1 + 1.0.4 diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/App.java b/domain-model/src/main/java/com/iluwatar/domainmodel/App.java index aade9924a400..3f209a35a5f8 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/App.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/App.java @@ -32,7 +32,6 @@ import org.h2.jdbcx.JdbcDataSource; import org.joda.money.Money; - /** * Domain Model pattern is a more complex solution for organizing domain logic than Transaction * Script and Table Module. It provides an object-oriented way of dealing with complicated logic. @@ -42,10 +41,10 @@ * is that in Table Module a single class encapsulates all the domain logic for all records stored * in table when in Domain Model every single class represents only one record in underlying table. * - *

In this example, we will use the Domain Model pattern to implement buying of products - * by customers in a Shop. The main method will create a customer and a few products. - * Customer will do a few purchases, try to buy product which are too expensive for him, - * return product which he bought to return money.

+ *

In this example, we will use the Domain Model pattern to implement buying of products by + * customers in a Shop. The main method will create a customer and a few products. Customer will do + * a few purchases, try to buy product which are too expensive for him, return product which he + * bought to return money. */ public class App { @@ -80,11 +79,7 @@ public static void main(String[] args) throws Exception { var customerDao = new CustomerDaoImpl(dataSource); var tom = - Customer.builder() - .name("Tom") - .money(Money.of(USD, 30)) - .customerDao(customerDao) - .build(); + Customer.builder().name("Tom").money(Money.of(USD, 30)).customerDao(customerDao).build(); tom.save(); diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java b/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java index e0f646a77442..019a06c8470b 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java @@ -36,9 +36,8 @@ import org.joda.money.Money; /** - * This class organizes domain logic of customer. - * A single instance of this class - * contains both the data and behavior of a single customer. + * This class organizes domain logic of customer. A single instance of this class contains both the + * data and behavior of a single customer. */ @Slf4j @Getter @@ -51,9 +50,7 @@ public class Customer { @NonNull private String name; @NonNull private Money money; - /** - * Save customer or update if customer already exist. - */ + /** Save customer or update if customer already exist. */ public void save() { try { Optional customer = customerDao.findByName(name); @@ -117,9 +114,7 @@ public void returnProduct(Product product) { } } - /** - * Print customer's purchases. - */ + /** Print customer's purchases. */ public void showPurchases() { Optional purchasesToShow = purchases.stream() @@ -133,9 +128,7 @@ public void showPurchases() { } } - /** - * Print customer's money balance. - */ + /** Print customer's money balance. */ public void showBalance() { LOGGER.info(name + " balance: " + money); } diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java index 1375a032373d..18db0ab0bd8d 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java @@ -27,9 +27,7 @@ import java.sql.SQLException; import java.util.Optional; -/** - * DAO interface for customer transactions. - */ +/** DAO interface for customer transactions. */ public interface CustomerDao { Optional findByName(String name) throws SQLException; diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java index b24da7a49dee..cff4e30bc079 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java @@ -32,9 +32,7 @@ import javax.sql.DataSource; import org.joda.money.Money; -/** - * Implementations for database operations of Customer. - */ +/** Implementations for database operations of Customer. */ public class CustomerDaoImpl implements CustomerDao { private final DataSource dataSource; diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java b/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java index 6d2f37a026eb..9d91779773ae 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java @@ -40,9 +40,8 @@ import org.joda.money.Money; /** - * This class organizes domain logic of product. - * A single instance of this class - * contains both the data and behavior of a single product. + * This class organizes domain logic of product. A single instance of this class contains both the + * data and behavior of a single product. */ @Slf4j @Getter @@ -59,9 +58,7 @@ public class Product { @NonNull private Money price; @NonNull private LocalDate expirationDate; - /** - * Save product or update if product already exist. - */ + /** Save product or update if product already exist. */ public void save() { try { Optional product = productDao.findByName(name); @@ -75,16 +72,14 @@ public void save() { } } - /** - * Calculate sale price of product with discount. - */ + /** Calculate sale price of product with discount. */ public Money getSalePrice() { return price.minus(calculateDiscount()); } private Money calculateDiscount() { if (ChronoUnit.DAYS.between(LocalDate.now(), expirationDate) - < DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) { + < DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) { return price.multipliedBy(DISCOUNT_RATE, RoundingMode.DOWN); } diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java index bfedc5098aad..5a2644829565 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java @@ -27,9 +27,7 @@ import java.sql.SQLException; import java.util.Optional; -/** - * DAO interface for product transactions. - */ +/** DAO interface for product transactions. */ public interface ProductDao { Optional findByName(String name) throws SQLException; diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java index 781bc3a80efb..6313e8afa1fb 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java @@ -33,9 +33,7 @@ import javax.sql.DataSource; import org.joda.money.Money; -/** - * Implementations for database transactions of Product. - */ +/** Implementations for database transactions of Product. */ public class ProductDaoImpl implements ProductDao { private final DataSource dataSource; diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java index 06a260e5fae6..f15b33c2d1b3 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java @@ -24,10 +24,10 @@ */ package com.iluwatar.domainmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + /** Tests that Domain Model example runs without errors. */ final class AppTest { @@ -35,5 +35,4 @@ final class AppTest { void shouldExecuteApplicationWithoutException() { assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java index 9251420ba76a..aeab249ba7c7 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java @@ -24,18 +24,18 @@ */ package com.iluwatar.domainmodel; +import static org.joda.money.CurrencyUnit.USD; +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import javax.sql.DataSource; import org.joda.money.CurrencyUnit; import org.joda.money.Money; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.sql.DataSource; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; - -import static org.joda.money.CurrencyUnit.USD; -import static org.junit.jupiter.api.Assertions.*; class CustomerDaoImplTest { @@ -62,7 +62,12 @@ void setUp() throws SQLException { // setup objects customerDao = new CustomerDaoImpl(dataSource); - customer = Customer.builder().name("customer").money(Money.of(CurrencyUnit.USD,100.0)).customerDao(customerDao).build(); + customer = + Customer.builder() + .name("customer") + .money(Money.of(CurrencyUnit.USD, 100.0)) + .customerDao(customerDao) + .build(); product = Product.builder() diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java index cb55f33038a7..9d5f83a92156 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java @@ -24,89 +24,90 @@ */ package com.iluwatar.domainmodel; -import org.joda.money.CurrencyUnit; -import org.joda.money.Money; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.joda.money.CurrencyUnit.USD; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + import java.sql.SQLException; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; - -import static org.joda.money.CurrencyUnit.USD; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerTest { - private CustomerDao customerDao; - private Customer customer; - private Product product; + private CustomerDao customerDao; + private Customer customer; + private Product product; - @BeforeEach - void setUp() { - customerDao = mock(CustomerDao.class); + @BeforeEach + void setUp() { + customerDao = mock(CustomerDao.class); - customer = Customer.builder() - .name("customer") - .money(Money.of(CurrencyUnit.USD, 100.0)) - .customerDao(customerDao) - .build(); + customer = + Customer.builder() + .name("customer") + .money(Money.of(CurrencyUnit.USD, 100.0)) + .customerDao(customerDao) + .build(); - product = Product.builder() - .name("product") - .price(Money.of(USD, 100.0)) - .expirationDate(LocalDate.now().plusDays(10)) - .productDao(mock(ProductDao.class)) - .build(); - } + product = + Product.builder() + .name("product") + .price(Money.of(USD, 100.0)) + .expirationDate(LocalDate.now().plusDays(10)) + .productDao(mock(ProductDao.class)) + .build(); + } - @Test - void shouldSaveCustomer() throws SQLException { - when(customerDao.findByName("customer")).thenReturn(Optional.empty()); + @Test + void shouldSaveCustomer() throws SQLException { + when(customerDao.findByName("customer")).thenReturn(Optional.empty()); - customer.save(); + customer.save(); - verify(customerDao, times(1)).save(customer); + verify(customerDao, times(1)).save(customer); - when(customerDao.findByName("customer")).thenReturn(Optional.of(customer)); + when(customerDao.findByName("customer")).thenReturn(Optional.of(customer)); - customer.save(); + customer.save(); - verify(customerDao, times(1)).update(customer); - } + verify(customerDao, times(1)).update(customer); + } - @Test - void shouldAddProductToPurchases() { - product.setPrice(Money.of(USD, 200.0)); + @Test + void shouldAddProductToPurchases() { + product.setPrice(Money.of(USD, 200.0)); - customer.buyProduct(product); + customer.buyProduct(product); - assertEquals(customer.getPurchases(), new ArrayList<>()); - assertEquals(customer.getMoney(), Money.of(USD,100)); + assertEquals(customer.getPurchases(), new ArrayList<>()); + assertEquals(customer.getMoney(), Money.of(USD, 100)); - product.setPrice(Money.of(USD, 100.0)); + product.setPrice(Money.of(USD, 100.0)); - customer.buyProduct(product); + customer.buyProduct(product); - assertEquals(new ArrayList<>(Arrays.asList(product)), customer.getPurchases()); - assertEquals(Money.zero(USD), customer.getMoney()); - } + assertEquals(new ArrayList<>(Arrays.asList(product)), customer.getPurchases()); + assertEquals(Money.zero(USD), customer.getMoney()); + } - @Test - void shouldRemoveProductFromPurchases() { - customer.setPurchases(new ArrayList<>(Arrays.asList(product))); + @Test + void shouldRemoveProductFromPurchases() { + customer.setPurchases(new ArrayList<>(Arrays.asList(product))); - customer.returnProduct(product); + customer.returnProduct(product); - assertEquals(new ArrayList<>(), customer.getPurchases()); - assertEquals(Money.of(USD, 200), customer.getMoney()); + assertEquals(new ArrayList<>(), customer.getPurchases()); + assertEquals(Money.of(USD, 200), customer.getMoney()); - customer.returnProduct(product); + customer.returnProduct(product); - assertEquals(new ArrayList<>(), customer.getPurchases()); - assertEquals(Money.of(USD, 200), customer.getMoney()); - } + assertEquals(new ArrayList<>(), customer.getPurchases()); + assertEquals(Money.of(USD, 200), customer.getMoney()); + } } - diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java index ae2985afa536..7631c3581ec8 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java @@ -24,104 +24,104 @@ */ package com.iluwatar.domainmodel; +import static org.joda.money.CurrencyUnit.USD; +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import javax.sql.DataSource; import org.joda.money.Money; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.sql.DataSource; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; - -import static org.joda.money.CurrencyUnit.USD; -import static org.junit.jupiter.api.Assertions.*; class ProductDaoImplTest { - public static final String INSERT_PRODUCT_SQL = - "insert into PRODUCTS values('product', 100, DATE '2021-06-27')"; - public static final String SELECT_PRODUCTS_SQL = - "select name, price, expiration_date from PRODUCTS"; - - private DataSource dataSource; - private ProductDao productDao; - private Product product; - - @BeforeEach - void setUp() throws SQLException { - // create schema - dataSource = TestUtils.createDataSource(); - - TestUtils.deleteSchema(dataSource); - TestUtils.createSchema(dataSource); - - // setup objects - productDao = new ProductDaoImpl(dataSource); - - product = - Product.builder() - .name("product") - .price(Money.of(USD, 100.0)) - .expirationDate(LocalDate.parse("2021-06-27")) - .productDao(productDao) - .build(); - } + public static final String INSERT_PRODUCT_SQL = + "insert into PRODUCTS values('product', 100, DATE '2021-06-27')"; + public static final String SELECT_PRODUCTS_SQL = + "select name, price, expiration_date from PRODUCTS"; - @AfterEach - void tearDown() throws SQLException { - TestUtils.deleteSchema(dataSource); - } + private DataSource dataSource; + private ProductDao productDao; + private Product product; - @Test - void shouldFindProductByName() throws SQLException { - var product = productDao.findByName("product"); + @BeforeEach + void setUp() throws SQLException { + // create schema + dataSource = TestUtils.createDataSource(); - assertTrue(product.isEmpty()); + TestUtils.deleteSchema(dataSource); + TestUtils.createSchema(dataSource); - TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); + // setup objects + productDao = new ProductDaoImpl(dataSource); - product = productDao.findByName("product"); + product = + Product.builder() + .name("product") + .price(Money.of(USD, 100.0)) + .expirationDate(LocalDate.parse("2021-06-27")) + .productDao(productDao) + .build(); + } - assertTrue(product.isPresent()); - assertEquals("product", product.get().getName()); - assertEquals(Money.of(USD, 100), product.get().getPrice()); - assertEquals(LocalDate.parse("2021-06-27"), product.get().getExpirationDate()); - } + @AfterEach + void tearDown() throws SQLException { + TestUtils.deleteSchema(dataSource); + } - @Test - void shouldSaveProduct() throws SQLException { + @Test + void shouldFindProductByName() throws SQLException { + var product = productDao.findByName("product"); - productDao.save(product); + assertTrue(product.isEmpty()); - try (var connection = dataSource.getConnection(); - var statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { + TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); - assertTrue(rs.next()); - assertEquals(product.getName(), rs.getString("name")); - assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); - assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); - } + product = productDao.findByName("product"); - assertThrows(SQLException.class, () -> productDao.save(product)); + assertTrue(product.isPresent()); + assertEquals("product", product.get().getName()); + assertEquals(Money.of(USD, 100), product.get().getPrice()); + assertEquals(LocalDate.parse("2021-06-27"), product.get().getExpirationDate()); + } + + @Test + void shouldSaveProduct() throws SQLException { + + productDao.save(product); + + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { + + assertTrue(rs.next()); + assertEquals(product.getName(), rs.getString("name")); + assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); + assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); } - @Test - void shouldUpdateProduct() throws SQLException { - TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); + assertThrows(SQLException.class, () -> productDao.save(product)); + } + + @Test + void shouldUpdateProduct() throws SQLException { + TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); - product.setPrice(Money.of(USD, 99.0)); + product.setPrice(Money.of(USD, 99.0)); - productDao.update(product); + productDao.update(product); - try (var connection = dataSource.getConnection(); - var statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { - assertTrue(rs.next()); - assertEquals(product.getName(), rs.getString("name")); - assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); - assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); - } + assertTrue(rs.next()); + assertEquals(product.getName(), rs.getString("name")); + assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); + assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); } + } } diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java index f3326fe3ba29..f3193ce9b6aa 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java @@ -24,55 +24,56 @@ */ package com.iluwatar.domainmodel; -import org.joda.money.Money; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.sql.SQLException; -import java.time.LocalDate; -import java.util.Optional; - import static org.joda.money.CurrencyUnit.USD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Optional; +import org.joda.money.Money; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ProductTest { - private ProductDao productDao; - private Product product; + private ProductDao productDao; + private Product product; - @BeforeEach - void setUp() { - productDao = mock(ProductDaoImpl.class); + @BeforeEach + void setUp() { + productDao = mock(ProductDaoImpl.class); - product = Product.builder() - .name("product") - .price(Money.of(USD, 100.0)) - .expirationDate(LocalDate.now().plusDays(10)) - .productDao(productDao) - .build(); - } + product = + Product.builder() + .name("product") + .price(Money.of(USD, 100.0)) + .expirationDate(LocalDate.now().plusDays(10)) + .productDao(productDao) + .build(); + } - @Test - void shouldSaveProduct() throws SQLException { - when(productDao.findByName("product")).thenReturn(Optional.empty()); + @Test + void shouldSaveProduct() throws SQLException { + when(productDao.findByName("product")).thenReturn(Optional.empty()); - product.save(); + product.save(); - verify(productDao, times(1)).save(product); + verify(productDao, times(1)).save(product); - when(productDao.findByName("product")).thenReturn(Optional.of(product)); + when(productDao.findByName("product")).thenReturn(Optional.of(product)); - product.save(); + product.save(); - verify(productDao, times(1)).update(product); - } + verify(productDao, times(1)).update(product); + } - @Test - void shouldGetSalePriceOfProduct() { - assertEquals(Money.of(USD, 100), product.getSalePrice()); + @Test + void shouldGetSalePriceOfProduct() { + assertEquals(Money.of(USD, 100), product.getSalePrice()); - product.setExpirationDate(LocalDate.now().plusDays(2)); + product.setExpirationDate(LocalDate.now().plusDays(2)); - assertEquals(Money.of(USD, 80), product.getSalePrice()); - } + assertEquals(Money.of(USD, 80), product.getSalePrice()); + } } diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java b/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java index 5c788fa8ec49..8555cedc8b1d 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java @@ -24,13 +24,13 @@ */ package com.iluwatar.domainmodel; -import org.h2.jdbcx.JdbcDataSource; -import javax.sql.DataSource; import java.sql.SQLException; +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; public class TestUtils { - public static void executeSQL( String sql, DataSource dataSource) throws SQLException { + public static void executeSQL(String sql, DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); var statement = connection.createStatement()) { statement.executeUpdate(sql); diff --git a/double-buffer/README.md b/double-buffer/README.md index 04b47ad97f7e..befa83a9a596 100644 --- a/double-buffer/README.md +++ b/double-buffer/README.md @@ -1,255 +1,252 @@ --- -title: Double Buffer +title: "Double Buffer Pattern in Java: Enhancing Animation and Graphics Performance" +shortTitle: Double Buffer +description: "Learn how the Double Buffer Pattern in Java optimizes performance and ensures smooth graphics rendering for applications. Explore practical examples and real-world use cases." category: Behavioral language: en -tag: - - Performance - - Game programming +tag: + - Buffering + - Game programming + - Optimization + - Performance --- - -## Intent -Double buffering is a term used to describe a device that has two buffers. The usage of multiple -buffers increases the overall throughput of a device and helps prevents bottlenecks. This example -shows using double buffer pattern on graphics. It is used to show one image or frame while a separate -frame is being buffered to be shown next. This method makes animations and games look more realistic -than the same done in a single buffer mode. - -## Explanation - -Real world example -> A typical example, and one that every game engine must address, is rendering. When the game draws -> the world the users see, it does so one piece at a time -- the mountains in the distance, -> the rolling hills, the trees, each in its turn. If the user watched the view draw incrementally -> like that, the illusion of a coherent world would be shattered. The scene must update smoothly -> and quickly, displaying a series of complete frames, each appearing instantly. Double buffering solves -> the problem. + +## Also known as + +* Buffer Switching +* Ping-Pong Buffer + +## Intent of Double Buffer Design Pattern + +The Double Buffer pattern in Java is designed to reduce rendering time and enhance performance in graphical or computational applications by utilizing two buffers. This pattern is crucial for smooth graphics rendering and is commonly used in game development and other real-time applications. + +## Detailed Explanation of Double Buffer Pattern with Real-World Examples + +Real-world example + +> Imagine a busy restaurant kitchen where chefs are constantly preparing dishes, and waitstaff are constantly picking up ready dishes to serve to customers. To avoid confusion and delays, the restaurant uses a double buffer system. They have two counters: one for chefs to place newly prepared dishes and another for waitstaff to pick up the dishes. While the chefs are filling one counter with prepared dishes, the waitstaff are simultaneously clearing the other counter by picking up dishes to serve. Once the waitstaff have cleared all dishes from their counter, they switch to the counter where the chefs have placed the newly prepared dishes, and the chefs start filling the now-empty counter. This system ensures a smooth and continuous workflow without either party waiting idly, maximizing efficiency and minimizing downtime. In plain words -> It ensures a state that is being rendered correctly while that state is modifying incrementally. It is -> widely used in computer graphics. + +> It ensures a state that is being rendered correctly while that state is modifying incrementally. It is widely used in computer graphics. Wikipedia says -> In computer science, multiple buffering is the use of more than one buffer to hold a block of data, -> so that a "reader" will see a complete (though perhaps old) version of the data, rather than a -> partially updated version of the data being created by a "writer". It is very commonly used for -> computer display images. -**Programmatic Example** +> In computer science, multiple buffering is the use of more than one buffer to hold a block of data, so that a "reader" will see a complete (though perhaps old) version of the data, rather than a partially updated version of the data being created by a "writer". It is very commonly used for computer display images. + +## Programmatic Example of Double Buffer Pattern in Java -Buffer interface that assures basic functionalities of a buffer. +A typical example, and one that every game engine must address, is rendering. When the game draws the world the users see, it does so one piece at a time - the mountains in the distance, the rolling hills, the trees, each in its turn. If the user watched the view draw incrementally like that, the illusion of a coherent world would be shattered. The scene must update smoothly and quickly, displaying a series of complete frames, each appearing instantly. Double buffering solves the problem. + +`Buffer` interface that assures basic functionalities of a buffer. ```java -/** - * Buffer interface. - */ public interface Buffer { - /** - * Clear the pixel in (x, y). - * - * @param x X coordinate - * @param y Y coordinate - */ - void clear(int x, int y); - - /** - * Draw the pixel in (x, y). - * - * @param x X coordinate - * @param y Y coordinate - */ - void draw(int x, int y); - - /** - * Clear all the pixels. - */ - void clearAll(); - - /** - * Get all the pixels. - * - * @return pixel list - */ - Pixel[] getPixels(); + void clear(int x, int y); + + void draw(int x, int y); + + void clearAll(); + Pixel[] getPixels(); } ``` -One of the implementation of Buffer interface. +One of the implementations of `Buffer` interface. + ```java -/** - * FrameBuffer implementation class. - */ public class FrameBuffer implements Buffer { - public static final int WIDTH = 10; - public static final int HEIGHT = 8; + public static final int WIDTH = 10; + public static final int HEIGHT = 8; - private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; + private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; - public FrameBuffer() { - clearAll(); - } + public FrameBuffer() { + clearAll(); + } - @Override - public void clear(int x, int y) { - pixels[getIndex(x, y)] = Pixel.WHITE; - } + @Override + public void clear(int x, int y) { + pixels[getIndex(x, y)] = Pixel.WHITE; + } - @Override - public void draw(int x, int y) { - pixels[getIndex(x, y)] = Pixel.BLACK; - } + @Override + public void draw(int x, int y) { + pixels[getIndex(x, y)] = Pixel.BLACK; + } - @Override - public void clearAll() { - Arrays.fill(pixels, Pixel.WHITE); - } + @Override + public void clearAll() { + Arrays.fill(pixels, Pixel.WHITE); + } - @Override - public Pixel[] getPixels() { - return pixels; - } + @Override + public Pixel[] getPixels() { + return pixels; + } - private int getIndex(int x, int y) { - return x + WIDTH * y; - } + private int getIndex(int x, int y) { + return x + WIDTH * y; + } } ``` +We support black and white pixels. + ```java -/** - * Pixel enum. Each pixel can be white (not drawn) or black (drawn). - */ public enum Pixel { - WHITE, - BLACK; + WHITE, + BLACK } ``` -Scene represents the game scene where current buffer has already been rendered. + +`Scene` represents the game scene where current buffer has already been rendered. + ```java -/** - * Scene class. Render the output frame. - */ @Slf4j public class Scene { - private final Buffer[] frameBuffers; - - private int current; - - private int next; - - /** - * Constructor of Scene. - */ - public Scene() { - frameBuffers = new FrameBuffer[2]; - frameBuffers[0] = new FrameBuffer(); - frameBuffers[1] = new FrameBuffer(); - current = 0; - next = 1; - } - - /** - * Draw the next frame. - * - * @param coordinateList list of pixels of which the color should be black - */ - public void draw(List> coordinateList) { - LOGGER.info("Start drawing next frame"); - LOGGER.info("Current buffer: " + current + " Next buffer: " + next); - frameBuffers[next].clearAll(); - coordinateList.forEach(coordinate -> { - var x = coordinate.getKey(); - var y = coordinate.getValue(); - frameBuffers[next].draw(x, y); - }); - LOGGER.info("Swap current and next buffer"); - swap(); - LOGGER.info("Finish swapping"); - LOGGER.info("Current buffer: " + current + " Next buffer: " + next); - } - - public Buffer getBuffer() { - LOGGER.info("Get current buffer: " + current); - return frameBuffers[current]; - } - - private void swap() { - current = current ^ next; - next = current ^ next; - current = current ^ next; - } + private final Buffer[] frameBuffers; + + private int current; + + private int next; + + public Scene() { + frameBuffers = new FrameBuffer[2]; + frameBuffers[0] = new FrameBuffer(); + frameBuffers[1] = new FrameBuffer(); + current = 0; + next = 1; + } + + public void draw(List> coordinateList) { + LOGGER.info("Start drawing next frame"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + frameBuffers[next].clearAll(); + coordinateList.forEach(coordinate -> { + var x = coordinate.getKey(); + var y = coordinate.getValue(); + frameBuffers[next].draw(x, y); + }); + LOGGER.info("Swap current and next buffer"); + swap(); + LOGGER.info("Finish swapping"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + } + + public Buffer getBuffer() { + LOGGER.info("Get current buffer: " + current); + return frameBuffers[current]; + } + + private void swap() { + current = current ^ next; + next = current ^ next; + current = current ^ next; + } } ``` +Now, we can show the `App` class that drives the double buffering example. + ```java -public static void main(String[] args) { - final var scene = new Scene(); - var drawPixels1 = List.of( - new MutablePair<>(1, 1), - new MutablePair<>(5, 6), - new MutablePair<>(3, 2) - ); - scene.draw(drawPixels1); - var buffer1 = scene.getBuffer(); - printBlackPixelCoordinate(buffer1); - - var drawPixels2 = List.of( - new MutablePair<>(3, 7), - new MutablePair<>(6, 1) - ); - scene.draw(drawPixels2); - var buffer2 = scene.getBuffer(); - printBlackPixelCoordinate(buffer2); - } - - private static void printBlackPixelCoordinate(Buffer buffer) { - StringBuilder log = new StringBuilder("Black Pixels: "); - var pixels = buffer.getPixels(); - for (var i = 0; i < pixels.length; ++i) { - if (pixels[i] == Pixel.BLACK) { - var y = i / FrameBuffer.WIDTH; - var x = i % FrameBuffer.WIDTH; - log.append(" (").append(x).append(", ").append(y).append(")"); - } +@Slf4j +public class App { + + public static void main(String[] args) { + final var scene = new Scene(); + var drawPixels1 = List.of( + new MutablePair<>(1, 1), + new MutablePair<>(5, 6), + new MutablePair<>(3, 2) + ); + scene.draw(drawPixels1); + var buffer1 = scene.getBuffer(); + printBlackPixelCoordinate(buffer1); + + var drawPixels2 = List.of( + new MutablePair<>(3, 7), + new MutablePair<>(6, 1) + ); + scene.draw(drawPixels2); + var buffer2 = scene.getBuffer(); + printBlackPixelCoordinate(buffer2); + } + + private static void printBlackPixelCoordinate(Buffer buffer) { + StringBuilder log = new StringBuilder("Black Pixels: "); + var pixels = buffer.getPixels(); + for (var i = 0; i < pixels.length; ++i) { + if (pixels[i] == Pixel.BLACK) { + var y = i / FrameBuffer.WIDTH; + var x = i % FrameBuffer.WIDTH; + log.append(" (").append(x).append(", ").append(y).append(")"); + } + } + LOGGER.info(log.toString()); } - LOGGER.info(log.toString()); - } +} ``` -The console output +The console output: + ```text -[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame -[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 -[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer -[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping -[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 -[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 1 -[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (1, 1) (3, 2) (5, 6) -[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame -[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 -[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer -[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping -[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 -[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 0 -[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (6, 1) (3, 7) +12:33:02.525 [main] INFO com.iluwatar.doublebuffer.Scene -- Start drawing next frame +12:33:02.529 [main] INFO com.iluwatar.doublebuffer.Scene -- Current buffer: 0 Next buffer: 1 +12:33:02.529 [main] INFO com.iluwatar.doublebuffer.Scene -- Swap current and next buffer +12:33:02.529 [main] INFO com.iluwatar.doublebuffer.Scene -- Finish swapping +12:33:02.529 [main] INFO com.iluwatar.doublebuffer.Scene -- Current buffer: 1 Next buffer: 0 +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.Scene -- Get current buffer: 1 +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.App -- Black Pixels: (1, 1) (3, 2) (5, 6) +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.Scene -- Start drawing next frame +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.Scene -- Current buffer: 1 Next buffer: 0 +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.Scene -- Swap current and next buffer +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.Scene -- Finish swapping +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.Scene -- Current buffer: 0 Next buffer: 1 +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.Scene -- Get current buffer: 0 +12:33:02.530 [main] INFO com.iluwatar.doublebuffer.App -- Black Pixels: (6, 1) (3, 7) ``` -## Class diagram -![alt text](./etc/double-buffer.urm.png "Double Buffer pattern class diagram") +## When to Use the Double Buffer Pattern in Java + +* Real-time Applications: Ideal for video games, simulations, and GUI applications where frequent and smooth display updates are essential. +* High Computational Tasks: Suitable for applications that require intensive data preparation, enabling parallel processing and display. +* Minimizing Lag: Effective in reducing lag or stutter in data or graphics display. + +## Real-World Applications of Double Buffer Pattern in Java + +* Graphics Rendering Engines: Widely used in 2D and 3D rendering engines to ensure fluid animations and transitions. +* GUI Frameworks: Enhances the responsiveness and smoothness of user interfaces. +* Simulation and Modeling: Ensures real-time updates in simulations without interrupting ongoing processes. +* Video Playback Software: Provides seamless video playback by preloading the next frame during the display of the current one. + +## Benefits and Trade-offs of Double Buffer Pattern + +Benefits: + +* Smooth User Experience: Pre-renders frames to deliver smooth animations and transitions. +* Performance Optimization: Allows background rendering, optimizing overall application performance. +* Minimized Flickering: Reduces flickering and visual artifacts in graphical applications. + +Trade-offs: + +* Memory Overhead: Requires additional memory for the secondary buffer, potentially increasing memory usage. +* Implementation Complexity: Adds complexity to the architecture, necessitating careful buffer management. +* Latency: May introduce slight delays as data must be fully rendered in the back buffer before display. + +## Related Java Design Patterns -## Applicability -This pattern is one of those ones where you’ll know when you need it. If you have a system that lacks double buffering, it will probably look visibly wrong (tearing, etc.) or will behave incorrectly. But saying, “you’ll know when you need it” doesn’t give you much to go on. More specifically, this pattern is appropriate when all of these are true: +* Triple Buffering: An extension of the Double Buffer pattern, where three buffers are used to further optimize rendering and reduce latency. +* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): The Double Buffer pattern can be seen as a variant of the Producer-Consumer pattern, with one buffer being "produced" while the other is "consumed". +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Often used in conjunction with the Strategy pattern to dynamically choose the buffering strategy based on runtime conditions. -- We have some state that is being modified incrementally. -- That same state may be accessed in the middle of modification. -- We want to prevent the code that’s accessing the state from seeing the work in progress. -- We want to be able to read the state and we don’t want to have to wait while it’s being written. +## References and Credits -## Credits - -* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html) +* [Game Programming Patterns](https://amzn.to/4ayDNkS) +* [Real-Time Design Patterns: Robust Scalable Architecture for Real-Time Systems](https://amzn.to/3xFfNxA) +* [Double Buffer (Game Programming Patterns)](https://gameprogrammingpatterns.com/double-buffer.html) diff --git a/double-buffer/pom.xml b/double-buffer/pom.xml index 5718eddf1eeb..ad5bf6c3e314 100644 --- a/double-buffer/pom.xml +++ b/double-buffer/pom.xml @@ -34,9 +34,18 @@ 4.0.0 double-buffer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.apache.commons commons-lang3 + 3.17.0 org.junit.jupiter diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java index bbd4ad8d4514..899f714667d3 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java @@ -45,19 +45,13 @@ public class App { */ public static void main(String[] args) { final var scene = new Scene(); - var drawPixels1 = List.of( - new MutablePair<>(1, 1), - new MutablePair<>(5, 6), - new MutablePair<>(3, 2) - ); + var drawPixels1 = + List.of(new MutablePair<>(1, 1), new MutablePair<>(5, 6), new MutablePair<>(3, 2)); scene.draw(drawPixels1); var buffer1 = scene.getBuffer(); printBlackPixelCoordinate(buffer1); - var drawPixels2 = List.of( - new MutablePair<>(3, 7), - new MutablePair<>(6, 1) - ); + var drawPixels2 = List.of(new MutablePair<>(3, 7), new MutablePair<>(6, 1)); scene.draw(drawPixels2); var buffer2 = scene.getBuffer(); printBlackPixelCoordinate(buffer2); diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java index dfa2dbe48e90..191ad0e12225 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doublebuffer; -/** - * Buffer interface. - */ +/** Buffer interface. */ public interface Buffer { /** @@ -45,9 +43,7 @@ public interface Buffer { */ void draw(int x, int y); - /** - * Clear all the pixels. - */ + /** Clear all the pixels. */ void clearAll(); /** @@ -56,5 +52,4 @@ public interface Buffer { * @return pixel list */ Pixel[] getPixels(); - } diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java index ef86549b48e8..e015bb62e8b8 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java @@ -26,9 +26,7 @@ import java.util.Arrays; -/** - * FrameBuffer implementation class. - */ +/** FrameBuffer implementation class. */ public class FrameBuffer implements Buffer { public static final int WIDTH = 10; diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java index 91ab966fc718..eea701e8afc6 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java @@ -24,11 +24,8 @@ */ package com.iluwatar.doublebuffer; -/** - * Pixel enum. Each pixel can be white (not drawn) or black (drawn). - */ +/** Pixel enum. Each pixel can be white (not drawn) or black (drawn). */ public enum Pixel { - WHITE, - BLACK; + BLACK } diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java index 692249dd0b99..79a528fabe6b 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java @@ -28,9 +28,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; -/** - * Scene class. Render the output frame. - */ +/** Scene class. Render the output frame. */ @Slf4j public class Scene { @@ -40,9 +38,7 @@ public class Scene { private int next; - /** - * Constructor of Scene. - */ + /** Constructor of Scene. */ public Scene() { frameBuffers = new FrameBuffer[2]; frameBuffers[0] = new FrameBuffer(); @@ -60,11 +56,12 @@ public void draw(List> coordinateList) { LOGGER.info("Start drawing next frame"); LOGGER.info("Current buffer: " + current + " Next buffer: " + next); frameBuffers[next].clearAll(); - coordinateList.forEach(coordinate -> { - var x = coordinate.getKey(); - var y = coordinate.getValue(); - frameBuffers[next].draw(x, y); - }); + coordinateList.forEach( + coordinate -> { + var x = coordinate.getKey(); + var y = coordinate.getValue(); + frameBuffers[next].draw(x, y); + }); LOGGER.info("Swap current and next buffer"); swap(); LOGGER.info("Finish swapping"); @@ -81,5 +78,4 @@ private void swap() { next = current ^ next; current = current ^ next; } - } diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java index 2338fde5928e..4c1d8674d66f 100644 --- a/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java @@ -28,21 +28,17 @@ import org.junit.jupiter.api.Test; -/** - * App unit test. - */ +/** App unit test. */ class AppTest { /** * Issue: Add at least one assertion to this test case. - *

- * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java index ea842c3c5157..2bd852a35898 100644 --- a/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java @@ -30,9 +30,7 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * FrameBuffer unit test. - */ +/** FrameBuffer unit test. */ class FrameBufferTest { @Test @@ -91,5 +89,4 @@ void testGetPixels() { fail("Fail to modify field access."); } } - } diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java index 6176bf792579..41806e566ba4 100644 --- a/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java @@ -30,9 +30,7 @@ import java.util.ArrayList; import org.junit.jupiter.api.Test; -/** - * Scene unit tests. - */ +/** Scene unit tests. */ class SceneTest { @Test diff --git a/double-checked-locking/README.md b/double-checked-locking/README.md index a09ae6100876..df2ddfe980e3 100644 --- a/double-checked-locking/README.md +++ b/double-checked-locking/README.md @@ -1,22 +1,124 @@ --- -title: Double Checked Locking -category: Idiom +title: "Double-Checked Locking Pattern in Java: Ensuring Thread Safety with Minimal Overhead" +shortTitle: Double-Checked Locking +description: "Master double-checked locking in Java with our detailed guide and practical examples. Enhance your Java design patterns knowledge today." +category: Concurrency language: en tag: - - Performance + - Lazy initialization + - Optimization + - Performance + - Thread management --- -## Intent -Reduce the overhead of acquiring a lock by first testing the -locking criterion (the "lock hint") without actually acquiring the lock. Only -if the locking criterion check indicates that locking is required does the -actual locking logic proceed. +## Intent of Double-Checked Locking Design Pattern -## Class diagram -![alt text](./etc/double_checked_locking_1.png "Double Checked Locking") +Reduce the overhead of acquiring a lock by first testing the locking criterion (the "lock hint") without actually acquiring the lock. Only if the locking criterion appears to be true does the actual locking logic proceed. Double-checked locking in Java helps in optimizing performance and ensuring thread safety. -## Applicability -Use the Double Checked Locking pattern when +## Detailed Explanation of Double-Checked Locking Pattern with Real-World Examples -* there is a concurrent access in object creation, e.g. singleton, where you want to create single instance of the same class and checking if it's null or not maybe not be enough when there are two or more threads that checks if instance is null or not. -* there is a concurrent access on a method where method's behaviour changes according to the some constraints and these constraint change within this method. +Real-world example + +> In a company with a high-value equipment room, employees first check a visible sign to see if the room is locked. If the sign shows it's unlocked, they enter directly; if locked, they use a security keycard for access. This two-step verification process efficiently manages security without unnecessary use of the electronic lock system, mirroring the Double-Checked Locking pattern used in software to minimize resource-intensive operations. + +In plain words + +> The Double-Checked Locking pattern in software minimizes costly locking operations by first checking the lock status in a low-cost manner before proceeding with a more resource-intensive lock, ensuring efficiency and thread safety during object initialization. + +Wikipedia says + +> In software engineering, double-checked locking (also known as "double-checked locking optimization") is a software design pattern used to reduce the overhead of acquiring a lock by testing the locking criterion (the "lock hint") before acquiring the lock. Locking occurs only if the locking criterion check indicates that locking is required. + +## Programmatic Example of Double-Checked Locking Pattern in Java + +The Double-Checked Locking pattern is used in the `HolderThreadSafe` class to ensure that the `Heavy` object is only created once, even when accessed from multiple threads. Here's how it works: + +1. Check if the object is initialized (first check): If it is, return it immediately. + +```java +if (heavy == null) { + // ... +} +``` + +2. Synchronize the block of code where the object is created: This ensures that only one thread can create the object. + +```java +synchronized (this) { + // ... +} +``` + +3. Check again if the object is initialized. If another thread has already created the object by the time the current thread enters the synchronized block, return the created object. + +```java +if (heavy == null) { + heavy = new Heavy(); +} +``` + +4. Return the created object. + +```java +return heavy; +``` + +Here's the complete code for the `HolderThreadSafe` class: + +```java +public class HolderThreadSafe { + + private Heavy heavy; + + public HolderThreadSafe() { + LOGGER.info("Holder created"); + } + + public synchronized Heavy getHeavy() { + if (heavy == null) { + synchronized (this) { + if (heavy == null) { + heavy = new Heavy(); + } + } + } + return heavy; + } +} +``` + +In this code, the `Heavy` object is only created when the `getHeavy` method is called for the first time. This is known as lazy initialization. The double-checked locking pattern is used to ensure that the `Heavy` object is only created once, even when the `getHeavy` method is called from multiple threads simultaneously. + +## When to Use the Double-Checked Locking Pattern in Java + +Use the Double-Checked Locking pattern in Java when all the following conditions are met: + +* There is a singleton resource that is expensive to create. +* There is a need to reduce the overhead of acquiring a lock every time the resource is accessed. + +## Real-World Applications of Double-Checked Locking Pattern in Java + +* Singleton pattern implementation in multithreading environments. +* Lazy initialization of resource-intensive objects in Java applications. + +## Benefits and Trade-offs of Double-Checked Locking Pattern + +Benefits: + +* Performance gains from avoiding unnecessary locking after the object is initialized. +* Thread safety is maintained for critical initialization sections. + +Trade-offs: + +* Complex implementation can lead to mistakes, such as incorrect publishing of objects due to memory visibility issues. +* In Java, it can be redundant or broken in some versions unless volatile variables are used with care. + +## Related Java Design Patterns + +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Double-Checked Locking is often used in implementing thread-safe Singletons. +* [Lazy Loading](https://java-design-patterns.com/patterns/lazy-loading/): Shares the concept of delaying object creation until necessary. + +## References and Credits + +* [Java Concurrency in Practice](https://amzn.to/4aIAPKa) +* [Effective Java](https://amzn.to/3xx7KDh) diff --git a/double-checked-locking/pom.xml b/double-checked-locking/pom.xml index a339de7c0c4a..650c76700498 100644 --- a/double-checked-locking/pom.xml +++ b/double-checked-locking/pom.xml @@ -33,6 +33,14 @@ double-checked-locking + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java index 8a5198229051..99dd6d05c628 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java @@ -35,10 +35,9 @@ * lock. Only if the locking criterion check indicates that locking is required does the actual * locking logic proceed. * - *

In {@link Inventory} we store the items with a given size. However, we do not store more - * items than the inventory size. To address concurrent access problems we use double checked - * locking to add item to inventory. In this method, the thread which gets the lock first adds the - * item. + *

In {@link Inventory} we store the items with a given size. However, we do not store more items + * than the inventory size. To address concurrent access problems we use double checked locking to + * add item to inventory. In this method, the thread which gets the lock first adds the item. */ @Slf4j public class App { @@ -51,11 +50,15 @@ public class App { public static void main(String[] args) { final var inventory = new Inventory(1000); var executorService = Executors.newFixedThreadPool(3); - IntStream.range(0, 3).mapToObj(i -> () -> { - while (inventory.addItem(new Item())) { - LOGGER.info("Adding another item"); - } - }).forEach(executorService::execute); + IntStream.range(0, 3) + .mapToObj( + i -> + () -> { + while (inventory.addItem(new Item())) { + LOGGER.info("Adding another item"); + } + }) + .forEach(executorService::execute); executorService.shutdown(); try { diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java index 83f72fca29aa..84b76a5769da 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java @@ -30,9 +30,7 @@ import java.util.concurrent.locks.ReentrantLock; import lombok.extern.slf4j.Slf4j; -/** - * Inventory. - */ +/** Inventory. */ @Slf4j public class Inventory { @@ -40,18 +38,14 @@ public class Inventory { private final List items; private final Lock lock; - /** - * Constructor. - */ + /** Constructor. */ public Inventory(int inventorySize) { this.inventorySize = inventorySize; this.items = new ArrayList<>(inventorySize); this.lock = new ReentrantLock(); } - /** - * Add item. - */ + /** Add item. */ public boolean addItem(Item item) { if (items.size() < inventorySize) { lock.lock(); @@ -77,5 +71,4 @@ public boolean addItem(Item item) { public final List getItems() { return List.copyOf(items); } - } diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java index 1e25d77c425a..8f16ea69f3af 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java @@ -24,8 +24,5 @@ */ package com.iluwatar.doublechecked.locking; -/** - * Item. - */ -public class Item { -} +/** Item. */ +public class Item {} diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java index 4e29db89d438..9d9ae3496f7c 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java @@ -24,25 +24,19 @@ */ package com.iluwatar.doublechecked.locking; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java index 032f133e41b6..d79f6b660a00 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java @@ -43,11 +43,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/10/15 - 9:34 PM - * - * @author Jeroen Meulemeester - */ +/** InventoryTest */ class InventoryTest { private InMemoryAppender appender; @@ -68,9 +64,7 @@ void tearDown() { */ private static final int THREAD_COUNT = 8; - /** - * The maximum number of {@link Item}s allowed in the {@link Inventory} - */ + /** The maximum number of {@link Item}s allowed in the {@link Inventory} */ private static final int INVENTORY_SIZE = 1000; /** @@ -80,36 +74,44 @@ void tearDown() { * item limit. */ @Test - void testAddItem() throws Exception { - assertTimeout(ofMillis(10000), () -> { - // Create a new inventory with a limit of 1000 items and put some load on the add method - final var inventory = new Inventory(INVENTORY_SIZE); - final var executorService = Executors.newFixedThreadPool(THREAD_COUNT); - IntStream.range(0, THREAD_COUNT).mapToObj(i -> () -> { - while (inventory.addItem(new Item())) ; - }).forEach(executorService::execute); - - // Wait until all threads have finished - executorService.shutdown(); - executorService.awaitTermination(5, TimeUnit.SECONDS); - - // Check the number of items in the inventory. It should not have exceeded the allowed maximum - final var items = inventory.getItems(); - assertNotNull(items); - assertEquals(INVENTORY_SIZE, items.size()); - - assertEquals(INVENTORY_SIZE, appender.getLogSize()); - - // ... and check if the inventory size is increasing continuously - IntStream.range(0, items.size()) - .mapToObj(i -> appender.log.get(i).getFormattedMessage() - .contains("items.size()=" + (i + 1))) - .forEach(Assertions::assertTrue); - }); + void testAddItem() { + assertTimeout( + ofMillis(10000), + () -> { + // Create a new inventory with a limit of 1000 items and put some load on the add method + final var inventory = new Inventory(INVENTORY_SIZE); + final var executorService = Executors.newFixedThreadPool(THREAD_COUNT); + IntStream.range(0, THREAD_COUNT) + .mapToObj( + i -> + () -> { + while (inventory.addItem(new Item())) + ; + }) + .forEach(executorService::execute); + + // Wait until all threads have finished + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + + // Check the number of items in the inventory. It should not have exceeded the allowed + // maximum + final var items = inventory.getItems(); + assertNotNull(items); + assertEquals(INVENTORY_SIZE, items.size()); + + assertEquals(INVENTORY_SIZE, appender.getLogSize()); + + // ... and check if the inventory size is increasing continuously + IntStream.range(0, items.size()) + .mapToObj( + i -> + appender.log.get(i).getFormattedMessage().contains("items.size()=" + (i + 1))) + .forEach(Assertions::assertTrue); + }); } - - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { @@ -126,5 +128,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/double-dispatch/README.md b/double-dispatch/README.md index 644282da10e1..0022467191ee 100644 --- a/double-dispatch/README.md +++ b/double-dispatch/README.md @@ -1,23 +1,152 @@ --- -title: Double Dispatch -category: Idiom +title: "Double Dispatch Pattern in Java: Enhancing Polymorphic Behavior" +shortTitle: Double Dispatch +description: "Learn the Double Dispatch Pattern in Java with detailed implementation examples. Understand how to use this design pattern to enhance your Java applications. Read our comprehensive guide." +category: Behavioral language: en tag: - - Extensibility + - Decoupling + - Dynamic typing + - Polymorphism + - Runtime --- -## Intent -Double Dispatch pattern is a way to create maintainable dynamic -behavior based on receiver and parameter types. +## Also known as -## Class diagram -![alt text](./etc/double-dispatch.png "Double Dispatch") +* Multi-methods -## Applicability -Use the Double Dispatch pattern when +## Intent of Double Dispatch Design Pattern -* the dynamic behavior is not defined only based on receiving object's type but also on the receiving method's parameter type. +The Double Dispatch pattern is used to achieve dynamic polymorphism based on the types of two objects involved in a method call. It allows method behavior to be different based on the combination of the runtime types of both the object on which the method is called and the object being passed as a parameter. -## Real world examples +## Detailed Explanation of Double Dispatch Pattern with Real-World Examples -* [ObjectOutputStream](https://docs.oracle.com/javase/8/docs/api/java/io/ObjectOutputStream.html) +Real-world example + +> In a logistics company, different types of delivery vehicles like trucks, drones, and bikes interact with various types of packages (fragile, oversized, standard). The Double Dispatch design pattern is used to determine the optimal delivery method: trucks might handle oversized items, drones for quick deliveries of light packages, and bikes for urban areas. Each vehicle-package combination results in a different handling and delivery strategy, dynamically determined at runtime based on the types of both the vehicle and the package. + +In plain words + +> The Double Dispatch design pattern in Java allows a program to select a different function to execute based on the types of two objects involved in a call, enhancing flexibility in handling interactions between them. + +Wikipedia says + +> In software engineering, double dispatch is a special form of multiple dispatch, and a mechanism that dispatches a function call to different concrete functions depending on the runtime types of two objects involved in the call. In most object-oriented systems, the concrete function that is called from a function call in the code depends on the dynamic type of a single object and therefore they are known as single dispatch calls, or simply virtual function calls. + +## Programmatic Example of Double Dispatch Pattern in Java + +The Double Dispatch pattern in Java is used to handle collisions between different types of game objects. Each game object is an instance of a class that extends the `GameObject` abstract class. The `GameObject` class has a `collision(GameObject)` method, which is overridden in each subclass to define the behavior when a collision occurs with another game object. Here is a simplified version of the `GameObject` class and its subclasses: + +```java +public abstract class GameObject { + // Other properties and methods... + + public abstract void collision(GameObject gameObject); +} + +public class FlamingAsteroid extends GameObject { + // Other properties and methods... + + @Override + public void collision(GameObject gameObject) { + gameObject.collisionWithFlamingAsteroid(this); + } +} + +public class SpaceStationMir extends GameObject { + // Other properties and methods... + + @Override + public void collision(GameObject gameObject) { + gameObject.collisionWithSpaceStationMir(this); + } +} +``` + +In the App class, the Double Dispatch pattern is used to check for collisions between all pairs of game objects: + +```java +public static void main(String[] args) { + // initialize game objects and print their status + LOGGER.info("Init objects and print their status"); + var objects = List.of( + new FlamingAsteroid(0, 0, 5, 5), + new SpaceStationMir(1, 1, 2, 2), + new Meteoroid(10, 10, 15, 15), + new SpaceStationIss(12, 12, 14, 14) + ); + objects.forEach(o -> LOGGER.info(o.toString())); + + // collision check + LOGGER.info("Collision check"); + objects.forEach(o1 -> objects.forEach(o2 -> { + if (o1 != o2 && o1.intersectsWith(o2)) { + o1.collision(o2); + } + })); + + // output eventual object statuses + LOGGER.info("Print object status after collision checks"); + objects.forEach(o -> LOGGER.info(o.toString())); +} +``` + +When a collision is detected between two objects, the `collision(GameObject)` method is called on the first object (o1) with the second object (o2) as the argument. This method call is dispatched at runtime to the appropriate `collision(GameObject)` method in the class of o1. Inside this method, another method call `gameObject.collisionWithX(this)` is made on o2 (where X is the type of o1), which is dispatched at runtime to the appropriate `collisionWithX(GameObject)` method in the class of o2. This is the "double dispatch" in Java - two method calls are dispatched at runtime based on the types of two objects. + +Here is the program output: + +``` +15:47:23.763 [main] INFO com.iluwatar.doubledispatch.App -- Init objects and print their status +15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- FlamingAsteroid at [0,0,5,5] damaged=false onFire=true +15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationMir at [1,1,2,2] damaged=false onFire=false +15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- Meteoroid at [10,10,15,15] damaged=false onFire=false +15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationIss at [12,12,14,14] damaged=false onFire=false +15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- Collision check +15:47:23.772 [main] INFO com.iluwatar.doubledispatch.SpaceStationMir -- FlamingAsteroid hits SpaceStationMir. SpaceStationMir is damaged! SpaceStationMir is set on fire! +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.Meteoroid -- SpaceStationMir hits FlamingAsteroid. +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.SpaceStationMir -- {} is damaged! hits Meteoroid. +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.Meteoroid -- SpaceStationIss hits Meteoroid. +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- Print object status after collision checks +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- FlamingAsteroid at [0,0,5,5] damaged=false onFire=true +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationMir at [1,1,2,2] damaged=true onFire=true +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- Meteoroid at [10,10,15,15] damaged=false onFire=false +15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationIss at [12,12,14,14] damaged=true onFire=false +``` + +## Detailed Explanation of Double Dispatch Pattern with Real-World Examples + +![Double Dispatch](./etc/double-dispatch.png "Double Dispatch") + +## When to Use the Double Dispatch Pattern in Java + +* When the behavior of a method needs to vary not just based on the object it is called on, but also based on the type of the argument. +* In scenarios where if-else or switch-case type checks against the type of objects are cumbersome and not scalable. +* When implementing operations in domain classes without contaminating their code with complex decision-making logic about other domain classes. + +## Real-World Applications of Double Dispatch Pattern in Java + +* Graphical user interfaces where different actions are taken based on different types of mouse events interacting with different types of elements. +* Simulation systems where interactions between different types of objects need to trigger distinct behaviors. + +## Benefits and Trade-offs of Double Dispatch Pattern + +Benefits: + +* Increases the flexibility of code by handling interaction between objects in a manner that is easy to understand and maintain. +* Helps in adhering to the [Open/Closed Principle](https://java-design-patterns.com/principles/#open-closed-principle) by allowing new classes to be introduced without modifying existing classes. + +Trade-offs: + +* Can lead to more complex code structures, especially in languages like Java that do not support this pattern natively. +* May require additional effort in maintaining and extending as new classes are added. + +## Related Java Design Patterns + +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Similar in intent where it's used to choose an algorithm at runtime, though Strategy focuses on single object context rather than interactions between multiple objects. +* [Visitor](https://java-design-patterns.com/patterns/visitor/): Often used together with Double Dispatch to encapsulate operations performed on a set of element objects. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/4awj7cV) +* [Java Design Pattern Essentials](https://amzn.to/3Jg8ZZV) +* [Refactoring to Patterns](https://amzn.to/3vRBJ8k) diff --git a/double-dispatch/pom.xml b/double-dispatch/pom.xml index 297e93b9dd70..035732318ee0 100644 --- a/double-dispatch/pom.xml +++ b/double-dispatch/pom.xml @@ -34,6 +34,14 @@ double-dispatch + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java index ed61cd436b32..bc38d0ee689d 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java @@ -37,9 +37,9 @@ * to change the method's implementation and add a new instanceof-check. This violates the single * responsibility principle - a class should have only one reason to change. * - *

Instead of the instanceof-checks a better way is to make another virtual call on the - * parameter object. This way new functionality can be easily added without the need to modify - * existing implementation (open-closed principle). + *

Instead of the instanceof-checks a better way is to make another virtual call on the parameter + * object. This way new functionality can be easily added without the need to modify existing + * implementation (open-closed principle). * *

In this example we have hierarchy of objects ({@link GameObject}) that can collide to each * other. Each object has its own coordinates which are checked against the other objects' @@ -56,25 +56,28 @@ public class App { */ public static void main(String[] args) { // initialize game objects and print their status - var objects = List.of( - new FlamingAsteroid(0, 0, 5, 5), - new SpaceStationMir(1, 1, 2, 2), - new Meteoroid(10, 10, 15, 15), - new SpaceStationIss(12, 12, 14, 14) - ); + LOGGER.info("Init objects and print their status"); + var objects = + List.of( + new FlamingAsteroid(0, 0, 5, 5), + new SpaceStationMir(1, 1, 2, 2), + new Meteoroid(10, 10, 15, 15), + new SpaceStationIss(12, 12, 14, 14)); objects.forEach(o -> LOGGER.info(o.toString())); - LOGGER.info(""); // collision check - objects.forEach(o1 -> objects.forEach(o2 -> { - if (o1 != o2 && o1.intersectsWith(o2)) { - o1.collision(o2); - } - })); - LOGGER.info(""); + LOGGER.info("Collision check"); + objects.forEach( + o1 -> + objects.forEach( + o2 -> { + if (o1 != o2 && o1.intersectsWith(o2)) { + o1.collision(o2); + } + })); // output eventual object statuses + LOGGER.info("Print object status after collision checks"); objects.forEach(o -> LOGGER.info(o.toString())); - LOGGER.info(""); } } diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java index 9789fc3bb567..98b4c6c7a932 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doubledispatch; -/** - * Flaming asteroid game object. - */ +/** Flaming asteroid game object. */ public class FlamingAsteroid extends Meteoroid { public FlamingAsteroid(int left, int top, int right, int bottom) { diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java index 2393cb2bbb72..85ef3aa85241 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java @@ -24,9 +24,12 @@ */ package com.iluwatar.doubledispatch; -/** - * Game objects have coordinates and some other status information. - */ +import lombok.Getter; +import lombok.Setter; + +/** Game objects have coordinates and some other status information. */ +@Getter +@Setter public abstract class GameObject extends Rectangle { private boolean damaged; @@ -38,24 +41,9 @@ public GameObject(int left, int top, int right, int bottom) { @Override public String toString() { - return String.format("%s at %s damaged=%b onFire=%b", this.getClass().getSimpleName(), - super.toString(), isDamaged(), isOnFire()); - } - - public boolean isOnFire() { - return onFire; - } - - public void setOnFire(boolean onFire) { - this.onFire = onFire; - } - - public boolean isDamaged() { - return damaged; - } - - public void setDamaged(boolean damaged) { - this.damaged = damaged; + return String.format( + "%s at %s damaged=%b onFire=%b", + this.getClass().getSimpleName(), super.toString(), isDamaged(), isOnFire()); } public abstract void collision(GameObject gameObject); diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java index 7d9d950a81c4..f2e0587590cb 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java @@ -27,9 +27,7 @@ import com.iluwatar.doubledispatch.constants.AppConstants; import lombok.extern.slf4j.Slf4j; -/** - * Meteoroid game object. - */ +/** Meteoroid game object. */ @Slf4j public class Meteoroid extends GameObject { @@ -44,14 +42,14 @@ public void collision(GameObject gameObject) { @Override public void collisionResolve(FlamingAsteroid asteroid) { - LOGGER.info(AppConstants.HITS, asteroid.getClass().getSimpleName(), this.getClass() - .getSimpleName()); + LOGGER.info( + AppConstants.HITS, asteroid.getClass().getSimpleName(), this.getClass().getSimpleName()); } @Override public void collisionResolve(Meteoroid meteoroid) { - LOGGER.info(AppConstants.HITS, meteoroid.getClass().getSimpleName(), this.getClass() - .getSimpleName()); + LOGGER.info( + AppConstants.HITS, meteoroid.getClass().getSimpleName(), this.getClass().getSimpleName()); } @Override diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java index 7f5e88994d36..4dfe8f22a7f1 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Rectangle has coordinates and can be checked for overlap against other Rectangles. - */ +/** Rectangle has coordinates and can be checked for overlap against other Rectangles. */ @Getter @RequiredArgsConstructor public class Rectangle { @@ -40,8 +38,10 @@ public class Rectangle { private final int bottom; boolean intersectsWith(Rectangle r) { - return !(r.getLeft() > getRight() || r.getRight() < getLeft() || r.getTop() > getBottom() || r - .getBottom() < getTop()); + return !(r.getLeft() > getRight() + || r.getRight() < getLeft() + || r.getTop() > getBottom() + || r.getBottom() < getTop()); } @Override diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java index 0ab506092d71..14c44c59602a 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doubledispatch; -/** - * Space station ISS game object. - */ +/** Space station ISS game object. */ public class SpaceStationIss extends SpaceStationMir { public SpaceStationIss(int left, int top, int right, int bottom) { diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java index b92e7e9d4695..045698256a2e 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java @@ -27,9 +27,7 @@ import com.iluwatar.doubledispatch.constants.AppConstants; import lombok.extern.slf4j.Slf4j; -/** - * Space station Mir game object. - */ +/** Space station Mir game object. */ @Slf4j public class SpaceStationMir extends GameObject { @@ -44,32 +42,40 @@ public void collision(GameObject gameObject) { @Override public void collisionResolve(FlamingAsteroid asteroid) { - LOGGER.info(AppConstants.HITS + " {} is damaged! {} is set on fire!", asteroid.getClass() - .getSimpleName(), - this.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass() - .getSimpleName()); + LOGGER.info( + AppConstants.HITS + " {} is damaged! {} is set on fire!", + asteroid.getClass().getSimpleName(), + this.getClass().getSimpleName(), + this.getClass().getSimpleName(), + this.getClass().getSimpleName()); setDamaged(true); setOnFire(true); } @Override public void collisionResolve(Meteoroid meteoroid) { - LOGGER.info(AppConstants.HITS + " {} is damaged!", meteoroid.getClass().getSimpleName(), - this.getClass().getSimpleName(), this.getClass().getSimpleName()); + logHits(meteoroid); setDamaged(true); } @Override public void collisionResolve(SpaceStationMir mir) { - LOGGER.info(AppConstants.HITS + " {} is damaged!", mir.getClass().getSimpleName(), - this.getClass().getSimpleName(), this.getClass().getSimpleName()); + logHits(mir); setDamaged(true); } @Override public void collisionResolve(SpaceStationIss iss) { - LOGGER.info(AppConstants.HITS, " {} is damaged!", iss.getClass().getSimpleName(), - this.getClass().getSimpleName(), this.getClass().getSimpleName()); + logHits(iss); setDamaged(true); } + + private void logHits(GameObject gameObject) { + LOGGER.info( + AppConstants.HITS, + " {} is damaged!", + gameObject.getClass().getSimpleName(), + this.getClass().getSimpleName(), + this.getClass().getSimpleName()); + } } diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java index 0f4a51da1b56..f664092fa944 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doubledispatch.constants; -/** - * Constants class to define all constants. - */ +/** Constants class to define all constants. */ public class AppConstants { public static final String HITS = "{} hits {}."; diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java index 5976262093c2..1abff6a45c73 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java @@ -24,25 +24,19 @@ */ package com.iluwatar.doubledispatch; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java index df1065dc6310..7d11fa8502fc 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java @@ -29,10 +29,9 @@ import java.util.Objects; /** - * Date: 12/10/15 - 8:37 PM Test for Collision + * CollisionTest * * @param Type of GameObject - * @author Jeroen Meulemeester */ public abstract class CollisionTest { @@ -47,14 +46,18 @@ public abstract class CollisionTest { * Collide the tested item with the other given item and verify if the damage and fire state is as * expected * - * @param other The other object we have to collide with + * @param other The other object we have to collide with * @param otherDamaged Indicates if the other object should be damaged after the collision - * @param otherOnFire Indicates if the other object should be burning after the collision - * @param thisDamaged Indicates if the test object should be damaged after the collision - * @param thisOnFire Indicates if the other object should be burning after the collision + * @param otherOnFire Indicates if the other object should be burning after the collision + * @param thisDamaged Indicates if the test object should be damaged after the collision + * @param thisOnFire Indicates if the other object should be burning after the collision */ - void testCollision(final GameObject other, final boolean otherDamaged, final boolean otherOnFire, - final boolean thisDamaged, final boolean thisOnFire) { + void testCollision( + final GameObject other, + final boolean otherDamaged, + final boolean otherOnFire, + final boolean thisDamaged, + final boolean thisOnFire) { Objects.requireNonNull(other); Objects.requireNonNull(getTestedObject()); @@ -68,24 +71,33 @@ void testCollision(final GameObject other, final boolean otherDamaged, final boo testOnFire(tested, other, thisOnFire); testDamaged(tested, other, thisDamaged); - } /** * Test if the fire state of the target matches the expected state after colliding with the given * object * - * @param target The target object - * @param other The other object + * @param target The target object + * @param other The other object * @param expectTargetOnFire The expected state of fire on the target object */ - private void testOnFire(final GameObject target, final GameObject other, final boolean expectTargetOnFire) { + private void testOnFire( + final GameObject target, final GameObject other, final boolean expectTargetOnFire) { final var targetName = target.getClass().getSimpleName(); final var otherName = other.getClass().getSimpleName(); - final var errorMessage = expectTargetOnFire - ? "Expected [" + targetName + "] to be on fire after colliding with [" + otherName + "] but it was not!" - : "Expected [" + targetName + "] not to be on fire after colliding with [" + otherName + "] but it was!"; + final var errorMessage = + expectTargetOnFire + ? "Expected [" + + targetName + + "] to be on fire after colliding with [" + + otherName + + "] but it was not!" + : "Expected [" + + targetName + + "] not to be on fire after colliding with [" + + otherName + + "] but it was!"; assertEquals(expectTargetOnFire, target.isOnFire(), errorMessage); } @@ -94,19 +106,28 @@ private void testOnFire(final GameObject target, final GameObject other, final b * Test if the damage state of the target matches the expected state after colliding with the * given object * - * @param target The target object - * @param other The other object + * @param target The target object + * @param other The other object * @param expectedDamage The expected state of damage on the target object */ - private void testDamaged(final GameObject target, final GameObject other, final boolean expectedDamage) { + private void testDamaged( + final GameObject target, final GameObject other, final boolean expectedDamage) { final var targetName = target.getClass().getSimpleName(); final var otherName = other.getClass().getSimpleName(); - final var errorMessage = expectedDamage - ? "Expected [" + targetName + "] to be damaged after colliding with [" + otherName + "] but it was not!" - : "Expected [" + targetName + "] not to be damaged after colliding with [" + otherName + "] but it was!"; + final var errorMessage = + expectedDamage + ? "Expected [" + + targetName + + "] to be damaged after colliding with [" + + otherName + + "] but it was not!" + : "Expected [" + + targetName + + "] not to be damaged after colliding with [" + + otherName + + "] but it was!"; assertEquals(expectedDamage, target.isDamaged(), errorMessage); } - } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java index 1fe4529f66e8..65cd7cbd16b9 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java @@ -30,11 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/10/15 - 11:31 PM - * - * @author Jeroen Meulemeester - */ +/** FlamingAsteroidTest */ class FlamingAsteroidTest extends CollisionTest { @Override @@ -42,9 +38,7 @@ final FlamingAsteroid getTestedObject() { return new FlamingAsteroid(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var asteroid = new FlamingAsteroid(1, 2, 3, 4); @@ -57,52 +51,27 @@ void testConstructor() { assertEquals("FlamingAsteroid at [1,2,3,4] damaged=false onFire=true", asteroid.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 2, 3, 4), - false, true, - false, true - ); + testCollision(new FlamingAsteroid(1, 2, 3, 4), false, true, false, true); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, true - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, true); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, true, - false, true - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, true, false, true); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, true, - false, true - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, true, false, true); } - -} \ No newline at end of file +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java index 630512731c15..17d529523dfa 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java @@ -29,11 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/10/15 - 11:31 PM - * - * @author Jeroen Meulemeester - */ +/** MeteoroidTest */ class MeteoroidTest extends CollisionTest { @Override @@ -41,9 +37,7 @@ final Meteoroid getTestedObject() { return new Meteoroid(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var meteoroid = new Meteoroid(1, 2, 3, 4); @@ -56,52 +50,27 @@ void testConstructor() { assertEquals("Meteoroid at [1,2,3,4] damaged=false onFire=false", meteoroid.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 1, 3, 4), - false, true, - false, false - ); + testCollision(new FlamingAsteroid(1, 1, 3, 4), false, true, false, false); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, false - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, false); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, false, false, false); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, false, false, false); } - -} \ No newline at end of file +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java index edff5010e010..6b143643a805 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Unit test for Rectangle - */ +/** Unit test for Rectangle */ class RectangleTest { /** @@ -48,18 +46,15 @@ void testConstructor() { } /** - * Test if the values passed through the constructor matches the values in the {@link - * #toString()} + * Test if the values passed through the constructor matches the values in the {@link #toString()} */ @Test - void testToString() throws Exception { + void testToString() { final var rectangle = new Rectangle(1, 2, 3, 4); assertEquals("[1,2,3,4]", rectangle.toString()); } - /** - * Test if the {@link Rectangle} class can detect if it intersects with another rectangle. - */ + /** Test if the {@link Rectangle} class can detect if it intersects with another rectangle. */ @Test void testIntersection() { assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(0, 0, 1, 1))); @@ -67,5 +62,4 @@ void testIntersection() { assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(2, 2, 3, 3))); assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-2, -2, -1, -1))); } - } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java index f4f39ad1970b..593caf3d64fb 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java @@ -29,11 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/10/15 - 11:31 PM - * - * @author Jeroen Meulemeester - */ +/** SpaceStationIssTest */ class SpaceStationIssTest extends CollisionTest { @Override @@ -41,9 +37,7 @@ final SpaceStationIss getTestedObject() { return new SpaceStationIss(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var iss = new SpaceStationIss(1, 2, 3, 4); @@ -56,52 +50,27 @@ void testConstructor() { assertEquals("SpaceStationIss at [1,2,3,4] damaged=false onFire=false", iss.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 1, 3, 4), - false, true, - false, false - ); + testCollision(new FlamingAsteroid(1, 1, 3, 4), false, true, false, false); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, false - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, false); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, false, false, false); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, false, false, false); } - -} \ No newline at end of file +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java index b57085eb2ec5..155595253ebc 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java @@ -29,11 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/10/15 - 11:31 PM - * - * @author Jeroen Meulemeester - */ +/** SpaceStationMirTest */ class SpaceStationMirTest extends CollisionTest { @Override @@ -41,9 +37,7 @@ final SpaceStationMir getTestedObject() { return new SpaceStationMir(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var mir = new SpaceStationMir(1, 2, 3, 4); @@ -56,52 +50,27 @@ void testConstructor() { assertEquals("SpaceStationMir at [1,2,3,4] damaged=false onFire=false", mir.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 1, 3, 4), - false, true, - false, false - ); + testCollision(new FlamingAsteroid(1, 1, 3, 4), false, true, false, false); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, false - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, false); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, false, false, false); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, false, false, false); } - -} \ No newline at end of file +} diff --git a/dynamic-proxy/README.md b/dynamic-proxy/README.md index 21976ddeb905..9dedbedd0ce5 100644 --- a/dynamic-proxy/README.md +++ b/dynamic-proxy/README.md @@ -1,354 +1,341 @@ --- -title: Dynamic Proxy +title: "Dynamic Proxy Pattern in Java: Facilitating Seamless Object Interception" +shortTitle: Dynamic Proxy +description: "Explore the Dynamic Proxy Pattern in Java, a flexible runtime mechanism for creating proxies that enhance functionality and control access to objects. Learn through real-world examples and detailed explanations." category: Structural language: en tag: -- Generic + - Decoupling + - Dynamic typing + - Enhancement + - Extensibility + - Proxy + - Runtime --- -## Intent -Dynamic proxy is a Java mechanism that allows developers to create a proxy instance for interfaces at runtime. It is primarily used for intercepting method calls, enabling developers to add additional processing around the actual method invocation. +## Also known as -## Explanation +* Runtime Proxy -### Real-world example -Mockito, a popular Java mocking framework, employs dynamic proxy to create mock objects for testing. Mock objects mimic the behavior of real objects, allowing developers to isolate components and verify interactions in unit tests. +## Intent of Dynamic Proxy Design Pattern -Consider a scenario where a service class depends on an external component, such as a database access object (DAO). Instead of interacting with a real DAO in a test, Mockito can dynamically generate a proxy, intercepting method invocations and returning predefined values. This enables focused unit testing without the need for a real database connection. +To provide a flexible proxy mechanism capable of dynamically creating proxies for various interfaces at runtime, allowing for controlled access or functionality enhancement of objects. -### In plain words -Dynamic proxy is a specialized form of proxy in Java, serving as a flexible and dynamic method to intercept and manipulate method calls. By utilizing dynamic proxies, developers can implement additional functionalities without modifying the original class code. +## Detailed Explanation of Dynamic Proxy Pattern with Real-World Examples -### Wikipedia says -A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface. Thus, a dynamic proxy class can be used to create a type-safe proxy object for a list of interfaces without requiring pre-generation of the proxy class, such as with compile-time tools. Method invocations on an instance of a dynamic proxy class are dispatched to a single method in the instance's invocation handler, and they are encoded with a _java.lang.reflect.Method_ object identifying the method that was invoked and an array of type _Object_ containing the arguments. +Real-world example -### Programmatic Example -This example allow us to hit the public fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. +> Mockito, a popular Java mocking framework, employs dynamic proxy to create mock objects for testing. Mock objects mimic the behavior of real objects, allowing developers to isolate components and verify interactions in unit tests. Consider a scenario where a service class depends on an external component, such as a database access object (DAO). Instead of interacting with a real DAO in a test, Mockito can dynamically generate a proxy, intercepting method invocations and returning predefined values. This enables focused unit testing without the need for a real database connection. -The call to _Proxy.newProxyInstance_ creates a new dynamic proxy for the _AlbumService_ interface and sets the _AlbumInvocationHandler_ class as the handler to intercept all the interface's methods. Everytime that we call an _AlbumService_'s method, the handler's method `invoke` will be call automatically, and it will pass all the method's metadata and arguments to other specialized class - _TinyRestClient_ - to prepare the Rest API call accordingly. +In plain words -![Sequence diagram](./etc/dynamic-proxy-sequence.png "Dynamic Proxy sequence diagram") +> The Dynamic Proxy Pattern in Java is a specialized form of proxy, serving as a flexible and dynamic method to intercept and manipulate method calls. By utilizing dynamic proxies, developers can implement additional functionalities without modifying the original class code. This is particularly useful in scenarios requiring enhancement of existing functionalities. -In this demo, the Dynamic Proxy pattern help us to run business logic through an interface without an explicit implementation of that interface and supported on the Java Reflection approach. +Wikipedia says + +> A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface. Thus, a dynamic proxy class can be used to create a type-safe proxy object for a list of interfaces without requiring pre-generation of the proxy class, such as with compile-time tools. Method invocations on an instance of a dynamic proxy class are dispatched to a single method in the instance's invocation handler, and they are encoded with a _java.lang.reflect.Method_ object identifying the method that was invoked and an array of type _Object_ containing the arguments. + +## Programmatic Example of Dynamic Proxy Pattern in Java + +This example demonstrates using the Dynamic Proxy pattern in Java to hit the public fake API [JSONPlaceholder](https://jsonplaceholder.typicode.com) for the resource `Album` through an interface. + +In this demo, the Dynamic Proxy pattern helps us run business logic through an interface without explicitly implementing that interface, leveraging Java Reflection. + +The `App` class sets up the dynamic proxy for the `AlbumService` interface and demonstrates how to use the proxy to make API calls. ```java +@Slf4j public class App { - private static final Logger logger = LoggerFactory.getLogger(App.class); - - static final String REST_API_URL = "https://jsonplaceholder.typicode.com"; - - private String baseUrl; - private HttpClient httpClient; - private AlbumService albumServiceProxy; - - /** - * Class constructor. - * - * @param baseUrl Root url for endpoints. - * @param httpClient Handle the http communication. - */ - public App(String baseUrl, HttpClient httpClient) { - this.baseUrl = baseUrl; - this.httpClient = httpClient; - } - - /** - * Create the Dynamic Proxy linked to the AlbumService interface and to the AlbumInvocationHandler. - */ - public void createDynamicProxy() { - AlbumInvocationHandler albumInvocationHandler = new AlbumInvocationHandler(baseUrl, httpClient); - - albumServiceProxy = (AlbumService) Proxy.newProxyInstance( - App.class.getClassLoader(), new Class[] { AlbumService.class }, albumInvocationHandler); - } - - /** - * Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods - * and receive the responses from the Rest API. - */ - public void callMethods() { - int albumId = 17; - int userId = 3; - - var albums = albumServiceProxy.readAlbums(); - albums.forEach(album -> logger.info("{}", album)); - - var album = albumServiceProxy.readAlbum(albumId); - logger.info("{}", album); - - var newAlbum = albumServiceProxy.createAlbum(Album.builder() - .title("Big World").userId(userId).build()); - logger.info("{}", newAlbum); - - var editAlbum = albumServiceProxy.updateAlbum(albumId, Album.builder() - .title("Green Valley").userId(userId).build()); - logger.info("{}", editAlbum); - - var removedAlbum = albumServiceProxy.deleteAlbum(albumId); - logger.info("{}", removedAlbum); - } - - /** - * Application entry point. - * - * @param args External arguments to be passed. Optional. - */ - public static void main(String[] args) { - App app = new App(App.REST_API_URL, HttpClient.newHttpClient()); - app.createDynamicProxy(); - app.callMethods(); - } + static final String REST_API_URL = "https://jsonplaceholder.typicode.com"; + private AlbumService albumServiceProxy; + + public static void main(String[] args) { + App app = new App(); + app.createDynamicProxy(); + app.callMethods(); + } + + public void createDynamicProxy() { + AlbumInvocationHandler handler = new AlbumInvocationHandler(REST_API_URL, HttpClient.newHttpClient()); + albumServiceProxy = (AlbumService) Proxy.newProxyInstance( + App.class.getClassLoader(), new Class[]{AlbumService.class}, handler); + } + + public void callMethods() { + var albums = albumServiceProxy.readAlbums(); + albums.forEach(album -> LOGGER.info("{}", album)); + + var album = albumServiceProxy.readAlbum(17); + LOGGER.info("{}", album); + + var newAlbum = albumServiceProxy.createAlbum(Album.builder().title("Big World").userId(3).build()); + LOGGER.info("{}", newAlbum); + var editAlbum = albumServiceProxy.updateAlbum(17, Album.builder().title("Green Valley").userId(3).build()); + LOGGER.info("{}", editAlbum); + + var removedAlbum = albumServiceProxy.deleteAlbum(17); + LOGGER.info("{}", removedAlbum); + } } ``` -Declaration of the AlbumService interface. Annotations were used to provide additional information such as: http method (Get, Post, Put, Delete), endpoint's url, path parameters, body parameters, among others. +* The `createDynamicProxy` method uses `Proxy.newProxyInstance` to create a new dynamic proxy for the `AlbumService` interface. +* The `callMethods` method demonstrates using the dynamic proxy to make various API calls. + +The `AlbumService` interface defines the API operations that can be dynamically proxied. It uses custom annotations to specify HTTP methods and paths. ```java public interface AlbumService { + @Get("/albums") + List readAlbums(); - /** - * Get a list of albums from an endpoint. - * - * @return List of albums' data. - */ - @Get("/albums") - List readAlbums(); - - /** - * Get a specific album from an endpoint. - * - * @param albumId Album's id to search for. - * @return Album's data. - */ - @Get("/albums/{albumId}") - Album readAlbum(@Path("albumId") Integer albumId); - - /** - * Creates a new album. - * - * @param album Album's data to be created. - * @return New album's data. - */ - @Post("/albums") - Album createAlbum(@Body Album album); - - /** - * Updates an existing album. - * - * @param albumId Album's id to be modified. - * @param album New album's data. - * @return Updated album's data. - */ - @Put("/albums/{albumId}") - Album updateAlbum(@Path("albumId") Integer albumId, @Body Album album); - - /** - * Deletes an album. - * - * @param albumId Album's id to be deleted. - * @return Empty album. - */ - @Delete("/albums/{albumId}") - Album deleteAlbum(@Path("albumId") Integer albumId); + @Get("/albums/{albumId}") + Album readAlbum(@Path("albumId") Integer albumId); + @Post("/albums") + Album createAlbum(@Body Album album); + + @Put("/albums/{albumId}") + Album updateAlbum(@Path("albumId") Integer albumId, @Body Album album); + + @Delete("/albums/{albumId}") + Album deleteAlbum(@Path("albumId") Integer albumId); } ``` -Declaration of the AlbumInvocationHandler class whose method _invoke_ will be called automatically every time that an AlbumService's method is called. The call processing is delegated to the _TinyRestClient_. +* Annotations like `@Get` and `@Post` indicate the HTTP method and the path for each method. +* These annotations are used by the `TinyRestClient` to construct appropriate HTTP requests. + +The `AlbumInvocationHandler` class is where method calls on the proxy are intercepted and delegated to the `TinyRestClient`. ```java +@Slf4j public class AlbumInvocationHandler implements InvocationHandler { - private static final Logger logger = LoggerFactory.getLogger(AlbumInvocationHandler.class); + private TinyRestClient restClient; - private TinyRestClient restClient; - - /** - * Class constructor. It instantiates a TinyRestClient object. - * - * @param baseUrl Root url for endpoints. - * @param httpClient Handle the http communication. - */ - public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { - this.restClient = new TinyRestClient(baseUrl, httpClient); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - - logger.info("===== Calling the method {}.{}()", - method.getDeclaringClass().getSimpleName(), method.getName()); - - return restClient.send(method, args); - } + public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { + this.restClient = new TinyRestClient(baseUrl, httpClient); + } + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + LOGGER.info("===== Calling the method {}.{}()", + method.getDeclaringClass().getSimpleName(), method.getName()); + return restClient.send(method, args); + } } ``` -The TinyRestClient class handle all the http communication with a Rest API. It is supported by the built-in component HttpClient. This class is an optional part of the pattern. +* Implements `InvocationHandler`, which requires defining the `invoke` method. +* The `invoke` method is called every time a method on the proxy is invoked. It delegates the call to the `TinyRestClient`, passing along the method and its arguments. + +The `TinyRestClient` handles the construction and sending of HTTP requests based on the annotations and method details. ```java +@Slf4j public class TinyRestClient { + private String baseUrl; + private HttpClient httpClient; - private String baseUrl; - private HttpClient httpClient; - - /** - * Class constructor. - * - * @param baseUrl Root url for endpoints. - * @param httpClient Handle the http communication. - */ - public TinyRestClient(String baseUrl, HttpClient httpClient) { - this.baseUrl = baseUrl; - this.httpClient = httpClient; - } - - /** - * Creates a http communication to request and receive data from an endpoint. - * - * @param method Interface's method which is annotated with a http method. - * @param args Method's arguments passed in the call. - * @return Response from the endpoint. - * @throws IOException Exception thrown when any fail happens in the call. - * @throws InterruptedException Exception thrown when call is interrupted. - */ - public Object send(Method method, Object[] args) throws IOException, InterruptedException { - var httpMethodAnnotation = getHttpMethodAnnotation(method.getDeclaredAnnotations()); - if (httpMethodAnnotation == null) { - return null; - } - var httpMethodName = httpMethodAnnotation.annotationType().getSimpleName().toUpperCase(); - var url = baseUrl + buildUrl(method, args, httpMethodAnnotation); - var bodyPublisher = buildBodyPublisher(method, args); - var httpRequest = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Content-Type", "application/json") - .method(httpMethodName, bodyPublisher) - .build(); - var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); - return getResponse(method, httpResponse); - } - - private String buildUrl(Method method, Object[] args, Annotation httpMethodAnnotation) { - var url = annotationValue(httpMethodAnnotation); - if (url == null) { - return ""; + public TinyRestClient(String baseUrl, HttpClient httpClient) { + this.baseUrl = baseUrl; + this.httpClient = httpClient; } - var index = 0; - for (var parameter : method.getParameters()) { - var pathAnnotation = getAnnotationOf(parameter.getDeclaredAnnotations(), Path.class); - if (pathAnnotation != null) { - var pathParam = "{" + annotationValue(pathAnnotation) + "}"; - var pathValue = args[index].toString(); - url = url.replace(pathParam, pathValue); - } - index++; - } - return url; - } - - private HttpRequest.BodyPublisher buildBodyPublisher(Method method, Object[] args) { - var index = 0; - for (var parameter : method.getParameters()) { - var bodyAnnotation = getAnnotationOf(parameter.getDeclaredAnnotations(), Body.class); - if (bodyAnnotation != null) { - var body = JsonUtil.objectToJson(args[index]); - return HttpRequest.BodyPublishers.ofString(body); - } - index++; - } - return HttpRequest.BodyPublishers.noBody(); - } - - private Object getResponse(Method method, HttpResponse httpResponse) { - var rawData = httpResponse.body(); - var returnType = method.getGenericReturnType(); - if (returnType instanceof ParameterizedType) { - Class responseClass = (Class) (((ParameterizedType) returnType) - .getActualTypeArguments()[0]); - return JsonUtil.jsonToList(rawData, responseClass); - } else { - Class responseClass = method.getReturnType(); - return JsonUtil.jsonToObject(rawData, responseClass); + + public Object send(Method method, Object[] args) throws IOException, InterruptedException { + var url = baseUrl + buildUrl(method, args); + var httpRequest = HttpRequest.newBuilder().uri(URI.create(url)).build(); + var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + return getResponse(method, httpResponse); } - } - - private Annotation getHttpMethodAnnotation(Annotation[] annotations) { - return Arrays.stream(annotations) - .filter(annot -> annot.annotationType().isAnnotationPresent(HttpMethod.class)) - .findFirst().orElse(null); - } - - private Annotation getAnnotationOf(Annotation[] annotations, Class clazz) { - return Arrays.stream(annotations) - .filter(annot -> annot.annotationType().equals(clazz)) - .findFirst().orElse(null); - } - - private String annotationValue(Annotation annotation) { - var valueMethod = Arrays.stream(annotation.annotationType().getDeclaredMethods()) - .filter(methodAnnot -> methodAnnot.getName().equals("value")) - .findFirst().orElse(null); - if (valueMethod == null) { - return null; + + private String buildUrl(Method method, Object[] args) { + // Simplified URL building logic + return "/albums"; } - Object result; - try { - result = valueMethod.invoke(annotation, (Object[]) null); - } catch (Exception e) { - result = null; + + private Object getResponse(Method method, HttpResponse httpResponse) { + // Simplified response handling logic + return httpResponse.body(); } - return (result instanceof String strResult ? strResult : null); - } } ``` -## Class diagram -![Class diagram](./etc/dynamic-proxy-classes.png "Dynamic Proxy class diagram") +* This class uses Java's `HttpClient` to send HTTP requests. +* The `send` method constructs a `HttpRequest` using details from the method's annotations, although simplified here to focus on the dynamic proxy mechanism. +* The response is returned as a simple string, demonstrating basic interaction with an API. + +Running the example produces the following console output showcasing the API calls and responses. See the `App` class and `main` method above. + +``` +16:05:41.964 [main] INFO com.iluwatar.dynamicproxy.AlbumInvocationHandler -- ===== Calling the method AlbumService.readAlbums() +16:05:42.409 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=1, title=quidem molestiae enim, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=2, title=sunt qui excepturi placeat culpa, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=3, title=omnis laborum odio, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=4, title=non esse culpa molestiae omnis sed optio, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=5, title=eaque aut omnis a, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=6, title=natus impedit quibusdam illo est, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=7, title=quibusdam autem aliquid et et quia, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=8, title=qui fuga est a eum, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=9, title=saepe unde necessitatibus rem, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=10, title=distinctio laborum qui, userId=1) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=11, title=quam nostrum impedit mollitia quod et dolor, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=12, title=consequatur autem doloribus natus consectetur, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=13, title=ab rerum non rerum consequatur ut ea unde, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=14, title=ducimus molestias eos animi atque nihil, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=15, title=ut pariatur rerum ipsum natus repellendus praesentium, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=16, title=voluptatem aut maxime inventore autem magnam atque repellat, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=17, title=aut minima voluptatem ut velit, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=18, title=nesciunt quia et doloremque, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=19, title=velit pariatur quaerat similique libero omnis quia, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=20, title=voluptas rerum iure ut enim, userId=2) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=21, title=repudiandae voluptatem optio est consequatur rem in temporibus et, userId=3) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=22, title=et rem non provident vel ut, userId=3) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=23, title=incidunt quisquam hic adipisci sequi, userId=3) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=24, title=dolores ut et facere placeat, userId=3) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=25, title=vero maxime id possimus sunt neque et consequatur, userId=3) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=26, title=quibusdam saepe ipsa vel harum, userId=3) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=27, title=id non nostrum expedita, userId=3) +16:05:42.410 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=28, title=omnis neque exercitationem sed dolor atque maxime aut cum, userId=3) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=29, title=inventore ut quasi magnam itaque est fugit, userId=3) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=30, title=tempora assumenda et similique odit distinctio error, userId=3) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=31, title=adipisci laborum fuga laboriosam, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=32, title=reiciendis dolores a ut qui debitis non quo labore, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=33, title=iste eos nostrum, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=34, title=cumque voluptatibus rerum architecto blanditiis, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=35, title=et impedit nisi quae magni necessitatibus sed aut pariatur, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=36, title=nihil cupiditate voluptate neque, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=37, title=est placeat dicta ut nisi rerum iste, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=38, title=unde a sequi id, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=39, title=ratione porro illum labore eum aperiam sed, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=40, title=voluptas neque et sint aut quo odit, userId=4) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=41, title=ea voluptates maiores eos accusantium officiis tempore mollitia consequatur, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=42, title=tenetur explicabo ea, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=43, title=aperiam doloremque nihil, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=44, title=sapiente cum numquam officia consequatur vel natus quos suscipit, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=45, title=tenetur quos ea unde est enim corrupti qui, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=46, title=molestiae voluptate non, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=47, title=temporibus molestiae aut, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=48, title=modi consequatur culpa aut quam soluta alias perspiciatis laudantium, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=49, title=ut aut vero repudiandae voluptas ullam voluptas at consequatur, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=50, title=sed qui sed quas sit ducimus dolor, userId=5) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=51, title=odit laboriosam sint quia cupiditate animi quis, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=52, title=necessitatibus quas et sunt at voluptatem, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=53, title=est vel sequi voluptatem nemo quam molestiae modi enim, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=54, title=aut non illo amet perferendis, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=55, title=qui culpa itaque omnis in nesciunt architecto error, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=56, title=omnis qui maiores tempora officiis omnis rerum sed repellat, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=57, title=libero excepturi voluptatem est architecto quae voluptatum officia tempora, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=58, title=nulla illo consequatur aspernatur veritatis aut error delectus et, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=59, title=eligendi similique provident nihil, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=60, title=omnis mollitia sunt aliquid eum consequatur fugit minus laudantium, userId=6) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=61, title=delectus iusto et, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=62, title=eos ea non recusandae iste ut quasi, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=63, title=velit est quam, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=64, title=autem voluptatem amet iure quae, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=65, title=voluptates delectus iure iste qui, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=66, title=velit sed quia dolor dolores delectus, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=67, title=ad voluptas nostrum et nihil, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=68, title=qui quasi nihil aut voluptatum sit dolore minima, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=69, title=qui aut est, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=70, title=et deleniti unde, userId=7) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=71, title=et vel corporis, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=72, title=unde exercitationem ut, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=73, title=quos omnis officia, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=74, title=quia est eius vitae dolor, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=75, title=aut quia expedita non, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=76, title=dolorem magnam facere itaque ut reprehenderit tenetur corrupti, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=77, title=cupiditate sapiente maiores iusto ducimus cum excepturi veritatis quia, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=78, title=est minima eius possimus ea ratione velit et, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=79, title=ipsa quae voluptas natus ut suscipit soluta quia quidem, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=80, title=id nihil reprehenderit, userId=8) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=81, title=quibusdam sapiente et, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=82, title=recusandae consequatur vel amet unde, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=83, title=aperiam odio fugiat, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=84, title=est et at eos expedita, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=85, title=qui voluptatem consequatur aut ab quis temporibus praesentium, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=86, title=eligendi mollitia alias aspernatur vel ut iusto, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=87, title=aut aut architecto, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=88, title=quas perspiciatis optio, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=89, title=sit optio id voluptatem est eum et, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=90, title=est vel dignissimos, userId=9) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=91, title=repellendus praesentium debitis officiis, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=92, title=incidunt et et eligendi assumenda soluta quia recusandae, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=93, title=nisi qui dolores perspiciatis, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=94, title=quisquam a dolores et earum vitae, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=95, title=consectetur vel rerum qui aperiam modi eos aspernatur ipsa, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=96, title=unde et ut molestiae est molestias voluptatem sint, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=97, title=est quod aut, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=98, title=omnis quia possimus nesciunt deleniti assumenda sed autem, userId=10) +16:05:42.411 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=99, title=consectetur ut id impedit dolores sit ad ex aut, userId=10) +16:05:42.412 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=100, title=enim repellat iste, userId=10) +16:05:42.412 [main] INFO com.iluwatar.dynamicproxy.AlbumInvocationHandler -- ===== Calling the method AlbumService.readAlbum() +16:05:42.741 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=17, title=aut minima voluptatem ut velit, userId=2) +16:05:42.741 [main] INFO com.iluwatar.dynamicproxy.AlbumInvocationHandler -- ===== Calling the method AlbumService.createAlbum() +16:05:43.073 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=101, title=Big World, userId=3) +16:05:43.073 [main] INFO com.iluwatar.dynamicproxy.AlbumInvocationHandler -- ===== Calling the method AlbumService.updateAlbum() +16:05:43.220 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=17, title=Green Valley, userId=3) +16:05:43.220 [main] INFO com.iluwatar.dynamicproxy.AlbumInvocationHandler -- ===== Calling the method AlbumService.deleteAlbum() +16:05:43.357 [main] INFO com.iluwatar.dynamicproxy.App -- Album(id=null, title=null, userId=null) +``` + +## When to Use the Dynamic Proxy Pattern in Java -## Applicability Dynamic proxy should be used when you need to augment or enhance your current functionality without modifying your current code. Some examples of that usage could be: -- _Logging and Monitoring_: By creating a logging proxy, developers can intercept method invocations and log relevant information like method names, parameters, and execution times. +* You want to intercept calls to methods of an object at runtime for handling purposes such as logging, transaction management, or security checks. +* You need to create a proxy object for one or more interfaces dynamically at runtime without coding it explicitly for each interface. +* You aim to simplify complex systems by decoupling the client and the real object through a flexible proxy mechanism. -- _Security and Access Control_: Developers can create proxies that enforce security policies, restricting access to certain methods based on user roles or permissions. +## Dynamic Proxy Pattern Java Tutorials -- _Aspect-Oriented Programming_: Dynamic proxies facilitate the application of transversal aspects to methods without changing the original code. +* [Dynamic Proxies in Java (CodeGym)](https://codegym.cc/groups/posts/208-dynamic-proxies) +* [Introduction To Java Dynamic Proxy (Xperti)](https://xperti.io/blogs/java-dynamic-proxies-introduction/) +* [Dynamic Proxies in Java (Baeldung)](https://www.baeldung.com/java-dynamic-proxies) +* [Intro To Java Dynamic Proxies (KapreSoft)](https://www.kapresoft.com/java/2023/12/27/intro-to-java-proxies.html) +* [Exploring the Depths of Dynamic Proxy in Java: A Comprehensive Guide (Medium)](https://naveen-metta.medium.com/exploring-the-depths-of-dynamic-proxy-in-java-a-comprehensive-guide-f34fb45b38a3) -## Tutorials -- [Dynamic Proxies in Java - CodeGym](https://codegym.cc/groups/posts/208-dynamic-proxies) -- [Introduction To Java Dynamic Proxy - Xperti](https://xperti.io/blogs/java-dynamic-proxies-introduction/) +## Real-World Applications of Dynamic Proxy Pattern in Java -## Known uses Many frameworks and libraries use dynamic proxy to implement their functionalities: -- [Spring Framework](https://docs.spring.io/spring-framework/reference/core/aop.html), for aspect oriented programming. -- [Hibernate](https://hibernate.org/orm/), for data lazy loading. -- [Mockito](https://site.mockito.org/), for mocking objects in testing. -- [Cleverclient](https://github.com/sashirestela/cleverclient), for calling http endpoints through annotated interfaces. - -## Consequences - -### Pros -- **Increased flexibility in code**: Dynamic proxies in Java offer a high degree of flexibility, allowing developers to create versatile and adaptable applications. By using dynamic proxies, software -engineers can modify the behavior of methods at runtime, which is particularly useful in scenarios where the behavior of classes needs to be augmented or manipulated without altering their source code. -This flexibility is crucial in developing applications that require dynamic response to varying conditions or need to integrate with systems where interfaces may change over time. -- **Simplifying complex operations**: Dynamic proxies excel in simplifying complex operations, particularly in the areas of cross-cutting concerns such as logging, transaction management, and security. By intercepting method calls, dynamic proxies can uniformly apply certain operations across various methods and classes, thereby reducing the need for repetitive code. This capability is particularly beneficial in large-scale applications where such cross-cutting concerns are prevalent. For example, adding logging or authorization checks across multiple methods becomes a matter of implementing these features once in an invocation handler, rather than modifying each method individually. -- **Enhancing code maintainability**: Maintainability is a key advantage of using dynamic proxies. They promote cleaner and more organized code by separating the core business logic from cross-cutting concerns. This separation of concerns not only makes the codebase more understandable but also easier to test and debug. When the business logic is decoupled from aspects like logging or transaction handling, any changes in these areas do not impact the core functionality of the application. As a result, applications become more robust and easier to maintain and update, which is crucial in the fast-paced environment of software development where requirements and technologies are constantly evolving. - -### Cons -- **Performance overhead**: The use of reflection and method invocation through proxies can introduce latency, especially in performance-critical applications. This overhead might be negligible in most cases, but it becomes significant in scenarios with high-frequency method calls. -- **Complexity of debugging**: Since dynamic proxies introduce an additional layer of abstraction, tracing and debugging issues can be more challenging. It can be difficult to trace the flow of execution through proxies, especially when multiple proxies are involved. -- **Limited to interface-based programming**: They can only proxy interfaces, not classes. This limitation requires careful design considerations, particularly in situations where class-based proxies would be more appropriate. -- **Higher level of expertise**: Developers are normally not a fan of “magic code” — code that works in a non-transparent or overly complex manner. Those unfamiliar with the proxy pattern or reflection might find the codebase more complex to understand and maintain, potentially leading to errors or misuse of the feature. This complexity can be perceived as a form of “magic” that obscures the underlying process, making the code less intuitive and more challenging to debug or extend. Therefore, while dynamic proxies are powerful, their use should be approached with caution and a thorough understanding of their inner workings. - -## Related patterns -- [Proxy](https://java-design-patterns.com/patterns/proxy) - -## Credits -- [Dynamic Proxies in Java - Baeldung](https://www.baeldung.com/java-dynamic-proxies) -- [Dynamic Proxy Classes - Oracle](https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html) -- [Intro To Java Dynamic Proxies - KapreSoft](https://www.kapresoft.com/java/2023/12/27/intro-to-java-proxies.html) -- [Exploring the Depths of Dynamic Proxy in Java: A Comprehensive Guide](https://naveen-metta.medium.com/exploring-the-depths-of-dynamic-proxy-in-java-a-comprehensive-guide-f34fb45b38a3) + +* Java's `java.lang.reflect.Proxy` class is a built-in dynamic proxy mechanism. +* [Spring Framework](https://docs.spring.io/spring-framework/reference/core/aop.html), for aspect oriented programming. +* [Hibernate](https://hibernate.org/orm/), for data lazy loading. +* [Mockito](https://site.mockito.org/), for mocking objects in testing. +* [Cleverclient](https://github.com/sashirestela/cleverclient), for calling http endpoints through annotated interfaces. +* Java Reflection API: Java's built-in support for dynamic proxies with the use of java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler. +* Frameworks: Extensively used in Java frameworks like Spring for AOP (Aspect-Oriented Programming) to handle transactions, security, logging, etc. +* Middleware: In middleware services for transparently adding services like load balancing and access control. + +## Benefits and Trade-offs of Dynamic Proxy Pattern + +Benefits: + +* Increased flexibility in code: Dynamic proxies in Java offer a high degree of flexibility, allowing developers to create versatile and adaptable applications. By using dynamic proxies, software engineers can modify the behavior of methods at runtime, which is particularly useful in scenarios where the behavior of classes needs to be augmented or manipulated without altering their source code. This flexibility is crucial in developing applications that require dynamic response to varying conditions or need to integrate with systems where interfaces may change over time. +* Simplifying complex operations: Dynamic proxies excel in simplifying complex operations, particularly in the areas of cross-cutting concerns such as logging, transaction management, and security. By intercepting method calls, dynamic proxies can uniformly apply certain operations across various methods and classes, thereby reducing the need for repetitive code. This capability is particularly beneficial in large-scale applications where such cross-cutting concerns are prevalent. For example, adding logging or authorization checks across multiple methods becomes a matter of implementing these features once in an invocation handler, rather than modifying each method individually. +* Enhancing code maintainability: Maintainability is a key advantage of using dynamic proxies. They promote cleaner and more organized code by separating the core business logic from cross-cutting concerns. This separation of concerns not only makes the codebase more understandable but also easier to test and debug. When the business logic is decoupled from aspects like logging or transaction handling, any changes in these areas do not impact the core functionality of the application. As a result, applications become more robust and easier to maintain and update, which is crucial in the fast-paced environment of software development where requirements and technologies are constantly evolving. +* Separation of Concerns: Promotes separation of concerns by decoupling the client code from the actual handling of method calls. + +Trade-offs: + +* Performance overhead: The use of reflection and method invocation through proxies can introduce latency, especially in performance-critical applications. This overhead might be negligible in most cases, but it becomes significant in scenarios with high-frequency method calls. +* Complexity of debugging: Since dynamic proxies introduce an additional layer of abstraction, tracing and debugging issues can be more challenging. It can be difficult to trace the flow of execution through proxies, especially when multiple proxies are involved. +* Limited to interface-based programming: They can only proxy interfaces, not classes. This limitation requires careful design considerations, particularly in situations where class-based proxies would be more appropriate. +* Higher level of expertise: Developers are normally not a fan of “magic code” — code that works in a non-transparent or overly complex manner. Those unfamiliar with the proxy pattern or reflection might find the codebase more complex to understand and maintain, potentially leading to errors or misuse of the feature. This complexity can be perceived as a form of “magic” that obscures the underlying process, making the code less intuitive and more challenging to debug or extend. Therefore, while dynamic proxies are powerful, their use should be approached with caution and a thorough understanding of their inner workings. + +## Related Java Design Patterns + +* [Proxy](https://java-design-patterns.com/patterns/proxy): Static counterpart of the Dynamic Proxy, where proxies are explicitly coded. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar in structure by providing additional functionality, but without the dynamic proxy's capability to handle any interface. +* [Facade](https://java-design-patterns.com/patterns/facade/): Simplifies the interface to complex systems, not through dynamic proxies but through a single simplified interface. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3U0d8Gm) +* [Java Reflection in Action](https://amzn.to/3TVpe3t) +* [Pro Spring 6: An In-Depth Guide to the Spring Framework](https://amzn.to/43Zs2Bx) +* [Dynamic Proxy Classes - Oracle](https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html) diff --git a/dynamic-proxy/etc/dynamic-proxy-classes.png b/dynamic-proxy/etc/dynamic-proxy-classes.png deleted file mode 100644 index 86f5a7db7900..000000000000 Binary files a/dynamic-proxy/etc/dynamic-proxy-classes.png and /dev/null differ diff --git a/dynamic-proxy/etc/dynamic-proxy-classes.puml b/dynamic-proxy/etc/dynamic-proxy-classes.puml deleted file mode 100644 index 119f1dc1141c..000000000000 --- a/dynamic-proxy/etc/dynamic-proxy-classes.puml +++ /dev/null @@ -1,35 +0,0 @@ -@startuml - -class A as "App" { -} - -interface I as "MyInterface" { - someMethod(args) - otherMethods(args) -} - -class P as "Proxy" <> { - {static} DynamicProxy newProxyInstance(\n classLoader,\n interfaces,\n invocationHandler\n) -} - -class D as "DynamicProxy" { - someMethod(args) - otherMethods(args) -} - -class H as "InvocationHandler" { - invoke(proxy, method, args) -} - -class X as "AuxiliarProcessor" { - process(method, args) -} - -A -r-> D - -I <|.. D : implements - -D -r-> H : delegate -H -r-> X : (optional) - -@enduml diff --git a/dynamic-proxy/etc/dynamic-proxy-sequence.png b/dynamic-proxy/etc/dynamic-proxy-sequence.png deleted file mode 100644 index 9a139b1c8ffe..000000000000 Binary files a/dynamic-proxy/etc/dynamic-proxy-sequence.png and /dev/null differ diff --git a/dynamic-proxy/etc/dynamic-proxy-sequence.puml b/dynamic-proxy/etc/dynamic-proxy-sequence.puml deleted file mode 100644 index 76705f81e2aa..000000000000 --- a/dynamic-proxy/etc/dynamic-proxy-sequence.puml +++ /dev/null @@ -1,63 +0,0 @@ -@startuml - -participant App as A -participant Proxy as P <> -participant DynamicProxy as D <> -participant InvocationHandler as H -participant AuxiliarProcessor as R - -== Create Dynamic Proxy == - -create H -A -> H : new(args) -activate H - -create R -H -> R : new(args) -activate R - -R --> H : processor -deactivate R - -H --> A : invocationHandler -deactivate H - -||| - -A -> P : newProxyInstance(\n classLoader,\n new Class[]{MyInterface.class},\n invocationHandler\n) -activate P - -create D -P -> D : new -activate D - -D --> P : dynamicProxy -deactivate D - -P --> A : dynamicProxy -deactivate P - -== Call Interface's Method == - -A -> D : someMethod(args) -activate D - -D -> H : invoke(proxy, method, args) -activate H - -H -> R : process(method, args) -activate R - -R -> R : parse method's\nmetadata -R -> R : execute some\nprocessing - -R --> H : response -deactivate R - -H --> D : response -deactivate H - -D --> A : response -deactivate D - -@enduml diff --git a/dynamic-proxy/etc/dynamic-proxy.urm.png b/dynamic-proxy/etc/dynamic-proxy.urm.png new file mode 100644 index 000000000000..6178af2fbd20 Binary files /dev/null and b/dynamic-proxy/etc/dynamic-proxy.urm.png differ diff --git a/dynamic-proxy/pom.xml b/dynamic-proxy/pom.xml index 0ec69f0bcc6e..236723c52682 100644 --- a/dynamic-proxy/pom.xml +++ b/dynamic-proxy/pom.xml @@ -35,14 +35,28 @@ dynamic-proxy + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + com.fasterxml.jackson.core + jackson-core + 2.18.2 + com.fasterxml.jackson.core jackson-databind - 2.16.1 + 2.18.3 org.springframework spring-web + 7.0.0-M3 org.junit.jupiter diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java index 1a4efc606c96..c61223008e19 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java @@ -30,8 +30,8 @@ import lombok.NoArgsConstructor; /** - * This class represents an endpoint resource that - * we are going to interchange with a Rest API server. + * This class represents an endpoint resource that we are going to interchange with a Rest API + * server. */ @Data @AllArgsConstructor @@ -42,5 +42,4 @@ public class Album { private Integer id; private String title; private Integer userId; - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java index e53485ae06f1..84a0b22d041f 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java @@ -31,8 +31,8 @@ import lombok.extern.slf4j.Slf4j; /** - * Class whose method 'invoke' will be called every time that an interface's method is called. - * That interface is linked to this class by the Proxy class. + * Class whose method 'invoke' will be called every time that an interface's method is called. That + * interface is linked to this class by the Proxy class. */ @Slf4j public class AlbumInvocationHandler implements InvocationHandler { @@ -42,7 +42,7 @@ public class AlbumInvocationHandler implements InvocationHandler { /** * Class constructor. It instantiates a TinyRestClient object. * - * @param baseUrl Root url for endpoints. + * @param baseUrl Root url for endpoints. * @param httpClient Handle the http communication. */ public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { @@ -52,10 +52,11 @@ public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - LOGGER.info("===== Calling the method {}.{}()", - method.getDeclaringClass().getSimpleName(), method.getName()); + LOGGER.info( + "===== Calling the method {}.{}()", + method.getDeclaringClass().getSimpleName(), + method.getName()); return restClient.send(method, args); } - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java index c0975b6e6555..8b4c0d02d692 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java @@ -34,8 +34,8 @@ /** * Every method in this interface is annotated with the necessary metadata to represents an endpoint - * that we can call to communicate with a host server which is serving a resource by Rest API. - * This interface is focused in the resource Album. + * that we can call to communicate with a host server which is serving a resource by Rest API. This + * interface is focused in the resource Album. */ public interface AlbumService { @@ -69,7 +69,7 @@ public interface AlbumService { * Updates an existing album. * * @param albumId Album's id to be modified. - * @param album New album's data. + * @param album New album's data. * @return Updated album's data. */ @Put("/albums/{albumId}") @@ -83,5 +83,4 @@ public interface AlbumService { */ @Delete("/albums/{albumId}") Album deleteAlbum(@Path("albumId") Integer albumId); - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java index 61118694d652..d5a19184bf91 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java @@ -30,14 +30,14 @@ /** * Application to demonstrate the Dynamic Proxy pattern. This application allow us to hit the public - * fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. - * The call to Proxy.newProxyInstance creates a new dynamic proxy for the AlbumService interface and + * fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. The + * call to Proxy.newProxyInstance creates a new dynamic proxy for the AlbumService interface and * sets the AlbumInvocationHandler class as the handler to intercept all the interface's methods. * Everytime that we call an AlbumService's method, the handler's method "invoke" will be call * automatically, and it will pass all the method's metadata and arguments to other specialized - * class - TinyRestClient - to prepare the Rest API call accordingly. - * In this demo, the Dynamic Proxy pattern help us to run business logic through interfaces without - * an explicit implementation of the interfaces and supported on the Java Reflection approach. + * class - TinyRestClient - to prepare the Rest API call accordingly. In this demo, the Dynamic + * Proxy pattern help us to run business logic through interfaces without an explicit implementation + * of the interfaces and supported on the Java Reflection approach. */ @Slf4j public class App { @@ -51,7 +51,7 @@ public class App { /** * Class constructor. * - * @param baseUrl Root url for endpoints. + * @param baseUrl Root url for endpoints. * @param httpClient Handle the http communication. */ public App(String baseUrl, HttpClient httpClient) { @@ -71,18 +71,23 @@ public static void main(String[] args) { } /** - * Create the Dynamic Proxy linked to the AlbumService interface and to the AlbumInvocationHandler. + * Create the Dynamic Proxy linked to the AlbumService interface and to the + * AlbumInvocationHandler. */ public void createDynamicProxy() { AlbumInvocationHandler albumInvocationHandler = new AlbumInvocationHandler(baseUrl, httpClient); - albumServiceProxy = (AlbumService) Proxy.newProxyInstance( - App.class.getClassLoader(), new Class[]{AlbumService.class}, albumInvocationHandler); + albumServiceProxy = + (AlbumService) + Proxy.newProxyInstance( + App.class.getClassLoader(), + new Class[] {AlbumService.class}, + albumInvocationHandler); } /** - * Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods - * and receive the responses from the Rest API. + * Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods and + * receive the responses from the Rest API. */ public void callMethods() { int albumId = 17; @@ -94,16 +99,16 @@ public void callMethods() { var album = albumServiceProxy.readAlbum(albumId); LOGGER.info("{}", album); - var newAlbum = albumServiceProxy.createAlbum(Album.builder() - .title("Big World").userId(userId).build()); + var newAlbum = + albumServiceProxy.createAlbum(Album.builder().title("Big World").userId(userId).build()); LOGGER.info("{}", newAlbum); - var editAlbum = albumServiceProxy.updateAlbum(albumId, Album.builder() - .title("Green Valley").userId(userId).build()); + var editAlbum = + albumServiceProxy.updateAlbum( + albumId, Album.builder().title("Green Valley").userId(userId).build()); LOGGER.info("{}", editAlbum); var removedAlbum = albumServiceProxy.deleteAlbum(albumId); LOGGER.info("{}", removedAlbum); } - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java index f76fe88ec76f..d474c59da20f 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java @@ -32,22 +32,19 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -/** - * Utility class to handle Json operations. - */ +/** Utility class to handle Json operations. */ @Slf4j public class JsonUtil { private static ObjectMapper objectMapper = new ObjectMapper(); - private JsonUtil() { - } + private JsonUtil() {} /** * Convert an object to a Json string representation. * * @param object Object to convert. - * @param Object's class. + * @param Object's class. * @return Json string. */ public static String objectToJson(T object) { @@ -62,9 +59,9 @@ public static String objectToJson(T object) { /** * Convert a Json string to an object of a class. * - * @param json Json string to convert. + * @param json Json string to convert. * @param clazz Object's class. - * @param Object's generic class. + * @param Object's generic class. * @return Object. */ public static T jsonToObject(String json, Class clazz) { @@ -79,20 +76,19 @@ public static T jsonToObject(String json, Class clazz) { /** * Convert a Json string to a List of objects of a class. * - * @param json Json string to convert. + * @param json Json string to convert. * @param clazz Object's class. - * @param Object's generic class. + * @param Object's generic class. * @return List of objects. */ public static List jsonToList(String json, Class clazz) { try { - CollectionType listType = objectMapper.getTypeFactory() - .constructCollectionType(ArrayList.class, clazz); + CollectionType listType = + objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, clazz); return objectMapper.reader().forType(listType).readValue(json); } catch (JsonProcessingException e) { LOGGER.error("Cannot convert the Json " + json + " to List of " + clazz.getName() + ".", e); return List.of(); } } - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java index 854791a72873..dd952f479fa0 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java @@ -45,8 +45,8 @@ import org.springframework.web.util.UriUtils; /** - * Class to handle all the http communication with a Rest API. - * It is supported by the HttpClient Java library. + * Class to handle all the http communication with a Rest API. It is supported by the HttpClient + * Java library. */ @Slf4j public class TinyRestClient { @@ -59,7 +59,7 @@ public class TinyRestClient { /** * Class constructor. * - * @param baseUrl Root url for endpoints. + * @param baseUrl Root url for endpoints. * @param httpClient Handle the http communication. */ public TinyRestClient(String baseUrl, HttpClient httpClient) { @@ -71,9 +71,9 @@ public TinyRestClient(String baseUrl, HttpClient httpClient) { * Creates a http communication to request and receive data from an endpoint. * * @param method Interface's method which is annotated with a http method. - * @param args Method's arguments passed in the call. + * @param args Method's arguments passed in the call. * @return Response from the endpoint. - * @throws IOException Exception thrown when any fail happens in the call. + * @throws IOException Exception thrown when any fail happens in the call. * @throws InterruptedException Exception thrown when call is interrupted. */ public Object send(Method method, Object[] args) throws IOException, InterruptedException { @@ -84,11 +84,12 @@ public Object send(Method method, Object[] args) throws IOException, Interrupted var httpAnnotationName = httpAnnotation.annotationType().getSimpleName().toUpperCase(); var url = baseUrl + buildUrl(method, args, httpAnnotation); var bodyPublisher = buildBodyPublisher(method, args); - var httpRequest = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Content-Type", "application/json") - .method(httpAnnotationName, bodyPublisher) - .build(); + var httpRequest = + HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .method(httpAnnotationName, bodyPublisher) + .build(); var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); var statusCode = httpResponse.statusCode(); if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) { @@ -132,7 +133,7 @@ private HttpRequest.BodyPublisher buildBodyPublisher(Method method, Object[] arg private Object getResponse(Method method, HttpResponse httpResponse) { var rawData = httpResponse.body(); - Type returnType = null; + Type returnType; try { returnType = method.getGenericReturnType(); } catch (Exception e) { @@ -140,8 +141,8 @@ private Object getResponse(Method method, HttpResponse httpResponse) { return null; } if (returnType instanceof ParameterizedType) { - Class responseClass = (Class) (((ParameterizedType) returnType) - .getActualTypeArguments()[0]); + Class responseClass = + (Class) (((ParameterizedType) returnType).getActualTypeArguments()[0]); return JsonUtil.jsonToList(rawData, responseClass); } else { Class responseClass = method.getReturnType(); @@ -150,22 +151,28 @@ private Object getResponse(Method method, HttpResponse httpResponse) { } private Annotation getHttpAnnotation(Method method) { - return httpAnnotationByMethod.computeIfAbsent(method, m -> - Arrays.stream(m.getDeclaredAnnotations()) - .filter(annot -> annot.annotationType().isAnnotationPresent(Http.class)) - .findFirst().orElse(null)); + return httpAnnotationByMethod.computeIfAbsent( + method, + m -> + Arrays.stream(m.getDeclaredAnnotations()) + .filter(annot -> annot.annotationType().isAnnotationPresent(Http.class)) + .findFirst() + .orElse(null)); } private Annotation getAnnotationOf(Annotation[] annotations, Class clazz) { return Arrays.stream(annotations) .filter(annot -> annot.annotationType().equals(clazz)) - .findFirst().orElse(null); + .findFirst() + .orElse(null); } private String annotationValue(Annotation annotation) { - var valueMethod = Arrays.stream(annotation.annotationType().getDeclaredMethods()) - .filter(methodAnnot -> methodAnnot.getName().equals("value")) - .findFirst().orElse(null); + var valueMethod = + Arrays.stream(annotation.annotationType().getDeclaredMethods()) + .filter(methodAnnot -> methodAnnot.getName().equals("value")) + .findFirst() + .orElse(null); if (valueMethod == null) { return null; } @@ -173,8 +180,13 @@ private String annotationValue(Annotation annotation) { try { result = valueMethod.invoke(annotation, (Object[]) null); } catch (Exception e) { - LOGGER.error("Cannot read the value " + annotation.annotationType().getSimpleName() - + "." + valueMethod.getName() + "()", e); + LOGGER.error( + "Cannot read the value " + + annotation.annotationType().getSimpleName() + + "." + + valueMethod.getName() + + "()", + e); result = null; } return (result instanceof String strResult ? strResult : null); diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java index 1d91c0af1acf..df89731936ba 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java @@ -30,10 +30,9 @@ import java.lang.annotation.Target; /** - * Annotation to mark a method's parameter as a Body parameter. - * It is typically used on Post and Put http methods. + * Annotation to mark a method's parameter as a Body parameter. It is typically used on Post and Put + * http methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) -public @interface Body { -} +public @interface Body {} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java index 5f21457abc36..a43df905798f 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a DELETE http method. - */ +/** Annotation to mark an interface's method as a DELETE http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java index 163a65ad9a65..74f96ac062ec 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a GET http method. - */ +/** Annotation to mark an interface's method as a GET http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java index beabcdb69aa7..f12878de2d90 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java @@ -29,10 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark other annotations to be recognized as http methods. - */ +/** Annotation to mark other annotations to be recognized as http methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) -public @interface Http { -} +public @interface Http {} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java index 0f2bcd007b94..4ff1faea4844 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark a method's parameter as a Path parameter. - */ +/** Annotation to mark a method's parameter as a Path parameter. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Path { diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java index 9885389c4c75..f10f38c83984 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a POST http method. - */ +/** Annotation to mark an interface's method as a POST http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java index 851303ed8182..9af9b27c4dde 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a PUT http method. - */ +/** Annotation to mark an interface's method as a PUT http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java b/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java index 72412ca5b976..107255695344 100644 --- a/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java +++ b/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.dynamicproxy; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { @Test void shouldRunAppWithoutExceptions() { - assertDoesNotThrow(() -> App.main(null)); + assertDoesNotThrow(() -> App.main(null)); } } diff --git a/embedded-value/README.md b/embedded-value/README.md deleted file mode 100644 index dc924cb45a0f..000000000000 --- a/embedded-value/README.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: Embedded Value -category: Structural -language: en -tag: - - Data Access - - Enterprise Application Pattern ---- - -## Also known as -Aggregate Mapping, Composer - -## Intent -Many small objects make sense in an OO system that don’t make sense as -tables in a database. An Embedded Value maps the values of an object to fields in the record of the object’s owner. - -## Explanation - -Real-world example - -> Examples include currency-aware money objects and date -ranges. Although the default thinking is to save an object as a table, no sane person would want a table of money values. -> Another example would be the online orders which have a shipping address like street, city, state. We map these values of Shipping address object to fields in record of Order object. - -In plain words - -> Embedded value pattern let's you map an object into several fields of another object’s table. - -**Programmatic Example** - -Consider online order's example where we have details of item ordered and shipping address. We have Shipping address embedded in Order object. But in database we map shipping address values in Order record instead of creating a separate table for Shipping address and using foreign key to reference the order object. - -First, we have POJOs `Order` and `ShippingAddress` - -```java -public class Order { - - private int id; - private String item; - private String orderedBy; - private ShippingAddress ShippingAddress; - - public Order(String item, String orderedBy, ShippingAddress ShippingAddress) { - this.item = item; - this.orderedBy = orderedBy; - this.ShippingAddress = ShippingAddress; - } -``` -```java -public class ShippingAddress { - - private String city; - private String state; - private String pincode; - - public ShippingAddress(String city, String state, String pincode) { - this.city = city; - this.state = state; - this.pincode = pincode; - } -} -``` -Now, we have to create only one table for Order along with fields for shipping address attributes. - -```Sql -CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50) city VARCHAR(50), state VARCHAR(50), pincode CHAR(6) NOT NULL, PRIMARY KEY(Id)) -``` - -While performing the database queries and inserts, we box and unbox shipping address details. - -```java -final String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES (?, ?, ?, ?, ?)"; - -public boolean insertOrder(Order order) throws Exception { - var insertOrder = new PreparedStatement(INSERT_ORDER); - var address = order.getShippingAddress(); - conn.setAutoCommit(false); - insertIntoOrders.setString(1, order.getItem()); - insertIntoOrders.setString(2, order.getOrderedBy()); - insertIntoOrders.setString(3, address.getCity()); - insertIntoOrders.setString(4, address.getState()); - insertIntoOrders.setString(5, address.getPincode()); - - var affectedRows = insertIntoOrders.executeUpdate(); - if(affectedRows == 1){ - Logger.info("Inserted successfully"); - }else{ - Logger.info("Couldn't insert " + order); - } -} -``` - -## Class diagram -![alt text](./etc/embedded-value.urm.png "Embedded value class diagram") - -## Applicability - -Use the Embedded value pattern when - -* Many small objects make sense in an OO system that don’t make sense as tables in a database. -* The simplest cases for Embedded Value are the clear, simple Value Objects like money and date range. -* If you’re mapping to an existing schema, you can use this pattern when a table contains data that you want to split into more than one object in memory. This can occur when you want to factor out some behaviour in object model. -* In most cases you’ll only use Embedded Value on a reference object when the association between them is single valued at both ends (a one-to-one association). -* It can only be used for fairly simple dependents. A solitary dependent, or a few separated dependents, works well. - -## Tutorials - -* [Dzone](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-3) -* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) -* [Five's Weblog](https://powerdream5.wordpress.com/2007/10/09/embedded-value/) - -## Consequences - -* The great advantage of Embedded Value is that it allows SQL queries to be made against the values in the dependent object. -* The embedded value object has no persistence behaviour at all. -* While using this, you have to be careful that any change to the dependent marks the owner as dirty—which isn’t an issue with Value Objects that are replaced in the owner. -* Another issue is the loading and saving. If you only load the embedded object memory when you load the owner, that’s an argument for saving both in the same table. -* Another question is whether you’ll want to access the embedded objects' data separately through SQL. This can be important if you’re reporting through SQL and don’t have a separate database for reporting. - - -## Credits - -* [Fowler, Martin - Patterns of enterprise application architecture-Addison-Wesley](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) -* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) \ No newline at end of file diff --git a/embedded-value/etc/embedded-value.urm.puml b/embedded-value/etc/embedded-value.urm.puml deleted file mode 100644 index ed73802792a2..000000000000 --- a/embedded-value/etc/embedded-value.urm.puml +++ /dev/null @@ -1,79 +0,0 @@ -@startuml -package com.iluwatar.embedded.value { - class App { - - LOGGER : Logger {static} - + App() - + main(args : String[]) {static} - } - class DataSource { - - LOGGER : Logger {static} - - conn : Connection - - createschema : Statement - - deleteschema : Statement - - getschema : Statement - - insertIntoOrders : PreparedStatement - - queryOrders : Statement - - queyOrderByID : PreparedStatement - - removeorder : PreparedStatement - + DataSource() - + createSchema() : boolean - + deleteSchema() : boolean - + getSchema() : String - + insertOrder(order : Order) : boolean - + queryOrder(id : int) : Order - + queryOrders() : Stream - + removeOrder(id : int) - } - ~interface DataSourceInterface { - + CREATE_SCHEMA : String {static} - + DELETE_SCHEMA : String {static} - + GET_SCHEMA : String {static} - + INSERT_ORDER : String {static} - + JDBC_URL : String {static} - + QUERY_ORDER : String {static} - + QUERY_ORDERS : String {static} - + REMOVE_ORDER : String {static} - + createSchema() : boolean {abstract} - + deleteSchema() : boolean {abstract} - + getSchema() : String {abstract} - + insertOrder(Order) : boolean {abstract} - + queryOrder(int) : Order {abstract} - + queryOrders() : Stream {abstract} - + removeOrder(int) {abstract} - } - class Order { - - id : int - - item : String - - orderedBy : String - - shippingAddress : ShippingAddress - + Order() - + Order(id : int, item : String, orderedBy : String, shippingAddress : ShippingAddress) - + Order(item : String, orderedBy : String, shippingAddress : ShippingAddress) - + getId() : int - + getItem() : String - + getOrderedBy() : String - + getShippingAddress() : ShippingAddress - + setId(id : int) - + setItem(item : String) - + setOrderedBy(orderedBy : String) - + setShippingAddress(shippingAddress : ShippingAddress) - + toString() : String - } - class ShippingAddress { - - city : String - - pincode : String - - state : String - + ShippingAddress() - + ShippingAddress(city : String, state : String, pincode : String) - + getCity() : String - + getPincode() : String - + getState() : String - + setCity(city : String) - + setPincode(pincode : String) - + setState(state : String) - + toString() : String - } -} -Order --> "-shippingAddress" ShippingAddress -DataSource ..|> DataSourceInterface -@enduml \ No newline at end of file diff --git a/embedded-value/src/main/java/com/iluwatar/embedded/value/App.java b/embedded-value/src/main/java/com/iluwatar/embedded/value/App.java deleted file mode 100644 index 07dbacd09702..000000000000 --- a/embedded-value/src/main/java/com/iluwatar/embedded/value/App.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.embedded.value; - -import lombok.extern.slf4j.Slf4j; - -/** - *

Many small objects make sense in an OO system that don’t make sense as - * tables in a database. Examples include currency-aware money objects (amount, currency) and date - * ranges. Although the default thinking is to save an object as a table, no sane - * person would want a table of money values.

- * - *

An Embedded Value maps the values of an object to fields in the record of - * the object’s owner. In this implementation we have an Order object with links to an - * ShippingAddress object. In the resulting table the fields in the ShippingAddress - * object map to fields in the Order table rather than make new records - * themselves.

- */ -@Slf4j -public class App { - - /** - * Program entry point. - * - * @param args command line args. - * @throws Exception if any error occurs. - * - */ - public static void main(String[] args) throws Exception { - final var dataSource = new DataSource(); - - // Orders to insert into database - final var order1 = new Order("JBL headphone", "Ram", - new ShippingAddress("Bangalore", "Karnataka", "560040")); - final var order2 = new Order("MacBook Pro", "Manjunath", - new ShippingAddress("Bangalore", "Karnataka", "581204")); - final var order3 = new Order("Carrie Soto is Back", "Shiva", - new ShippingAddress("Bangalore", "Karnataka", "560004")); - - // Create table for orders - Orders(id, name, orderedBy, city, state, pincode). - // We can see that table is different from the Order object we have. - // We're mapping ShippingAddress into city, state, pincode columns of the database and not creating a separate table. - if (dataSource.createSchema()) { - LOGGER.info("TABLE CREATED"); - LOGGER.info("Table \"Orders\" schema:\n" + dataSource.getSchema()); - } else { - //If not able to create table, there's nothing we can do further. - LOGGER.error("Error creating table"); - System.exit(0); - } - - // Initially, database is empty - LOGGER.info("Orders Query: {}", dataSource.queryOrders().toList()); - - //Insert orders where shippingAddress is mapped to different columns of the same table - dataSource.insertOrder(order1); - dataSource.insertOrder(order2); - dataSource.insertOrder(order3); - - - // Query orders. - // We'll create ShippingAddress object from city, state, pincode values from the table and add it to Order object - LOGGER.info("Orders Query: {}", dataSource.queryOrders().toList() + "\n"); - - //Query order by given id - LOGGER.info("Query Order with id=2: {}", dataSource.queryOrder(2)); - - - //Remove order by given id. - //Since we'd mapped address in the same table, deleting order will also take out the shipping address details. - LOGGER.info("Remove Order with id=1"); - dataSource.removeOrder(1); - LOGGER.info("\nOrders Query: {}", dataSource.queryOrders().toList() + "\n"); - - //After successful demonstration of the pattern, drop the table - if (dataSource.deleteSchema()) { - LOGGER.info("TABLE DROPPED"); - } else { - //If there's a potential error while dropping table - LOGGER.error("Error deleting table"); - } - } -} diff --git a/embedded-value/src/main/java/com/iluwatar/embedded/value/DataSource.java b/embedded-value/src/main/java/com/iluwatar/embedded/value/DataSource.java deleted file mode 100644 index 5704dff8f62a..000000000000 --- a/embedded-value/src/main/java/com/iluwatar/embedded/value/DataSource.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.embedded.value; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.stream.Stream; -import lombok.extern.slf4j.Slf4j; - -/** - * Communicates with H2 database with the help of JDBC API. - * Inherits the SQL queries and methods from @link AbstractDataSource class. - */ -@Slf4j -public class DataSource implements DataSourceInterface { - private Connection conn; - - // Statements are objects which are used to execute queries which will not be repeated. - private Statement getschema; - private Statement deleteschema; - private Statement queryOrders; - - // PreparedStatements are used to execute queries which will be repeated. - private PreparedStatement insertIntoOrders; - private PreparedStatement removeorder; - private PreparedStatement queyOrderById; - - /** - * {@summary Establish connection to database. - * Constructor to create DataSource object.} - */ - public DataSource() { - try { - conn = DriverManager.getConnection(JDBC_URL); - LOGGER.info("Connected to H2 in-memory database: " + conn.getCatalog()); - } catch (SQLException e) { - LOGGER.error(e.getLocalizedMessage(), e.getCause()); - } - } - - @Override - public boolean createSchema() { - try (Statement createschema = conn.createStatement()) { - createschema.execute(CREATE_SCHEMA); - insertIntoOrders = conn.prepareStatement(INSERT_ORDER, PreparedStatement.RETURN_GENERATED_KEYS); - getschema = conn.createStatement(); - queryOrders = conn.createStatement(); - removeorder = conn.prepareStatement(REMOVE_ORDER); - queyOrderById = conn.prepareStatement(QUERY_ORDER); - deleteschema = conn.createStatement(); - } catch (SQLException e) { - LOGGER.error(e.getLocalizedMessage(), e.getCause()); - return false; - } - return true; - } - - @Override - public String getSchema() { - try { - var resultSet = getschema.executeQuery(GET_SCHEMA); - var sb = new StringBuilder(); - while (resultSet.next()) { - sb.append("Col name: ").append(resultSet.getString(1)).append(", Col type: ").append(resultSet.getString(2)).append("\n"); - } - getschema.close(); - return sb.toString(); - } catch (Exception e) { - LOGGER.error("Error in retrieving schema: {}", e.getLocalizedMessage(), e.getCause()); - } - return "Schema unavailable"; - } - - @Override - public boolean insertOrder(Order order) { - try { - conn.setAutoCommit(false); - insertIntoOrders.setString(1, order.getItem()); - insertIntoOrders.setString(2, order.getOrderedBy()); - var address = order.getShippingAddress(); - insertIntoOrders.setString(3, address.getCity()); - insertIntoOrders.setString(4, address.getState()); - insertIntoOrders.setString(5, address.getPincode()); - var affectedRows = insertIntoOrders.executeUpdate(); - if (affectedRows == 1) { - var rs = insertIntoOrders.getGeneratedKeys(); - rs.last(); - var insertedAddress = new ShippingAddress(address.getCity(), address.getState(), address.getPincode()); - var insertedOrder = new Order(rs.getInt(1), order.getItem(), order.getOrderedBy(), - insertedAddress); - conn.commit(); - LOGGER.info("Inserted: {}", insertedOrder); - } else { - conn.rollback(); - } - } catch (Exception e) { - LOGGER.error(e.getLocalizedMessage()); - } finally { - try { - conn.setAutoCommit(true); - } catch (SQLException e) { - LOGGER.error(e.getLocalizedMessage()); - } - } - return true; - } - - @Override - public Stream queryOrders() { - var ordersList = new ArrayList(); - try (var rSet = queryOrders.executeQuery(QUERY_ORDERS)) { - while (rSet.next()) { - var order = new Order(rSet.getInt(1), rSet.getString(2), rSet.getString(3), - new ShippingAddress(rSet.getString(4), rSet.getString(5), - rSet.getString(6))); - ordersList.add(order); - } - rSet.close(); - } catch (SQLException e) { - LOGGER.error(e.getMessage(), e.getCause()); - } - return ordersList.stream(); - } - - /** - * Query order by given id. - * @param id as the parameter - * @return Order objct - * @throws SQLException in case of unexpected events - */ - - @Override - public Order queryOrder(int id) throws SQLException { - Order order = null; - queyOrderById.setInt(1, id); - try (var rSet = queyOrderById.executeQuery()) { - queyOrderById.setInt(1, id); - if (rSet.next()) { - var address = new ShippingAddress(rSet.getString(4), - rSet.getString(5), rSet.getString(6)); - order = new Order(rSet.getInt(1), rSet.getString(2), rSet.getString(3), address); - } - rSet.close(); - } catch (Exception e) { - LOGGER.error(e.getLocalizedMessage(), e.getCause()); - } - return order; - } - - @Override - public void removeOrder(int id) throws Exception { - try { - conn.setAutoCommit(false); - removeorder.setInt(1, id); - if (removeorder.executeUpdate() == 1) { - LOGGER.info("Order with id " + id + " successfully removed"); - } else { - LOGGER.info("Order with id " + id + " unavailable."); - } - } catch (Exception e) { - LOGGER.error(e.getLocalizedMessage(), e.getCause()); - conn.rollback(); - } finally { - conn.setAutoCommit(true); - } - } - - @Override - public boolean deleteSchema() throws Exception { - try { - deleteschema.execute(DELETE_SCHEMA); - queryOrders.close(); - queyOrderById.close(); - deleteschema.close(); - insertIntoOrders.close(); - conn.close(); - return true; - } catch (Exception e) { - LOGGER.error(e.getLocalizedMessage(), e.getCause()); - } - return false; - } -} diff --git a/embedded-value/src/main/java/com/iluwatar/embedded/value/DataSourceInterface.java b/embedded-value/src/main/java/com/iluwatar/embedded/value/DataSourceInterface.java deleted file mode 100644 index deddf55b93ac..000000000000 --- a/embedded-value/src/main/java/com/iluwatar/embedded/value/DataSourceInterface.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.embedded.value; - -import java.sql.SQLException; -import java.util.stream.Stream; - -/* - * Abstract class which contains the required SQL queries and basic methods declaration. - * - * The main thing to consider is that the ShippingAddress object doesn't have it's own class - * but it's values are stored into the Orders table as city, state, pincode - * - */ -interface DataSourceInterface { - - final String JDBC_URL = "jdbc:h2:mem:Embedded-Value"; - - final String CREATE_SCHEMA = "CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50)" - + ", city VARCHAR(50), state VARCHAR(50), pincode CHAR(6) NOT NULL, PRIMARY KEY(Id))"; - - final String GET_SCHEMA = "SHOW COLUMNS FROM Orders"; - - final String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES(?, ?, ?, ?, ?)"; - - final String QUERY_ORDERS = "SELECT * FROM Orders"; - - final String QUERY_ORDER = QUERY_ORDERS + " WHERE Id = ?"; - - final String REMOVE_ORDER = "DELETE FROM Orders WHERE Id = ?"; - - final String DELETE_SCHEMA = "DROP TABLE Orders"; - - boolean createSchema() throws SQLException; - - String getSchema() throws SQLException; - - boolean insertOrder(Order order) throws SQLException; - - Stream queryOrders() throws SQLException; - - Order queryOrder(int id) throws SQLException; - - void removeOrder(int id) throws Exception; - - boolean deleteSchema() throws Exception; -} diff --git a/event-aggregator/README.md b/event-aggregator/README.md index 9e4c7acd46f8..a052e87c462f 100644 --- a/event-aggregator/README.md +++ b/event-aggregator/README.md @@ -1,114 +1,126 @@ --- -title: Event Aggregator -category: Structural +title: "Event Aggregator Pattern in Java: Centralizing Event Management in Large Applications" +shortTitle: Event Aggregator +description: "Explore the Event Aggregator design pattern with our in-depth guide. Learn how to implement it effectively with examples and improve your Java applications. Perfect for developers seeking to enhance their design pattern knowledge." +category: Messaging language: en tag: - - Reactive + - Decoupling + - Event-driven + - Messaging + - Publish/subscribe + - Reactive +head: + - - meta + - name: keywords + content: --- -## Name +## Also known as -Event Aggregator +* Event Channel +* Event Central +* Message Hub -## Intent -A system with lots of objects can lead to complexities when a -client wants to subscribe to events. The client has to find and register for -each object individually, if each object has multiple events then each event -requires a separate subscription. An Event Aggregator acts as a single source -of events for many objects. It registers for all the events of the many objects -allowing clients to register with just the aggregator. +## Intent of Event Aggregator Design Pattern -## Explanation +An Event Aggregator is a design pattern used for handling events in a system. It centralizes the event handling logic, making it easier to manage and maintain. The Event Aggregator design pattern aims to decouple event generation from event handling. This design pattern collects events from multiple sources and routes them to the appropriate handlers. + +## Detailed Explanation of Event Aggregator Pattern with Real-World Examples Real-world example -> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most -> of his critical information from King's Hand, the second in command. King's hand has many -> close advisors himself, feeding him with relevant information about events occurring in the -> kingdom. +> The Event Aggregator pattern is often compared to a hub in a wheel. In this analogy, the Event Aggregator is the hub, and the spokes are the event sources. The hub collects events from all the spokes and then distributes them to the appropriate handlers. In Plain Words -> Event Aggregator is an event mediator that collects events from multiple sources and delivers -> them to registered observers. +> Event Aggregator is a design pattern that allows multiple event sources to communicate with event handlers through a central point, rather than having each event source communicate directly with each handler. + +## Programmatic Example of Event Aggregator Pattern in Java -**Programmatic Example** +Consider the following example where we use the Event Aggregator to handle multiple events. -In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of -the objects are event listeners, some are event emitters, and the event aggregator does both. +King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most of his critical information from King's Hand, the second in command. King's hand has many close advisors himself, feeding him with relevant information about events occurring in the kingdom. + +In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of the objects are event listeners, some are event emitters, and the event aggregator does both. ```java public interface EventObserver { - void onEvent(Event e); + void onEvent(Event e); } +``` +```java public abstract class EventEmitter { - private final Map> observerLists; + private final Map> observerLists; - public EventEmitter() { - observerLists = new HashMap<>(); - } + public EventEmitter() { + observerLists = new HashMap<>(); + } - public final void registerObserver(EventObserver obs, Event e) { - ... - } + public final void registerObserver(EventObserver obs, Event e) { + // implementation omitted + } - protected void notifyObservers(Event e) { - ... - } + protected void notifyObservers(Event e) { + // implementation omitted + } } ``` `KingJoffrey` is listening to events from `KingsHand`. ```java + @Slf4j public class KingJoffrey implements EventObserver { - @Override - public void onEvent(Event e) { - LOGGER.info("Received event from the King's Hand: {}", e.toString()); - } + @Override + public void onEvent(Event e) { + LOGGER.info("Received event from the King's Hand: {}", e.toString()); + } } ``` -`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`. -Whatever he hears from them, he delivers to `KingJoffrey`. +`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`. Whatever he hears from them, he delivers to `KingJoffrey`. ```java public class KingsHand extends EventEmitter implements EventObserver { - public KingsHand() { - } + public KingsHand() { + } - public KingsHand(EventObserver obs, Event e) { - super(obs, e); - } + public KingsHand(EventObserver obs, Event e) { + super(obs, e); + } - @Override - public void onEvent(Event e) { - notifyObservers(e); - } + @Override + public void onEvent(Event e) { + notifyObservers(e); + } } ``` For example, `LordVarys` finds a traitor every Sunday and notifies the `KingsHand`. ```java + @Slf4j public class LordVarys extends EventEmitter implements EventObserver { - @Override - public void timePasses(Weekday day) { - if (day == Weekday.SATURDAY) { - notifyObservers(Event.TRAITOR_DETECTED); + @Override + public void timePasses(Weekday day) { + if (day == Weekday.SATURDAY) { + notifyObservers(Event.TRAITOR_DETECTED); + } } - } } ``` The following snippet demonstrates how the objects are constructed and wired together. ```java +public static void main(String[] args) { + var kingJoffrey = new KingJoffrey(); var kingsHand = new KingsHand(); @@ -128,42 +140,67 @@ The following snippet demonstrates how the objects are constructed and wired tog var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); var emitters = List.of( - kingsHand, - baelish, - varys, - scout + kingsHand, + baelish, + varys, + scout ); Arrays.stream(Weekday.values()) - .>map(day -> emitter -> emitter.timePasses(day)) - .forEachOrdered(emitters::forEach); + .>map(day -> emitter -> emitter.timePasses(day)) + .forEachOrdered(emitters::forEach); +} ``` The console output after running the example. ``` -18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching -18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted -18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted -18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected +21:37:38.737 [main] INFO com.iluwatar.event.aggregator.KingJoffrey -- Received event from the King's Hand: Warships approaching +21:37:38.739 [main] INFO com.iluwatar.event.aggregator.KingJoffrey -- Received event from the King's Hand: White walkers sighted +21:37:38.739 [main] INFO com.iluwatar.event.aggregator.KingJoffrey -- Received event from the King's Hand: Stark sighted +21:37:38.739 [main] INFO com.iluwatar.event.aggregator.KingJoffrey -- Received event from the King's Hand: Traitor detected ``` -## Class diagram -![alt text](./etc/classes.png "Event Aggregator") +## Detailed Explanation of Event Aggregator Pattern with Real-World Examples + +![Event Aggregator](./etc/classes.png "Event Aggregator") + +## When to Use the Event Aggregator Pattern in Java -## Applicability Use the Event Aggregator pattern when -* Event Aggregator is a good choice when you have lots of objects that are - potential event sources. Rather than have the observer deal with registering - with them all, you can centralize the registration logic to the Event - Aggregator. As well as simplifying registration, an Event Aggregator also - simplifies the memory management issues in using observers. +* You have multiple event sources and handlers. +* You want to decouple the event generation and handling logic. +* You need a centralized event management system. + +## Real-World Applications of Event Aggregator Pattern in Java + +* Enterprise application integrations where systems need a central point to handle events generated by various subsystems. +* Complex GUI applications where user actions in one part of the interface need to affect other parts without tight coupling between the components. + +## Benefits and Trade-offs of Event Aggregator Pattern + +Benefits: + +* Decoupling: By centralizing event handling, the Event Aggregator minimizes direct interaction between components, leading to a more modular and easier-to-manage system. +* Improves Flexibility and Scalability: Adding new publishers or subscribers involves less effort since the central aggregator handles all routing. +* Simplifies Component Interface: Components need to know only about the Event Aggregator, not about other components. +* Centralizes event management: Makes the system easier to maintain. + +Trade-offs: + +* Complexity of the Aggregator: The Event Aggregator itself can become a complex and high-maintenance component if not properly designed. +* Potential Performance Bottleneck: If not scaled properly, the central event handling mechanism can become a bottleneck in the system. -## Related patterns +## Related Java Design Patterns -* [Observer](https://java-design-patterns.com/patterns/observer/) +* [Mediator](https://java-design-patterns.com/patterns/mediator/): Similar to Mediator in that it abstracts direct communications between components, but focused specifically on event messages. +* [Observer](https://java-design-patterns.com/patterns/observer/): The Event Aggregator pattern is often implemented using the Observer pattern, where the aggregator observes events and notifies subscribers. +* Publish-Subscribe: The Event Aggregator can be seen as a special case of the Publish-Subscribe pattern, with the aggregator acting as the broker. -## Credits +## References and Credits -* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/44eWKXv) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/440b0CZ) +* [Java Design Pattern Essentials](https://amzn.to/43XHCgM) +* [Event Aggregator (Martin Fowler)](http://martinfowler.com/eaaDev/EventAggregator.html) diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index 2bf8797f4855..3e7e58120155 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -34,6 +34,14 @@ event-aggregator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java index 60a6381c9e12..4b5e9a615cbf 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java @@ -67,12 +67,7 @@ public static void main(String[] args) { var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); - var emitters = List.of( - kingsHand, - baelish, - varys, - scout - ); + var emitters = List.of(kingsHand, baelish, varys, scout); Arrays.stream(Weekday.values()) .>map(day -> emitter -> emitter.timePasses(day)) diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java index dd839410cdc8..87b1f5eafa22 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Event enumeration. - */ +/** Event enumeration. */ @RequiredArgsConstructor public enum Event { - WHITE_WALKERS_SIGHTED("White walkers sighted"), STARK_SIGHTED("Stark sighted"), WARSHIPS_APPROACHING("Warships approaching"), diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java index 60bc056393d6..ccff62740db7 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java @@ -29,9 +29,7 @@ import java.util.List; import java.util.Map; -/** - * EventEmitter is the base class for event producers that can be observed. - */ +/** EventEmitter is the base class for event producers that can be observed. */ public abstract class EventEmitter { private final Map> observerLists; @@ -46,11 +44,11 @@ public EventEmitter(EventObserver obs, Event e) { } /** - * Registers observer for specific event in the related list. - * - * @param obs the observer that observers this emitter - * @param e the specific event for that observation occurs - * */ + * Registers observer for specific event in the related list. + * + * @param obs the observer that observers this emitter + * @param e the specific event for that observation occurs + */ public final void registerObserver(EventObserver obs, Event e) { if (!observerLists.containsKey(e)) { observerLists.put(e, new LinkedList<>()); @@ -62,9 +60,7 @@ public final void registerObserver(EventObserver obs, Event e) { protected void notifyObservers(Event e) { if (observerLists.containsKey(e)) { - observerLists - .get(e) - .forEach(observer -> observer.onEvent(e)); + observerLists.get(e).forEach(observer -> observer.onEvent(e)); } } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java index 631e24cb8c1f..2db98f93982b 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java @@ -24,11 +24,8 @@ */ package com.iluwatar.event.aggregator; -/** - * Observers of events implement this interface. - */ +/** Observers of events implement this interface. */ public interface EventObserver { void onEvent(Event e); - } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java index 6273fdd599eb..90036f664ab7 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * KingJoffrey observes events from {@link KingsHand}. - */ +/** KingJoffrey observes events from {@link KingsHand}. */ @Slf4j public class KingJoffrey implements EventObserver { diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java index 38bb04ea45cb..c9089a68fdda 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java @@ -24,13 +24,10 @@ */ package com.iluwatar.event.aggregator; -/** - * KingsHand observes events from multiple sources and delivers them to listeners. - */ +/** KingsHand observes events from multiple sources and delivers them to listeners. */ public class KingsHand extends EventEmitter implements EventObserver { - public KingsHand() { - } + public KingsHand() {} public KingsHand(EventObserver obs, Event e) { super(obs, e); @@ -43,5 +40,8 @@ public void onEvent(Event e) { @Override public void timePasses(Weekday day) { + // This method is intentionally left empty because KingsHand does not handle time-based events + // directly. + // It serves as a placeholder to fulfill the EventObserver interface contract. } } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java index 00033805ff0b..f133a774d89a 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java @@ -24,13 +24,10 @@ */ package com.iluwatar.event.aggregator; -/** - * LordBaelish produces events. - */ +/** LordBaelish produces events. */ public class LordBaelish extends EventEmitter { - public LordBaelish() { - } + public LordBaelish() {} public LordBaelish(EventObserver obs, Event e) { super(obs, e); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java index 883560bc7170..5472e2288496 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java @@ -26,14 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * LordVarys produces events. - */ +/** LordVarys produces events. */ @Slf4j public class LordVarys extends EventEmitter implements EventObserver { - public LordVarys() { - } + public LordVarys() {} public LordVarys(EventObserver obs, Event e) { super(obs, e); @@ -46,7 +43,6 @@ public void timePasses(Weekday day) { } } - @Override public void onEvent(Event e) { notifyObservers(e); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java index ef983585e59b..d1566571a465 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java @@ -24,13 +24,10 @@ */ package com.iluwatar.event.aggregator; -/** - * Scout produces events. - */ +/** Scout produces events. */ public class Scout extends EventEmitter { - public Scout() { - } + public Scout() {} public Scout(EventObserver obs, Event e) { super(obs, e); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java index 6dbbd75c95fb..b232ad54b563 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Weekday enumeration. - */ +/** Weekday enumeration. */ @RequiredArgsConstructor public enum Weekday { - MONDAY("Monday"), TUESDAY("Tuesday"), WEDNESDAY("Wednesday"), diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java index 7aa83bf641b6..2ccd559a832e 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.event.aggregator; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java index cdaa606e9578..bfc00e6cd88a 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java @@ -36,39 +36,32 @@ import org.junit.jupiter.api.Test; /** - * Date: 12/12/15 - 10:58 PM Tests for Event Emitter + * Tests for Event Emitter * * @param Type of Event Emitter - * @author Jeroen Meulemeester */ abstract class EventEmitterTest { - /** - * Factory used to create a new instance of the test object with a default observer - */ + /** Factory used to create a new instance of the test object with a default observer */ private final BiFunction factoryWithDefaultObserver; - /** - * Factory used to create a new instance of the test object without passing a default observer - */ + /** Factory used to create a new instance of the test object without passing a default observer */ private final Supplier factoryWithoutDefaultObserver; - /** - * The day of the week an event is expected - */ + /** The day of the week an event is expected */ private final Weekday specialDay; - /** - * The expected event, emitted on the special day - */ + /** The expected event, emitted on the special day */ private final Event event; /** * Create a new event emitter test, using the given test object factories, special day and event */ - EventEmitterTest(final Weekday specialDay, final Event event, - final BiFunction factoryWithDefaultObserver, - final Supplier factoryWithoutDefaultObserver) { + EventEmitterTest( + final Weekday specialDay, + final Event event, + final BiFunction factoryWithDefaultObserver, + final Supplier factoryWithoutDefaultObserver) { this.specialDay = specialDay; this.event = event; @@ -91,12 +84,15 @@ void testAllDays() { * received the correct event on the special day. * * @param specialDay The special day on which an event is emitted - * @param event The expected event emitted by the test object - * @param emitter The event emitter - * @param observers The registered observer mocks + * @param event The expected event emitted by the test object + * @param emitter The event emitter + * @param observers The registered observer mocks */ - private void testAllDays(final Weekday specialDay, final Event event, final E emitter, - final EventObserver... observers) { + private void testAllDays( + final Weekday specialDay, + final Event event, + final E emitter, + final EventObserver... observers) { for (final var weekday : Weekday.values()) { // Pass each week of the day, day by day to the event emitter @@ -122,7 +118,7 @@ private void testAllDays(final Weekday specialDay, final Event event, final E em * event emitter without a default observer * * @param specialDay The special day on which an event is emitted - * @param event The expected event emitted by the test object + * @param event The expected event emitted by the test object */ private void testAllDaysWithoutDefaultObserver(final Weekday specialDay, final Event event) { final var observer1 = mock(EventObserver.class); @@ -139,7 +135,7 @@ private void testAllDaysWithoutDefaultObserver(final Weekday specialDay, final E * Go over every day of the month, and check if the event is emitted on the given day. * * @param specialDay The special day on which an event is emitted - * @param event The expected event emitted by the test object + * @param event The expected event emitted by the test object */ private void testAllDaysWithDefaultObserver(final Weekday specialDay, final Event event) { final var defaultObserver = mock(EventObserver.class); @@ -152,5 +148,4 @@ private void testAllDaysWithDefaultObserver(final Weekday specialDay, final Even testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2); } - } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java index 73bbe5914240..20c3c489925f 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java @@ -30,22 +30,18 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 2:52 PM - * - * @author Jeroen Meulemeester - */ +/** EventTest */ class EventTest { - /** - * Verify if every event has a non-null, non-empty description - */ + /** Verify if every event has a non-null, non-empty description */ @Test void testToString() { - Arrays.stream(Event.values()).map(Event::toString).forEach(toString -> { - assertNotNull(toString); - assertFalse(toString.trim().isEmpty()); - }); + Arrays.stream(Event.values()) + .map(Event::toString) + .forEach( + toString -> { + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + }); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java index d7bebbb8ebc2..aa689a34e9c5 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java @@ -37,11 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/12/15 - 3:04 PM - * - * @author Jeroen Meulemeester - */ +/** KingJoffreyTest */ class KingJoffreyTest { private InMemoryAppender appender; @@ -56,25 +52,24 @@ void tearDown() { appender.stop(); } - /** - * Test if {@link KingJoffrey} tells us what event he received - */ + /** Test if {@link KingJoffrey} tells us what event he received */ @Test void testOnEvent() { final var kingJoffrey = new KingJoffrey(); - IntStream.range(0, Event.values().length).forEach(i -> { - assertEquals(i, appender.getLogSize()); - var event = Event.values()[i]; - kingJoffrey.onEvent(event); - final var expectedMessage = "Received event from the King's Hand: " + event.toString(); - assertEquals(expectedMessage, appender.getLastMessage()); - assertEquals(i + 1, appender.getLogSize()); - }); - + IntStream.range(0, Event.values().length) + .forEach( + i -> { + assertEquals(i, appender.getLogSize()); + var event = Event.values()[i]; + kingJoffrey.onEvent(event); + final var expectedMessage = "Received event from the King's Hand: " + event; + assertEquals(expectedMessage, appender.getLastMessage()); + assertEquals(i + 1, appender.getLogSize()); + }); } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { @@ -95,5 +90,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java index 649f5bb2c57d..e06a50fc80b9 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java @@ -33,27 +33,21 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:57 AM - * - * @author Jeroen Meulemeester - */ +/** KingsHandTest */ class KingsHandTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public KingsHandTest() { super(null, null, KingsHand::new, KingsHand::new); } /** * The {@link KingsHand} is both an {@link EventEmitter} as an {@link EventObserver} so verify if - * every event received is passed up to it's superior, in most cases {@link KingJoffrey} but now + * every event received is passed up to its superior, in most cases {@link KingJoffrey} but now * just a mocked observer. */ @Test - void testPassThrough() throws Exception { + void testPassThrough() { final var observer = mock(EventObserver.class); final var kingsHand = new KingsHand(); kingsHand.registerObserver(observer, Event.STARK_SIGHTED); @@ -65,12 +59,12 @@ void testPassThrough() throws Exception { verifyNoMoreInteractions(observer); // Verify if each event is passed on to the observer, nothing less, nothing more. - Arrays.stream(Event.values()).forEach(event -> { - kingsHand.onEvent(event); - verify(observer, times(1)).onEvent(eq(event)); - verifyNoMoreInteractions(observer); - }); - + Arrays.stream(Event.values()) + .forEach( + event -> { + kingsHand.onEvent(event); + verify(observer, times(1)).onEvent(eq(event)); + verifyNoMoreInteractions(observer); + }); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java index a400cec1bf2f..c90ccd993317 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java @@ -24,18 +24,11 @@ */ package com.iluwatar.event.aggregator; -/** - * Date: 12/12/15 - 10:57 AM - * - * @author Jeroen Meulemeester - */ +/** LordBaelishTest */ class LordBaelishTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public LordBaelishTest() { super(Weekday.FRIDAY, Event.STARK_SIGHTED, LordBaelish::new, LordBaelish::new); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java index a45d349c6cab..5ddd0a65e26f 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java @@ -24,18 +24,11 @@ */ package com.iluwatar.event.aggregator; -/** - * Date: 12/12/15 - 10:57 AM - * - * @author Jeroen Meulemeester - */ +/** LordVarysTest */ class LordVarysTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public LordVarysTest() { super(Weekday.SATURDAY, Event.TRAITOR_DETECTED, LordVarys::new, LordVarys::new); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java index 8e824f0a38a5..b6f86123e45c 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java @@ -24,20 +24,12 @@ */ package com.iluwatar.event.aggregator; -/** - * Date: 12/12/15 - 10:57 AM - * - * @author Jeroen Meulemeester - */ +/** ScoutTest */ class ScoutTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public ScoutTest() { - super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); - + super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java index a12db4b3ff07..e078951e4eb4 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java @@ -30,20 +30,17 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 2:12 PM - * - * @author Jeroen Meulemeester - */ +/** WeekdayTest */ class WeekdayTest { @Test void testToString() { - Arrays.stream(Weekday.values()).forEach(weekday -> { - final String toString = weekday.toString(); - assertNotNull(toString); - assertEquals(weekday.name(), toString.toUpperCase()); - }); + Arrays.stream(Weekday.values()) + .forEach( + weekday -> { + final String toString = weekday.toString(); + assertNotNull(toString); + assertEquals(weekday.name(), toString.toUpperCase()); + }); } - -} \ No newline at end of file +} diff --git a/event-asynchronous/README.md b/event-asynchronous/README.md deleted file mode 100644 index 0dbe37ed0743..000000000000 --- a/event-asynchronous/README.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Event-based Asynchronous -category: Concurrency -language: en -tag: - - Reactive ---- - -## Intent -The Event-based Asynchronous Pattern makes available the advantages of multithreaded applications while hiding many -of the complex issues inherent in multithreaded design. Using a class that supports this pattern can allow you to: - -1. Perform time-consuming tasks, such as downloads and database operations, "in the background," without interrupting your application. -2. Execute multiple operations simultaneously, receiving notifications when each completes. -3. Wait for resources to become available without stopping ("hanging") your application. -4. Communicate with pending asynchronous operations using the familiar events-and-delegates model. - -## Class diagram -![alt text](./etc/event-asynchronous.png "Event-based Asynchronous") - -## Applicability -Use the Event-based Asynchronous pattern(s) when - -* Time-consuming tasks are needed to run in the background without disrupting the current application. - -## Credits - -* [Event-based Asynchronous Pattern Overview](https://msdn.microsoft.com/en-us/library/wewwczdw%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) diff --git a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java b/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java deleted file mode 100644 index e54a0874fad9..000000000000 --- a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.event.asynchronous; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Application test - */ -class EventAsynchronousTest { - private static final Logger LOGGER = LoggerFactory.getLogger(EventAsynchronousTest.class); - - @Test - void testAsynchronousEvent() { - var eventManager = new EventManager(); - try { - var aEventId = eventManager.createAsync(60); - eventManager.start(aEventId); - assertEquals(1, eventManager.getEventPool().size()); - assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS); - assertEquals(-1, eventManager.numOfCurrentlyRunningSyncEvent()); - eventManager.cancel(aEventId); - assertTrue(eventManager.getEventPool().isEmpty()); - } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { - LOGGER.error(e.getMessage()); - } - } - - @Test - void testSynchronousEvent() { - var eventManager = new EventManager(); - try { - var sEventId = eventManager.create(60); - eventManager.start(sEventId); - assertEquals(1, eventManager.getEventPool().size()); - assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS); - assertNotEquals(-1, eventManager.numOfCurrentlyRunningSyncEvent()); - eventManager.cancel(sEventId); - assertTrue(eventManager.getEventPool().isEmpty()); - } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException - | InvalidOperationException e) { - LOGGER.error(e.getMessage()); - } - } - - @Test - void testUnsuccessfulSynchronousEvent() { - assertThrows(InvalidOperationException.class, () -> { - var eventManager = new EventManager(); - try { - var sEventId = eventManager.create(60); - eventManager.start(sEventId); - sEventId = eventManager.create(60); - eventManager.start(sEventId); - } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { - LOGGER.error(e.getMessage()); - } - }); - } - - @Test - void testFullSynchronousEvent() { - var eventManager = new EventManager(); - try { - var eventTime = 1; - - var sEventId = eventManager.create(eventTime); - assertEquals(1, eventManager.getEventPool().size()); - eventManager.start(sEventId); - - var currentTime = System.currentTimeMillis(); - // +2 to give a bit of buffer time for event to complete properly. - var endTime = currentTime + (eventTime + 2 * 1000); - while (System.currentTimeMillis() < endTime) ; - - assertTrue(eventManager.getEventPool().isEmpty()); - - } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException - | InvalidOperationException e) { - LOGGER.error(e.getMessage()); - } - } - - @Test - void testFullAsynchronousEvent() { - var eventManager = new EventManager(); - try { - var eventTime = 1; - - var aEventId1 = eventManager.createAsync(eventTime); - var aEventId2 = eventManager.createAsync(eventTime); - var aEventId3 = eventManager.createAsync(eventTime); - assertEquals(3, eventManager.getEventPool().size()); - - eventManager.start(aEventId1); - eventManager.start(aEventId2); - eventManager.start(aEventId3); - - var currentTime = System.currentTimeMillis(); - // +2 to give a bit of buffer time for event to complete properly. - var endTime = currentTime + (eventTime + 2 * 1000); - while (System.currentTimeMillis() < endTime) ; - - assertTrue(eventManager.getEventPool().isEmpty()); - - } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) { - LOGGER.error(e.getMessage()); - } - } - - @Test - void testLongRunningEventException(){ - assertThrows(LongRunningEventException.class, () -> { - var eventManager = new EventManager(); - eventManager.createAsync(2000); - }); - } - - - @Test - void testMaxNumOfEventsAllowedException(){ - assertThrows(MaxNumOfEventsAllowedException.class, () -> { - final var eventManager = new EventManager(); - for(int i=0;i<1100;i++){ - eventManager.createAsync(i); - } - }); - } - - - -} \ No newline at end of file diff --git a/event-based-asynchronous/README.md b/event-based-asynchronous/README.md new file mode 100644 index 000000000000..eee9758dd638 --- /dev/null +++ b/event-based-asynchronous/README.md @@ -0,0 +1,183 @@ +--- +title: "Event-Based Asynchronous Pattern in Java: Mastering Non-Blocking System Design" +shortTitle: Event-Based Asynchronous +description: "Explore the best practices and implementations of event-based asynchronous patterns in Java. Enhance your programming skills with our comprehensive guide and real-world examples." +category: Concurrency +language: en +tag: + - Asynchronous + - Decoupling + - Event-driven + - Fault tolerance + - Messaging + - Reactive + - Scalability +--- + +## Also known as + +* Asynchronous Event Handling + +## Intent of Event-Based Asynchronous Design Pattern + +The Event-Based Asynchronous pattern allows a system to handle tasks that might take some time to complete without blocking the execution of the program. It enables better resource utilization by freeing up a thread that would otherwise be blocked waiting for the task to complete. + +## Detailed Explanation of Event-Based Asynchronous Pattern with Real-World Examples + +Real-world example + +> A real-world analogy of the Event-Based Asynchronous design pattern is how a restaurant operates. When a customer places an order, the waiter records the order and passes it to the kitchen. Instead of waiting at the kitchen for the food to be prepared, the waiter continues to serve other tables. Once the kitchen completes the order, they signal (event) the waiter, who then delivers the food to the customer. This allows the waiter to handle multiple tasks efficiently without idle waiting, similar to how asynchronous programming handles tasks in parallel, enhancing overall efficiency and responsiveness. + +In Plain Words + +> The Event-Based Asynchronous design pattern allows tasks to be executed in the background, notifying the main program via events when completed, thereby enhancing system efficiency and responsiveness without blocking ongoing operations. + +## Programmatic Example of Event-Based Asynchronous Pattern in Java + +Event-Based Asynchronous design pattern allows tasks to be executed in the background, notifying the main program via events when completed, thereby enhancing system efficiency and responsiveness without blocking ongoing operations. + +In the provided code, we have several key classes implementing this pattern: + +- `App`: The main class that runs the application. It interacts with the `EventManager` to create, start, stop, and check the status of events. +- `EventManager`: Manages the lifecycle of events, including creating, starting, stopping, and checking the status of events. It maintains a map of event IDs to `Event` objects. +- `Event`: An abstract class that represents an event. It has two concrete subclasses: `AsyncEvent` and `SyncEvent`. +- `AsyncEvent` and `SyncEvent`: Represent asynchronous and synchronous events respectively. +- Custom exceptions: Thrown by the `EventManager` when certain conditions are not met. + +Here's a simplified code example of how these classes interact: + +```java +// Create an EventManager +EventManager eventManager = new EventManager(); + +// Create an asynchronous event that runs for 60 seconds +int asyncEventId = eventManager.createAsync(Duration.ofSeconds(60)); + +// Start the asynchronous event +eventManager.start(asyncEventId); + +// Check the status of the asynchronous event +eventManager.status(asyncEventId); + +// Stop the asynchronous event +eventManager.cancel(asyncEventId); +``` + +In this example, the `App` class creates an `EventManager`, then uses it to create, start, check the status of, and stop an asynchronous event. The `EventManager` creates an `AsyncEvent` object, starts it in a separate thread, checks its status, and stops it when requested. + +The `EventManager` class is the core of the Event-Based Asynchronous pattern implementation. It manages the lifecycle of events, including creating, starting, stopping, and checking the status of events. It maintains a map of event IDs to `Event` objects. Here's a snippet of how it creates an asynchronous event: + +```java +public int createAsync(Duration runtime) throws MaxNumOfEventsAllowedException, LongRunningEventException { + int id = counter.incrementAndGet(); + events.put(id, new AsyncEvent(id, runtime)); + return id; +} +``` + +The `Event` class is an abstract class that represents an event. It has two concrete subclasses: `AsyncEvent` and `SyncEvent`. An `Event` has an ID, a runtime (how long it should run), and a status (whether it's running, completed, or ready to start). It also has methods to start and stop the event. Here's a snippet of how an `AsyncEvent` starts: + +```java +@Override +public void start() { + Thread thread = new Thread(() -> { + try { + handleRunStart(); + Thread.sleep(getRuntime().toMillis()); + handleRunComplete(); + } catch (InterruptedException e) { + handleRunFailure(e.getMessage()); + } + }); + thread.start(); +} +``` + +In this snippet, when an `AsyncEvent` is started, it runs in a separate thread without blocking the main thread. + +A synchronous event is created and managed similarly to an asynchronous event. Here's a snippet of how it creates and manages a synchronous event: + +```java +// Create an EventManager +EventManager eventManager = new EventManager(); + +// Create a synchronous event that runs for 60 seconds +int syncEventId = eventManager.create(Duration.ofSeconds(60)); + +// Start the synchronous event +eventManager.start(syncEventId); + +// Check the status of the synchronous event +eventManager.status(syncEventId); + +// Stop the synchronous event +eventManager.cancel(syncEventId); +``` + +In the `EventManager` class, a synchronous event is created using the `create` method: + +```java +public int create(Duration runtime) throws MaxNumOfEventsAllowedException, LongRunningEventException { + int id = counter.incrementAndGet(); + events.put(id, new SyncEvent(id, runtime)); + return id; +} +``` + +The `SyncEvent` class is a subclass of `Event` that represents a synchronous event. When a `SyncEvent` is started, it runs on the main thread and blocks it until the event is completed. Here's a snippet of how a `SyncEvent` starts: + +```java +@Override +public void start() { + try { + handleRunStart(); + Thread.sleep(getRuntime().toMillis()); + handleRunComplete(); + } catch (InterruptedException e) { + handleRunFailure(e.getMessage()); + } +} +``` + +In this snippet, when a `SyncEvent` is started, it runs on the main thread, blocking it until the event is completed. This is in contrast to an `AsyncEvent`, which runs in a separate thread without blocking the main thread. + +These are the key parts of the Event-Based Asynchronous design pattern as implemented in this code. The pattern allows tasks to be executed in the background, notifying the main program via events when completed, thereby enhancing system efficiency and responsiveness without blocking ongoing operations. + +## When to Use the Event-Based Asynchronous Pattern in Java + +* When multiple tasks can be processed in parallel and independently. +* Systems that require responsiveness and cannot afford to have threads blocked waiting for an operation to complete. +* In GUI applications where user interface responsiveness is critical. +* Distributed systems where long network operations are involved. + +## Real-World Applications of Event-Based Asynchronous Pattern in Java + +* GUI libraries in Java (e.g., JavaFX, Swing with SwingWorker). +* Java Message Service (JMS) for handling asynchronous messaging. +* Java’s CompletableFuture and various Event-Driven Frameworks. + +## Benefits and Trade-offs of Event-Based Asynchronous Pattern + +Benefits: + +* Improves application scalability and responsiveness. +* Reduces the resources wasted on threads that would simply wait for I/O operations. +* Enhances fault tolerance through isolation of process execution. + +Trade-offs: + +* Increases complexity of error handling as errors may occur in different threads or at different times. +* Can lead to harder-to-follow code and debugging challenges due to the non-linear nature of asynchronous code execution. + +## Related Java Design Patterns + +* [Observer](https://java-design-patterns.com/patterns/observer/): Often used in conjunction where the observer reacts to events as they occur. +* Publish/Subscribe: Related in terms of event handling mechanisms, particularly for messaging and event distribution across components. +* [Command](https://java-design-patterns.com/patterns/command/): Useful for encapsulating all information needed to perform an action or trigger an event. + +## References and Credits + +* [Java Concurrency in Practice](https://amzn.to/4cYY4kU) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3Uh7rW1) +* [Pro JavaFX 8: A Definitive Guide to Building Desktop, Mobile, and Embedded Java Clients](https://amzn.to/3vHUqLL) +* [Event-based Asynchronous Pattern Overview (Microsoft)](https://msdn.microsoft.com/en-us/library/wewwczdw%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) diff --git a/event-asynchronous/etc/event-asynchronous.png b/event-based-asynchronous/etc/event-asynchronous.png similarity index 100% rename from event-asynchronous/etc/event-asynchronous.png rename to event-based-asynchronous/etc/event-asynchronous.png diff --git a/event-asynchronous/etc/event-asynchronous.ucls b/event-based-asynchronous/etc/event-asynchronous.ucls similarity index 100% rename from event-asynchronous/etc/event-asynchronous.ucls rename to event-based-asynchronous/etc/event-asynchronous.ucls diff --git a/event-asynchronous/etc/event-asynchronous.urm.puml b/event-based-asynchronous/etc/event-asynchronous.urm.puml similarity index 100% rename from event-asynchronous/etc/event-asynchronous.urm.puml rename to event-based-asynchronous/etc/event-asynchronous.urm.puml diff --git a/event-based-asynchronous/etc/event-based-asynchronous.urm.puml b/event-based-asynchronous/etc/event-based-asynchronous.urm.puml new file mode 100644 index 000000000000..518155ab44f9 --- /dev/null +++ b/event-based-asynchronous/etc/event-based-asynchronous.urm.puml @@ -0,0 +1,70 @@ +@startuml +package com.iluwatar.event.asynchronous { + class App { + - LOGGER : Logger {static} + + PROP_FILE_NAME : String {static} + ~ interactiveMode : boolean + + App() + + main(args : String[]) {static} + - processOption1(eventManager : EventManager, s : Scanner) + - processOption2(eventManager : EventManager, s : Scanner) + - processOption3(eventManager : EventManager, s : Scanner) + + quickRun() + + run() + + runInteractiveMode() + + setUp() + } + class AsyncEvent { + - LOGGER : Logger {static} + - eventId : int + - eventListener : ThreadCompleteListener + - eventTime : int + - isComplete : AtomicBoolean + - synchronous : boolean + - thread : Thread + + AsyncEvent(eventId : int, eventTime : int, synchronous : boolean) + + addListener(listener : ThreadCompleteListener) + - completed() + + isSynchronous() : boolean + + removeListener() + + run() + + start() + + status() + + stop() + } + interface Event { + + start() {abstract} + + status() {abstract} + + stop() {abstract} + } + class EventManager { + - DOES_NOT_EXIST : String {static} + + MAX_EVENT_TIME : int {static} + + MAX_ID : int {static} + + MAX_RUNNING_EVENTS : int {static} + + MIN_ID : int {static} + - currentlyRunningSyncEvent : int + - eventPool : Map + - rand : SecureRandom + + EventManager() + + cancel(eventId : int) + + completedEventHandler(eventId : int) + + create(eventTime : int) : int + + createAsync(eventTime : int) : int + - createEvent(eventTime : int, isSynchronous : boolean) : int + - generateId() : int + + getEventPool() : Map + + numOfCurrentlyRunningSyncEvent() : int + + shutdown() + + start(eventId : int) + + status(eventId : int) + + statusOfAllEvents() + } + interface ThreadCompleteListener { + + completedEventHandler(int) {abstract} + } +} +AsyncEvent --> "-eventListener" ThreadCompleteListener +AsyncEvent ..|> Event +EventManager ..|> ThreadCompleteListener +@enduml \ No newline at end of file diff --git a/event-asynchronous/pom.xml b/event-based-asynchronous/pom.xml similarity index 83% rename from event-asynchronous/pom.xml rename to event-based-asynchronous/pom.xml index 1acd158ba279..3a66d321c02f 100644 --- a/event-asynchronous/pom.xml +++ b/event-based-asynchronous/pom.xml @@ -32,13 +32,27 @@ java-design-patterns 1.26.0-SNAPSHOT - event-asynchronous + event-based-asynchronous + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + org.awaitility + awaitility + 4.3.0 + test + @@ -59,4 +73,4 @@ - + \ No newline at end of file diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java similarity index 91% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java index 33a61def59ff..731cd169157e 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java @@ -25,6 +25,7 @@ package com.iluwatar.event.asynchronous; import java.io.IOException; +import java.time.Duration; import java.util.Properties; import java.util.Scanner; import lombok.extern.slf4j.Slf4j; @@ -96,9 +97,7 @@ public void setUp() { } } - /** - * Run program in either interactive mode or not. - */ + /** Run program in either interactive mode or not. */ public void run() { if (interactiveMode) { runInteractiveMode(); @@ -107,21 +106,19 @@ public void run() { } } - /** - * Run program in non-interactive mode. - */ + /** Run program in non-interactive mode. */ public void quickRun() { var eventManager = new EventManager(); try { // Create an Asynchronous event. - var asyncEventId = eventManager.createAsync(60); + var asyncEventId = eventManager.createAsync(Duration.ofSeconds(60)); LOGGER.info("Async Event [{}] has been created.", asyncEventId); eventManager.start(asyncEventId); LOGGER.info("Async Event [{}] has been started.", asyncEventId); // Create a Synchronous event. - var syncEventId = eventManager.create(60); + var syncEventId = eventManager.create(Duration.ofSeconds(60)); LOGGER.info("Sync Event [{}] has been created.", syncEventId); eventManager.start(syncEventId); LOGGER.info("Sync Event [{}] has been started.", syncEventId); @@ -134,15 +131,15 @@ public void quickRun() { eventManager.cancel(syncEventId); LOGGER.info("Sync Event [{}] has been stopped.", syncEventId); - } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException + } catch (MaxNumOfEventsAllowedException + | LongRunningEventException + | EventDoesNotExistException | InvalidOperationException e) { LOGGER.error(e.getMessage()); } } - /** - * Run program in interactive mode. - */ + /** Run program in interactive mode. */ public void runInteractiveMode() { var eventManager = new EventManager(); @@ -150,7 +147,8 @@ public void runInteractiveMode() { var option = -1; while (option != 4) { LOGGER.info("Hello. Would you like to boil some eggs?"); - LOGGER.info(""" + LOGGER.info( + """ (1) BOIL AN EGG (2) STOP BOILING THIS EGG (3) HOW ARE MY EGGS? @@ -207,13 +205,14 @@ private void processOption1(EventManager eventManager, Scanner s) { LOGGER.info("Boil multiple eggs at once (A) or boil them one-by-one (S)?: "); var eventType = s.nextLine(); LOGGER.info("How long should this egg be boiled for (in seconds)?: "); - var eventTime = s.nextInt(); + var eventTime = Duration.ofSeconds(s.nextInt()); if (eventType.equalsIgnoreCase("A")) { try { var eventId = eventManager.createAsync(eventTime); eventManager.start(eventId); LOGGER.info("Egg [{}] is being boiled.", eventId); - } catch (MaxNumOfEventsAllowedException | LongRunningEventException + } catch (MaxNumOfEventsAllowedException + | LongRunningEventException | EventDoesNotExistException e) { LOGGER.error(e.getMessage()); } @@ -222,13 +221,14 @@ private void processOption1(EventManager eventManager, Scanner s) { var eventId = eventManager.create(eventTime); eventManager.start(eventId); LOGGER.info("Egg [{}] is being boiled.", eventId); - } catch (MaxNumOfEventsAllowedException | InvalidOperationException - | LongRunningEventException | EventDoesNotExistException e) { + } catch (MaxNumOfEventsAllowedException + | InvalidOperationException + | LongRunningEventException + | EventDoesNotExistException e) { LOGGER.error(e.getMessage()); } } else { LOGGER.info("Unknown event type."); } } - } diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java similarity index 78% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java index 63239a476e32..b58848f0a702 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java @@ -24,23 +24,24 @@ */ package com.iluwatar.event.asynchronous; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Each Event runs as a separate/individual thread. - */ +/** Each Event runs as a separate/individual thread. */ @Slf4j @RequiredArgsConstructor public class AsyncEvent implements Event, Runnable { private final int eventId; - private final int eventTime; - @Getter - private final boolean synchronous; + private final Duration eventTime; + @Getter private final boolean synchronous; private Thread thread; - private boolean isComplete = false; + private final AtomicBoolean isComplete = new AtomicBoolean(false); private ThreadCompleteListener eventListener; @Override @@ -59,7 +60,7 @@ public void stop() { @Override public void status() { - if (!isComplete) { + if (isComplete.get()) { LOGGER.info("[{}] is not done.", eventId); } else { LOGGER.info("[{}] is done.", eventId); @@ -68,17 +69,19 @@ public void status() { @Override public void run() { - var currentTime = System.currentTimeMillis(); - var endTime = currentTime + (eventTime * 1000); - while (System.currentTimeMillis() < endTime) { + + var currentTime = Instant.now(); + var endTime = currentTime.plusSeconds(eventTime.getSeconds()); + while (Instant.now().compareTo(endTime) < 0) { try { - Thread.sleep(1000); // Sleep for 1 second. + TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { + LOGGER.error("Thread was interrupted: ", e); Thread.currentThread().interrupt(); return; } } - isComplete = true; + isComplete.set(true); completed(); } @@ -86,14 +89,9 @@ public final void addListener(final ThreadCompleteListener listener) { this.eventListener = listener; } - public final void removeListener() { - this.eventListener = null; - } - private void completed() { if (eventListener != null) { eventListener.completedEventHandler(eventId); } } - } diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java similarity index 99% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java index 663ba5a7ef02..6c51f8584ec6 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java @@ -34,5 +34,4 @@ public interface Event { void stop(); void status(); - } diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java similarity index 90% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java index 6aea231ae6d0..ad6a649c058f 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java @@ -24,12 +24,12 @@ */ package com.iluwatar.event.asynchronous; -/** - * Custom Exception Class for Non Existent Event. - */ +import java.io.Serial; + +/** Custom Exception Class for Non-Existent Event. */ public class EventDoesNotExistException extends Exception { - private static final long serialVersionUID = -3398463738273811509L; + @Serial private static final long serialVersionUID = -3398463738273811509L; public EventDoesNotExistException(String message) { super(message); diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java similarity index 76% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java index 8414d145e639..cef3697ffb55 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java @@ -25,13 +25,15 @@ package com.iluwatar.event.asynchronous; import java.security.SecureRandom; +import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; /** - * EventManager handles and maintains a pool of event threads. {@link AsyncEvent} threads are created - * upon user request. Thre are two types of events; Asynchronous and Synchronous. There can be - * multiple Asynchronous events running at once but only one Synchronous event running at a time. + * EventManager handles and maintains a pool of event threads. {@link AsyncEvent} threads are + * created upon user request. Thre are two types of events; Asynchronous and Synchronous. There can + * be multiple Asynchronous events running at once but only one Synchronous event running at a time. * Currently supported event operations are: start, stop, and getStatus. Once an event is complete, * it then notifies EventManager through a listener. The EventManager then takes the event out of * the pool. @@ -39,23 +41,21 @@ public class EventManager implements ThreadCompleteListener { public static final int MAX_RUNNING_EVENTS = 1000; - // Just don't wanna have too many running events. :) + // Just don't want to have too many running events. :) public static final int MIN_ID = 1; public static final int MAX_ID = MAX_RUNNING_EVENTS; - public static final int MAX_EVENT_TIME = 1800; // in seconds / 30 minutes. + public static final Duration MAX_EVENT_TIME = Duration.ofSeconds(1800); // 30 minutes. private int currentlyRunningSyncEvent = -1; private final SecureRandom rand; - private final Map eventPool; + + @Getter private final Map eventPool; private static final String DOES_NOT_EXIST = " does not exist."; - /** - * EventManager constructor. - */ + /** EventManager constructor. */ public EventManager() { rand = new SecureRandom(); eventPool = new ConcurrentHashMap<>(MAX_RUNNING_EVENTS); - } /** @@ -64,15 +64,18 @@ public EventManager() { * @param eventTime Time an event should run for. * @return eventId * @throws MaxNumOfEventsAllowedException When too many events are running at a time. - * @throws InvalidOperationException No new synchronous events can be created when one is - * already running. - * @throws LongRunningEventException Long running events are not allowed in the app. + * @throws InvalidOperationException No new synchronous events can be created when one is already + * running. + * @throws LongRunningEventException Long-running events are not allowed in the app. */ - public int create(int eventTime) + public int create(Duration eventTime) throws MaxNumOfEventsAllowedException, InvalidOperationException, LongRunningEventException { if (currentlyRunningSyncEvent != -1) { - throw new InvalidOperationException("Event [" + currentlyRunningSyncEvent + "] is still" - + " running. Please wait until it finishes and try again."); + throw new InvalidOperationException( + "Event [" + + currentlyRunningSyncEvent + + "] is still" + + " running. Please wait until it finishes and try again."); } var eventId = createEvent(eventTime, true); @@ -87,21 +90,25 @@ public int create(int eventTime) * @param eventTime Time an event should run for. * @return eventId * @throws MaxNumOfEventsAllowedException When too many events are running at a time. - * @throws LongRunningEventException Long running events are not allowed in the app. + * @throws LongRunningEventException Long-running events are not allowed in the app. */ - public int createAsync(int eventTime) throws MaxNumOfEventsAllowedException, - LongRunningEventException { + public int createAsync(Duration eventTime) + throws MaxNumOfEventsAllowedException, LongRunningEventException { return createEvent(eventTime, false); } - private int createEvent(int eventTime, boolean isSynchronous) + private int createEvent(Duration eventTime, boolean isSynchronous) throws MaxNumOfEventsAllowedException, LongRunningEventException { + if (eventTime.isNegative()) { + throw new IllegalArgumentException("eventTime cannot be negative"); + } + if (eventPool.size() == MAX_RUNNING_EVENTS) { - throw new MaxNumOfEventsAllowedException("Too many events are running at the moment." - + " Please try again later."); + throw new MaxNumOfEventsAllowedException( + "Too many events are running at the moment." + " Please try again later."); } - if (eventTime >= MAX_EVENT_TIME) { + if (eventTime.getSeconds() > MAX_EVENT_TIME.getSeconds()) { throw new LongRunningEventException( "Maximum event time allowed is " + MAX_EVENT_TIME + " seconds. Please try again."); } @@ -162,17 +169,13 @@ public void status(int eventId) throws EventDoesNotExistException { eventPool.get(eventId).status(); } - /** - * Gets status of all running events. - */ + /** Gets status of all running events. */ @SuppressWarnings("rawtypes") public void statusOfAllEvents() { eventPool.entrySet().forEach(entry -> ((AsyncEvent) ((Map.Entry) entry).getValue()).status()); } - /** - * Stop all running events. - */ + /** Stop all running events. */ @SuppressWarnings("rawtypes") public void shutdown() { eventPool.entrySet().forEach(entry -> ((AsyncEvent) ((Map.Entry) entry).getValue()).stop()); @@ -180,8 +183,7 @@ public void shutdown() { /** * Returns a pseudo-random number between min and max, inclusive. The difference between min and - * max can be at most - * Integer.MAX_VALUE - 1. + * max can be at most Integer.MAX_VALUE - 1. */ private int generateId() { // nextInt is normally exclusive of the top value, @@ -195,7 +197,8 @@ private int generateId() { } /** - * Callback from an {@link AsyncEvent} (once it is complete). The Event is then removed from the pool. + * Callback from an {@link AsyncEvent} (once it is complete). The Event is then removed from the + * pool. */ @Override public void completedEventHandler(int eventId) { @@ -206,16 +209,7 @@ public void completedEventHandler(int eventId) { eventPool.remove(eventId); } - /** - * Getter method for event pool. - */ - public Map getEventPool() { - return eventPool; - } - - /** - * Get number of currently running Synchronous events. - */ + /** Get number of currently running Synchronous events. */ public int numOfCurrentlyRunningSyncEvent() { return currentlyRunningSyncEvent; } diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java similarity index 89% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java index 9b12e73fcf30..f8a5914602de 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java @@ -24,15 +24,14 @@ */ package com.iluwatar.event.asynchronous; -/** - * Type of Exception raised when the Operation being invoked is Invalid. - */ +import java.io.Serial; + +/** Type of Exception raised when the Operation being invoked is Invalid. */ public class InvalidOperationException extends Exception { - private static final long serialVersionUID = -6191545255213410803L; + @Serial private static final long serialVersionUID = -6191545255213410803L; public InvalidOperationException(String message) { super(message); } - } diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java similarity index 88% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java index 569ccc4311e1..9045b6dcf9b1 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java @@ -24,12 +24,12 @@ */ package com.iluwatar.event.asynchronous; -/** - * Type of Exception raised when the Operation being invoked is Long Running. - */ +import java.io.Serial; + +/** Type of Exception raised when the Operation being invoked is Long Running. */ public class LongRunningEventException extends Exception { - private static final long serialVersionUID = -483423544320148809L; + @Serial private static final long serialVersionUID = -483423544320148809L; public LongRunningEventException(String message) { super(message); diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java similarity index 88% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java index 243f02d73d46..16fa5502d1f9 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java @@ -24,12 +24,12 @@ */ package com.iluwatar.event.asynchronous; -/** - * Type of Exception raised when the max number of allowed events is exceeded. - */ +import java.io.Serial; + +/** Type of Exception raised when the max number of allowed events is exceeded. */ public class MaxNumOfEventsAllowedException extends Exception { - private static final long serialVersionUID = -8430876973516292695L; + @Serial private static final long serialVersionUID = -8430876973516292695L; public MaxNumOfEventsAllowedException(String message) { super(message); diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java similarity index 94% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java index 334cada2cb4e..f30eb51351a5 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java @@ -24,9 +24,7 @@ */ package com.iluwatar.event.asynchronous; -/** - * Interface with listener behaviour related to Thread Completion. - */ +/** Interface with listener behaviour related to Thread Completion. */ public interface ThreadCompleteListener { void completedEventHandler(final int eventId); } diff --git a/event-asynchronous/src/main/resources/config.properties b/event-based-asynchronous/src/main/resources/config.properties similarity index 100% rename from event-asynchronous/src/main/resources/config.properties rename to event-based-asynchronous/src/main/resources/config.properties diff --git a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java similarity index 82% rename from event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java rename to event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java index 8f1b40b4c304..4b5094033b13 100644 --- a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java +++ b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.event.asynchronous; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that EventAsynchronous example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that EventAsynchronous example runs without errors. */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java new file mode 100644 index 000000000000..ab9dd70bbf3d --- /dev/null +++ b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java @@ -0,0 +1,141 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.event.asynchronous; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Duration; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +/** Application test */ +class EventAsynchronousTest { + + @Test + @SneakyThrows + void testAsynchronousEvent() { + var eventManager = new EventManager(); + var aEventId = eventManager.createAsync(Duration.ofSeconds(60)); + + assertDoesNotThrow(() -> eventManager.start(aEventId)); + + assertEquals(1, eventManager.getEventPool().size()); + assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS); + assertEquals(-1, eventManager.numOfCurrentlyRunningSyncEvent()); + + assertDoesNotThrow(() -> eventManager.cancel(aEventId)); + assertTrue(eventManager.getEventPool().isEmpty()); + } + + @Test + @SneakyThrows + void testSynchronousEvent() { + var eventManager = new EventManager(); + var sEventId = eventManager.create(Duration.ofSeconds(60)); + + assertDoesNotThrow(() -> eventManager.start(sEventId)); + assertEquals(1, eventManager.getEventPool().size()); + assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS); + assertNotEquals(-1, eventManager.numOfCurrentlyRunningSyncEvent()); + + assertDoesNotThrow(() -> eventManager.cancel(sEventId)); + assertTrue(eventManager.getEventPool().isEmpty()); + } + + @Test + @SneakyThrows + void testFullSynchronousEvent() { + var eventManager = new EventManager(); + + var eventTime = Duration.ofSeconds(1); + + var sEventId = eventManager.create(eventTime); + assertEquals(1, eventManager.getEventPool().size()); + + eventManager.start(sEventId); + + await().until(() -> eventManager.getEventPool().isEmpty()); + } + + @Test + @SneakyThrows + void testUnsuccessfulSynchronousEvent() { + assertThrows( + InvalidOperationException.class, + () -> { + var eventManager = new EventManager(); + + var sEventId = assertDoesNotThrow(() -> eventManager.create(Duration.ofSeconds(60))); + eventManager.start(sEventId); + sEventId = eventManager.create(Duration.ofSeconds(60)); + eventManager.start(sEventId); + }); + } + + @Test + @SneakyThrows + void testFullAsynchronousEvent() { + var eventManager = new EventManager(); + var eventTime = Duration.ofSeconds(1); + + var aEventId1 = assertDoesNotThrow(() -> eventManager.createAsync(eventTime)); + var aEventId2 = assertDoesNotThrow(() -> eventManager.createAsync(eventTime)); + var aEventId3 = assertDoesNotThrow(() -> eventManager.createAsync(eventTime)); + assertEquals(3, eventManager.getEventPool().size()); + + eventManager.start(aEventId1); + eventManager.start(aEventId2); + eventManager.start(aEventId3); + + await().until(() -> eventManager.getEventPool().isEmpty()); + } + + @Test + void testLongRunningEventException() { + assertThrows( + LongRunningEventException.class, + () -> { + var eventManager = new EventManager(); + eventManager.createAsync(Duration.ofMinutes(31)); + }); + } + + @Test + void testMaxNumOfEventsAllowedException() { + assertThrows( + MaxNumOfEventsAllowedException.class, + () -> { + final var eventManager = new EventManager(); + for (int i = 0; i < 1100; i++) { + eventManager.createAsync(Duration.ofSeconds(i)); + } + }); + } +} diff --git a/event-driven-architecture/README.md b/event-driven-architecture/README.md index 4192892721f2..c868c174e078 100644 --- a/event-driven-architecture/README.md +++ b/event-driven-architecture/README.md @@ -1,33 +1,202 @@ --- -title: Event Driven Architecture +title: "Event-Driven Architecture Pattern in Java: Building Responsive and Scalable Java Systems" +shortTitle: Event-Driven Architecture +description: "Discover comprehensive guides on Event-Driven Architecture patterns with practical Java examples. Learn to implement effective event-driven systems in your projects." category: Architectural language: en tag: - - Reactive + - Asynchronous + - Decoupling + - Enterprise patterns + - Event-driven + - Messaging + - Publish/subscribe + - Reactive + - Scalability --- -## Intent -Send and notify state changes of your objects to other applications using an Event-driven Architecture. +## Also known as -## Class diagram -![alt text](./etc/eda.png "Event Driven Architecture") +* Event-Driven System +* Event-Based Architecture + +## Intent of Event-Driven Architecture Design Pattern + +Event-Driven Architecture (EDA) is designed to orchestrate behavior around the production, detection, consumption of, and reaction to events. This architecture enables highly decoupled, scalable, and dynamic interconnections between event producers and consumers. + +## Detailed Explanation of Event-Driven Architecture Pattern with Real-World Examples + +Real-world example + +> A real-world example of the Event-Driven Architecture (EDA) pattern is the operation of an air traffic control system. In this system, events such as aircraft entering airspace, changes in weather conditions, and ground vehicle movements trigger specific responses like altering flight paths, scheduling gate assignments, and updating runway usage. This setup allows for highly efficient, responsive, and safe management of airport operations, reflecting EDA's core principles of asynchronous communication and dynamic event handling. + +In plain words + +> Event-Driven Architecture is a design pattern where system behavior is dictated by the occurrence of specific events, allowing for dynamic, efficient, and decoupled responses. + +Wikipedia says + +> Event-driven architecture (EDA) is a software architecture paradigm concerning the production and detection of events. + +Architecture diagram + +![EDA Architecture Diagram](./etc/eda-architecture-diagram.png) + +## Programmatic Example of Event-Driven Architecture in Java + +The Event-Driven Architecture (EDA) pattern in this module is implemented using several key classes and concepts: + +* Event: This is an abstract class that represents an event. It's the base class for all types of events that can occur in the system. +* UserCreatedEvent and UserUpdatedEvent: These are concrete classes that extend the Event class. They represent specific types of events that can occur in the system, namely the creation and updating of a user. +* EventDispatcher: This class is responsible for dispatching events to their respective handlers. It maintains a mapping of event types to handlers. +* UserCreatedEventHandler and UserUpdatedEventHandler: These are the handler classes for the UserCreatedEvent and UserUpdatedEvent respectively. They contain the logic to execute when these events occur. + +First, we'll define the `Event` abstract class and the concrete event classes `UserCreatedEvent` and `UserUpdatedEvent`. + +```java +public abstract class Event { + // Event related properties and methods +} +``` + +```java +public class UserCreatedEvent extends Event { + private User user; + + public UserCreatedEvent(User user) { + this.user = user; + } + + public User getUser() { + return user; + } +} +``` + +```java +public class UserUpdatedEvent extends Event { + private User user; + + public UserUpdatedEvent(User user) { + this.user = user; + } + + public User getUser() { + return user; + } +} +``` + +Next, we'll define the event handlers `UserCreatedEventHandler` and `UserUpdatedEventHandler`. + +```java +public class UserCreatedEventHandler { + public void onUserCreated(UserCreatedEvent event) { + // Logic to execute when a UserCreatedEvent occurs + } +} +``` + +```java +public class UserUpdatedEventHandler { + public void onUserUpdated(UserUpdatedEvent event) { + // Logic to execute when a UserUpdatedEvent occurs + } +} +``` + +Then, we'll define the `EventDispatcher` class that is responsible for dispatching events to their respective handlers. + +```java +public class EventDispatcher { + private Map, List>> handlers = new HashMap<>(); + + public void registerHandler(Class eventType, Consumer handler) { + handlers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler::accept); + } + + public void dispatch(Event event) { + List> eventHandlers = handlers.get(event.getClass()); + if (eventHandlers != null) { + eventHandlers.forEach(handler -> handler.accept(event)); + } + } +} +``` + +Finally, we'll demonstrate how to use these classes in the main application. + +```java +public class App { + public static void main(String[] args) { + // Create an EventDispatcher + EventDispatcher dispatcher = new EventDispatcher(); + + // Register handlers for UserCreatedEvent and UserUpdatedEvent + dispatcher.registerHandler(UserCreatedEvent.class, new UserCreatedEventHandler()::onUserCreated); + dispatcher.registerHandler(UserUpdatedEvent.class, new UserUpdatedEventHandler()::onUserUpdated); + + // Create a User + User user = new User("iluwatar"); + + // Dispatch UserCreatedEvent + dispatcher.dispatch(new UserCreatedEvent(user)); + + // Dispatch UserUpdatedEvent + dispatcher.dispatch(new UserUpdatedEvent(user)); + } +} +``` + +Running the example produces the following console output: + +``` +22:15:19.997 [main] INFO com.iluwatar.eda.handler.UserCreatedEventHandler -- User 'iluwatar' has been Created! +22:15:20.000 [main] INFO com.iluwatar.eda.handler.UserUpdatedEventHandler -- User 'iluwatar' has been Updated! +``` + +This example demonstrates the Event-Driven Architecture pattern, where the occurrence of events drives the flow of the program. The system is designed to respond to events as they occur, which allows for a high degree of flexibility and decoupling between components. + +## When to Use the Event-Driven Architecture Pattern in Java -## Applicability Use an Event-driven architecture when -* you want to create a loosely coupled system -* you want to build a more responsive system -* you want a system that is easier to extend +* Systems where change detection is crucial. +* Applications that require real-time features and reactive systems. +* Systems needing to efficiently handle high throughput and sporadic loads. +* When integrating with microservices to enhance agility and scalability. -## Real world examples +## Real-World Applications of Event-Driven Architecture Pattern in Java +* Real-time data processing applications. +* Complex event processing systems in finance, such as stock trading platforms. +* IoT systems for dynamic device and information management. * Chargify, a billing API, exposes payment activity through various events (https://docs.chargify.com/api-events) * Amazon's AWS Lambda, lets you execute code in response to events such as changes to Amazon S3 buckets, updates to an Amazon DynamoDB table, or custom events generated by your applications or devices. (https://aws.amazon.com/lambda) * MySQL runs triggers based on events such as inserts and update events happening on database tables. -## Credits +## Benefits and Trade-offs of Event-Driven Architecture Pattern + +Benefits: + +* Scalability: Efficiently processes fluctuating loads with asynchronous processing. +* Flexibility and Agility: New event types and event consumers can be added with minimal impact on existing components. +* Responsiveness: Improves responsiveness by decoupling event processing and state management. + +Trade-offs: + +* Complexity in Tracking: Can be challenging to debug and track due to loose coupling and asynchronous behaviors. +* Dependency on Messaging Systems: Heavily relies on robust messaging infrastructures. +* Event Consistency: Requires careful design to handle event ordering and consistency. + +## Related Java Design Patterns + +* Microservices Architecture: Often used together with EDA to enhance agility and scalability. +* Publish/Subscribe: A common pattern used within EDA for messaging between event producers and consumers. + +## References and Credits -* [Event-driven architecture - Wikipedia](https://en.wikipedia.org/wiki/Event-driven_architecture) -* [What is an Event-Driven Architecture](https://aws.amazon.com/event-driven-architecture/) -* [Real World Applications/Event Driven Applications](https://wiki.haskell.org/Real_World_Applications/Event_Driven_Applications) -* [Event-driven architecture definition](http://searchsoa.techtarget.com/definition/event-driven-architecture) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3Q3vBki) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/49Aljz0) +* [Reactive Messaging Patterns With the Actor Model: Applications and Integration in Scala and Akka](https://amzn.to/3UeoBUa) +* [What is an Event-Driven Architecture (Amazon)](https://aws.amazon.com/event-driven-architecture/) diff --git a/event-driven-architecture/etc/eda-architecture-diagram.png b/event-driven-architecture/etc/eda-architecture-diagram.png new file mode 100644 index 000000000000..4e74bcf2e89a Binary files /dev/null and b/event-driven-architecture/etc/eda-architecture-diagram.png differ diff --git a/event-driven-architecture/pom.xml b/event-driven-architecture/pom.xml index eac1961709c1..8a9fc27876eb 100644 --- a/event-driven-architecture/pom.xml +++ b/event-driven-architecture/pom.xml @@ -34,6 +34,14 @@ event-driven-architecture + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java index c4aaa6bc3611..3b2ea6ae24b3 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java @@ -61,5 +61,4 @@ public static void main(String[] args) { dispatcher.dispatch(new UserCreatedEvent(user)); dispatcher.dispatch(new UserUpdatedEvent(user)); } - } diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java index 9a8834ff91ff..0add9446244e 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java @@ -30,10 +30,12 @@ /** * The {@link AbstractEvent} class serves as a base class for defining custom events happening with * your system. In this example we have two types of events defined. + * *
    - *
  • {@link UserCreatedEvent} - used when a user is created
  • - *
  • {@link UserUpdatedEvent} - used when a user is updated
  • + *
  • {@link UserCreatedEvent} - used when a user is created + *
  • {@link UserUpdatedEvent} - used when a user is updated *
+ * * Events can be distinguished using the {@link #getType() getType} method. */ public abstract class AbstractEvent implements Event { @@ -47,4 +49,4 @@ public abstract class AbstractEvent implements Event { public Class getType() { return getClass(); } -} \ No newline at end of file +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java index 245f79b4cbca..06cbe3122858 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java @@ -29,9 +29,9 @@ import lombok.RequiredArgsConstructor; /** - * The {@link UserCreatedEvent} should be dispatched whenever a user has been created. - * This class can be extended to contain details about the user has been created. - * In this example, the entire {@link User} object is passed on as data with the event. + * The {@link UserCreatedEvent} should be dispatched whenever a user has been created. This class + * can be extended to contain details about the user has been created. In this example, the entire + * {@link User} object is passed on as data with the event. */ @RequiredArgsConstructor @Getter diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java index 6fa1832cb4f9..5fd0e4a7d2ee 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java @@ -29,9 +29,9 @@ import lombok.RequiredArgsConstructor; /** - * The {@link UserUpdatedEvent} should be dispatched whenever a user has been updated. - * This class can be extended to contain details about the user has been updated. - * In this example, the entire {@link User} object is passed on as data with the event. + * The {@link UserUpdatedEvent} should be dispatched whenever a user has been updated. This class + * can be extended to contain details about the user has been updated. In this example, the entire + * {@link User} object is passed on as data with the event. */ @RequiredArgsConstructor @Getter diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java index 1d0fab9d5da7..da29f770eeea 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java @@ -43,12 +43,9 @@ public EventDispatcher() { * Links an {@link Event} to a specific {@link Handler}. * * @param eventType The {@link Event} to be registered - * @param handler The {@link Handler} that will be handling the {@link Event} + * @param handler The {@link Handler} that will be handling the {@link Event} */ - public void registerHandler( - Class eventType, - Handler handler - ) { + public void registerHandler(Class eventType, Handler handler) { handlers.put(eventType, handler); } @@ -64,5 +61,4 @@ public void dispatch(E event) { handler.onEvent(event); } } - } diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java index aace64302665..25f2354b5dd2 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java @@ -28,15 +28,12 @@ import com.iluwatar.eda.framework.Handler; import lombok.extern.slf4j.Slf4j; -/** - * Handles the {@link UserCreatedEvent} message. - */ +/** Handles the {@link UserCreatedEvent} message. */ @Slf4j public class UserCreatedEventHandler implements Handler { @Override public void onEvent(UserCreatedEvent event) { - LOGGER.info("User '{}' has been Created!", event.getUser().getUsername()); + LOGGER.info("User '{}' has been Created!", event.getUser().username()); } - } diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java index b33f373d8b1d..9947af48403c 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java @@ -28,14 +28,12 @@ import com.iluwatar.eda.framework.Handler; import lombok.extern.slf4j.Slf4j; -/** - * Handles the {@link UserUpdatedEvent} message. - */ +/** Handles the {@link UserUpdatedEvent} message. */ @Slf4j public class UserUpdatedEventHandler implements Handler { @Override public void onEvent(UserUpdatedEvent event) { - LOGGER.info("User '{}' has been Updated!", event.getUser().getUsername()); + LOGGER.info("User '{}' has been Updated!", event.getUser().username()); } } diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java index 57e132debb29..2b9e17693f14 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java @@ -26,16 +26,9 @@ import com.iluwatar.eda.event.UserCreatedEvent; import com.iluwatar.eda.event.UserUpdatedEvent; -import lombok.Getter; -import lombok.RequiredArgsConstructor; /** * This {@link User} class is a basic pojo used to demonstrate user data sent along with the {@link * UserCreatedEvent} and {@link UserUpdatedEvent} events. */ -@RequiredArgsConstructor -@Getter -public class User { - - private final String username; -} +public record User(String username) {} diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java index 17f3bddaa4a2..52e08eb9a76a 100644 --- a/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.eda; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Event Driven Architecture example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Event Driven Architecture example runs without errors. */ class AppTest { /** * Issue: Add at least one assertion to this test case. - *

- * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * + *

Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java index 88ba805a4a20..d3254255e714 100644 --- a/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java @@ -29,9 +29,7 @@ import com.iluwatar.eda.model.User; import org.junit.jupiter.api.Test; -/** - * {@link UserCreatedEventTest} tests and verifies {@link AbstractEvent} behaviour. - */ +/** {@link UserCreatedEventTest} tests and verifies {@link AbstractEvent} behaviour. */ class UserCreatedEventTest { /** diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java index b4cae33ab1a9..f92fde927109 100644 --- a/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java @@ -34,9 +34,7 @@ import com.iluwatar.eda.model.User; import org.junit.jupiter.api.Test; -/** - * Event Dispatcher unit tests to assert and verify correct event dispatcher behaviour - */ +/** Event Dispatcher unit tests to assert and verify correct event dispatcher behaviour */ class EventDispatcherTest { /** @@ -57,15 +55,14 @@ void testEventDriverPattern() { var userCreatedEvent = new UserCreatedEvent(user); var userUpdatedEvent = new UserUpdatedEvent(user); - //fire a userCreatedEvent and verify that userCreatedEventHandler has been invoked. + // fire a userCreatedEvent and verify that userCreatedEventHandler has been invoked. dispatcher.dispatch(userCreatedEvent); verify(userCreatedEventHandler).onEvent(userCreatedEvent); verify(dispatcher).dispatch(userCreatedEvent); - //fire a userCreatedEvent and verify that userUpdatedEventHandler has been invoked. + // fire a userCreatedEvent and verify that userUpdatedEventHandler has been invoked. dispatcher.dispatch(userUpdatedEvent); verify(userUpdatedEventHandler).onEvent(userUpdatedEvent); verify(dispatcher).dispatch(userUpdatedEvent); } - } diff --git a/event-queue/README.md b/event-queue/README.md index 4e2291e1f8d0..8b2991b1daf0 100644 --- a/event-queue/README.md +++ b/event-queue/README.md @@ -1,65 +1,49 @@ --- -title: Event Queue +title: "Event Queue Pattern in Java: Managing Concurrent Events Efficiently" +shortTitle: Event Queue +description: "Learn about the Event Queue design pattern in Java. Discover its best practices, examples, and how to implement it effectively in your Java projects." category: Concurrency language: en tag: - - Game programming + - Asynchronous + - Decoupling + - Messaging + - Event-driven + - Scalability --- -## Intent -The intent of the event queue design pattern, also known as message queues, is to decouple the relationship between the -sender and receiver of events within a system. By decoupling the two parties, they do not interact with the event queue -simultaneously. Essentially, the event queue handles and processes requests in an asynchronous manner, therefore, this -system can be described as a first in, first out design pattern model. Event Queue is a suitable pattern if there is a -resource with limited accessibility (i.e. Audio or Database), however, you need to provide access to all the requests -which seeks this resource. Upon accessing an event from the queue, the program also removes it from the queue. +## Also known as -![alt text](./etc/event-queue-model.png "Event Queue Visualised") +* Event Stream +* Message Queue -## Explanation +## Intent of Event Queue Design Pattern -Real world example +The Event Queue pattern is designed to manage tasks in an asynchronous manner, allowing applications to handle operations without blocking user interactions or other processes. This improves scalability and system performance. -> The modern emailing system is an example of the fundamental process behind the event-queue design pattern. When an email -> is sent, the sender continues their daily tasks without the necessity of an immediate response from the receiver. -> Additionally, the receiver has the freedom to access and process the email at their leisure. Therefore, this process -> decouples the sender and receiver so that they are not required to engage with the queue at the same time. +## Detailed Explanation of Event Queue Pattern with Real-World Examples +Real-world example + +> The modern emailing system is an example of the fundamental process behind the event-queue design pattern. When an email is sent, the sender continues their daily tasks without the necessity of an immediate response from the receiver. Additionally, the receiver has the freedom to access and process the email at their leisure. Therefore, this process decouples the sender and receiver so that they are not required to engage with the queue at the same time. In plain words -> The buffer between sender and receiver improves maintainability and scalability of a system. Event queues are typically -> used to organise and carry out interprocess communication (IPC). +> The buffer between sender and receiver improves maintainability and scalability of a system. Event queues are typically used to organize and carry out interprocess communication (IPC). Wikipedia says -> Message queues (also known as event queues) implement an asynchronous communication pattern between two or more processes/ ->threads whereby the sending and receiving party do not need to interact with the queue at the same time. - - -Key drawback +> Message queues (also known as event queues) implement an asynchronous communication pattern between two or more processes/threads whereby the sending and receiving party do not need to interact with the queue at the same time. -> As the event queue model decouples the sender-receiver relationship - this means that the event-queue design pattern is -> unsuitable for scenarios in which the sender requires a response. For example, this is a prominent feature within online -> multiplayer games, therefore, this approach require thorough consideration. +## Programmatic Example of Event Queue Pattern in Java -**Programmatic Example** +This example demonstrates an application using an event queue system to handle audio playback asynchronously. -Upon examining our event-queue example, here's the app which utilised an event queue system. +The `App` class sets up an instance of `Audio`, plays two sounds, and waits for user input to exit. It demonstrates how an event queue could be used to manage asynchronous operations in a software application. ```java -import javax.sound.sampled.UnsupportedAudioFileException; -import java.io.IOException; - public class App { - /** - * Program entry point. - * - * @param args command line args - * @throws IOException when there is a problem with the audio file loading - * @throws UnsupportedAudioFileException when the loaded audio file is unsupported - */ public static void main(String[] args) throws UnsupportedAudioFileException, IOException, InterruptedException { var audio = Audio.getInstance(); @@ -75,8 +59,7 @@ public class App { } ``` -Much of the design pattern is developed within the Audio class. Here we set instances, declare global variables and establish -the key methods used in the above runnable class. +The `Audio` class holds the singleton pattern implementation, manages a queue of audio play requests, and controls thread operations for asynchronous processing. ```java public class Audio { @@ -92,10 +75,7 @@ public class Audio { private final PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; - // Visible only for testing purposes - Audio() { - - } + Audio() {} public static Audio getInstance() { return INSTANCE; @@ -103,42 +83,32 @@ public class Audio { } ``` -The Audio class is also responsible for handling and setting the states of the thread, this is shown in the code segments -below. +These methods manage the lifecycle of the thread used to process the audio events. The `init` and `startThread` methods ensure the thread is properly initialized and running. ```java -/** - * This method stops the Update Method's thread and waits till service stops. - */ public synchronized void stopService() throws InterruptedException { - if (updateThread != null) {updateThread.interrupt();} - updateThread.join(); - updateThread = null; + if(updateThread != null) { + updateThread.interrupt(); + updateThread.join(); + updateThread = null; + } } -/** - * This method check the Update Method's thread is started. - * @return boolean - */ public synchronized boolean isServiceRunning() { - return updateThread != null && updateThread.isAlive();} + return updateThread != null && updateThread.isAlive(); +} -/** - * Starts the thread for the Update Method pattern if it was not started previously. Also when the - * thread is ready it initializes the indexes of the queue - */ public void init() { - if (updateThread == null) { + if(updateThread == null) { updateThread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { update(); - }});} - startThread(); + } + }); + startThread(); + } } -/** - * This is a synchronized thread starter. - */ private synchronized void startThread() { if (!updateThread.isAlive()) { updateThread.start(); @@ -148,48 +118,64 @@ private synchronized void startThread() { } ``` -New audio is added into our event queue in the playSound method found in the Audio class. The update method is then utilised -to retrieve an audio item from the queue and play it to the user. +The `playSound` method checks if the audio is already in the queue and either updates the volume or enqueues a new request, demonstrating the management of asynchronous tasks within the event queue. ```java public void playSound(AudioInputStream stream, float volume) { init(); - // Walk the pending requests. - for (var i = headIndex; i != tailIndex; i = (i + 1) % MAX_PENDING) { - var playMessage = getPendingAudio()[i]; - if (playMessage.getStream() == stream) { - // Use the larger of the two volumes. - playMessage.setVolume(Math.max(volume, playMessage.getVolume())); - // Don't need to enqueue. - return; - } + for(var i = headIndex; i != tailIndex; i = (i + 1) % MAX_PENDING) { + var playMessage = getPendingAudio()[i]; + if(playMessage.getStream() == stream) { + playMessage.setVolume(Math.max(volume, playMessage.getVolume())); + return; + } } getPendingAudio()[tailIndex] = new PlayMessage(stream, volume); tailIndex = (tailIndex + 1) % MAX_PENDING; } ``` -Within the Audio class are some more methods with assist the construction of the event-queue design patterns, they are -summarised below. - -- getAudioStream() = returns the input stream path of a file -- getPendingAudio() = returns the current event queue item - +## When to Use the Event Queue Pattern in Java -## Class diagram -![alt text](./etc/model.png "Event Queue") - -## Applicability - -Use the Event Queue Pattern when +This pattern is applicable in scenarios where tasks can be handled asynchronously outside the main application flow, such as in GUI applications, server-side event handling, or in systems that require task scheduling without immediate execution. In particular: * The sender does not require a response from the receiver. * You wish to decouple the sender & the receiver. * You want to process events asynchronously. * You have a limited accessibility resource and the asynchronous process is acceptable to reach that. -## Credits +## Real-World Applications of Event Queue Pattern in Java + +* Event-driven architectures +* GUI frameworks in Java (such as Swing and JavaFX) +* Server applications handling requests asynchronously + +## Benefits and Trade-offs of Event Queue Pattern + +Benefits: + +* Reduces system coupling. +* Enhances responsiveness of applications. +* Improves scalability by allowing event handling to be distributed across multiple threads or processors. + +Trade-offs: + +* Complexity in managing the event queue. +* Potential for difficult-to-track bugs due to asynchronous behavior. +* Overhead of maintaining event queue integrity and performance. +* As the event queue model decouples the sender-receiver relationship - this means that the event-queue design pattern is unsuitable for scenarios in which the sender requires a response. For example, this is a prominent feature within online multiplayer games, therefore, this approach requires thorough consideration. + +## Related Java Design Patterns + +* [Command](https://java-design-patterns.com/patterns/command/) (for encapsulating request processing in a command object) +* [Observer](https://java-design-patterns.com/patterns/observer/) (for subscribing and notifying changes to multiple observers) +* [Reactor](https://java-design-patterns.com/patterns/reactor/) (handles requests in a non-blocking event-driven manner similar to Event Queue) + +## References and Credits -* [Mihaly Kuprivecz - Event Queue] (http://gameprogrammingpatterns.com/event-queue.html) -* [Wikipedia - Message Queue] (https://en.wikipedia.org/wiki/Message_queue) -* [AWS - Message Queues] (https://aws.amazon.com/message-queue/) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3xzSlC2) +* [Game Programming Patterns](https://amzn.to/3K96fOn) +* [Java Concurrency in Practice](https://amzn.to/3Ji16mX) +* [Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3U2hlcy) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3xtVtPJ) +* [Event Queue (Game Programming Patterns)](http://gameprogrammingpatterns.com/event-queue.html) diff --git a/event-queue/etc/event-queue-model.png b/event-queue/etc/event-queue-model.png deleted file mode 100644 index c3f9c4ddb3d7..000000000000 Binary files a/event-queue/etc/event-queue-model.png and /dev/null differ diff --git a/event-queue/etc/model.png b/event-queue/etc/model.png deleted file mode 100644 index 925308ecd36a..000000000000 Binary files a/event-queue/etc/model.png and /dev/null differ diff --git a/event-queue/etc/model.ucls b/event-queue/etc/model.ucls deleted file mode 100644 index ed923014b40c..000000000000 --- a/event-queue/etc/model.ucls +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/event-queue/pom.xml b/event-queue/pom.xml index 3f2d1de623d6..4b7566df445b 100644 --- a/event-queue/pom.xml +++ b/event-queue/pom.xml @@ -34,6 +34,14 @@ event-queue + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/App.java b/event-queue/src/main/java/com/iluwatar/event/queue/App.java index 283884fea741..3ea957a5a9be 100644 --- a/event-queue/src/main/java/com/iluwatar/event/queue/App.java +++ b/event-queue/src/main/java/com/iluwatar/event/queue/App.java @@ -47,11 +47,11 @@ public class App { * Program entry point. * * @param args command line args - * @throws IOException when there is a problem with the audio file loading + * @throws IOException when there is a problem with the audio file loading * @throws UnsupportedAudioFileException when the loaded audio file is unsupported */ - public static void main(String[] args) throws UnsupportedAudioFileException, IOException, - InterruptedException { + public static void main(String[] args) + throws UnsupportedAudioFileException, IOException, InterruptedException { var audio = Audio.getInstance(); audio.playSound(audio.getAudioStream("./etc/Bass-Drum-1.wav"), -10.0f); audio.playSound(audio.getAudioStream("./etc/Closed-Hi-Hat-1.wav"), -8.0f); diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java index 242e6fbddd51..02bee71b86e2 100644 --- a/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java +++ b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java @@ -30,13 +30,10 @@ import javax.sound.sampled.AudioSystem; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * This class implements the Event Queue pattern. - * - * @author mkuprivecz - */ +/** This class implements the Event Queue pattern. */ @Slf4j public class Audio { private static final Audio INSTANCE = new Audio(); @@ -49,20 +46,16 @@ public class Audio { private volatile Thread updateThread = null; - private final PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; + @Getter private final PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; // Visible only for testing purposes - Audio() { - - } + Audio() {} public static Audio getInstance() { return INSTANCE; } - /** - * This method stops the Update Method's thread and waits till service stops. - */ + /** This method stops the Update Method's thread and waits till service stops. */ public synchronized void stopService() throws InterruptedException { if (updateThread != null) { updateThread.interrupt(); @@ -81,23 +74,23 @@ public synchronized boolean isServiceRunning() { } /** - * Starts the thread for the Update Method pattern if it was not started previously. Also when the - * thread is is ready initializes the indexes of the queue + * Starts the thread for the Update Method pattern if it was not started previously. Also, when + * the thread is ready initializes the indexes of the queue */ public void init() { if (updateThread == null) { - updateThread = new Thread(() -> { - while (!Thread.currentThread().isInterrupted()) { - update(); - } - }); + updateThread = + new Thread( + () -> { + while (!Thread.currentThread().isInterrupted()) { + update(); + } + }); } startThread(); } - /** - * This is a synchronized thread starter. - */ + /** This is a synchronized thread starter. */ private synchronized void startThread() { if (!updateThread.isAlive()) { updateThread.start(); @@ -129,9 +122,7 @@ public void playSound(AudioInputStream stream, float volume) { tailIndex = (tailIndex + 1) % MAX_PENDING; } - /** - * This method uses the Update Method pattern. It takes the audio from the queue and plays it - */ + /** This method uses the Update Method pattern. It takes the audio from the queue and plays it */ private void update() { // If there are no pending requests, do nothing. if (headIndex == tailIndex) { @@ -158,20 +149,10 @@ private void update() { * @param filePath is the path of the audio file * @return AudioInputStream * @throws UnsupportedAudioFileException when the audio file is not supported - * @throws IOException when the file is not readable + * @throws IOException when the file is not readable */ public AudioInputStream getAudioStream(String filePath) throws UnsupportedAudioFileException, IOException { return AudioSystem.getAudioInputStream(new File(filePath).getAbsoluteFile()); } - - /** - * Returns with the message array of the queue. - * - * @return PlayMessage[] - */ - public PlayMessage[] getPendingAudio() { - return pendingAudio; - } - } diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java b/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java index b221807004c7..60f328d79bd0 100644 --- a/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java +++ b/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java @@ -29,18 +29,12 @@ import lombok.Getter; import lombok.Setter; -/** - * The Event Queue's queue will store the instances of this class. - * - * @author mkuprivecz - */ +/** The Event Queue's queue will store the instances of this class. */ @Getter @AllArgsConstructor public class PlayMessage { private final AudioInputStream stream; - @Setter - private float volume; - + @Setter private float volume; } diff --git a/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java b/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java index 3014dce3333a..4f2abbdac895 100644 --- a/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java +++ b/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java @@ -32,12 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -/** - * Testing the Audio service of the Queue - * @author mkuprivecz - * - */ +/** Testing the Audio service of the Queue */ class AudioTest { private Audio audio; @@ -49,7 +44,8 @@ void createAudioInstance() { /** * Test here that the playSound method works correctly - * @throws UnsupportedAudioFileException when the audio file is not supported + * + * @throws UnsupportedAudioFileException when the audio file is not supported * @throws IOException when the file is not readable * @throws InterruptedException when the test is interrupted externally */ @@ -68,7 +64,8 @@ void testPlaySound() throws UnsupportedAudioFileException, IOException, Interrup /** * Test here that the Queue - * @throws UnsupportedAudioFileException when the audio file is not supported + * + * @throws UnsupportedAudioFileException when the audio file is not supported * @throws IOException when the file is not readable * @throws InterruptedException when the test is interrupted externally */ @@ -87,5 +84,4 @@ void testQueue() throws UnsupportedAudioFileException, IOException, InterruptedE // test that service is finished assertFalse(audio.isServiceRunning()); } - } diff --git a/event-sourcing/README.md b/event-sourcing/README.md index 423f892cb24a..4de1c2dd5025 100644 --- a/event-sourcing/README.md +++ b/event-sourcing/README.md @@ -1,32 +1,245 @@ --- -title: Event Sourcing +title: "Event Sourcing Pattern in Java: Building Immutable Historical Records for Robust Systems" +shortTitle: Event Sourcing +description: "Discover the Event Sourcing design pattern in Java. Learn how it stores state changes as events and benefits complex applications. See examples and explanations on Java Design Patterns." category: Architectural language: en tag: - - Performance - - Cloud distributed + - Decoupling + - Event-driven + - Fault tolerance + - Messaging + - Persistence + - Scalability + - Transactions +head: + - - meta + - name: keywords + content: --- -## Intent -Instead of storing just the current state of the data in a domain, use an append-only store to record the full series of actions taken on that data. The store acts as the system of record and can be used to materialize the domain objects. This can simplify tasks in complex domains, by avoiding the need to synchronize the data model and the business domain, while improving performance, scalability, and responsiveness. It can also provide consistency for transactional data, and maintain full audit trails and history that can enable compensating actions. +## Also known as -## Class diagram -![alt text](./etc/event-sourcing.png "Event Sourcing") +* Event Logging +* Event Streaming -## Applicability -Use the Event Sourcing pattern when +## Intent of Event Sourcing Design Pattern -* You need very high performance on persisting your application state even your application state has a complex relational data structure -* You need log of changes of your application state and ability to restore a state of any moment in time. -* You need to debug production problems by replaying the past events. +Event Sourcing is a design pattern that advocates for the storage of state changes as a sequence of events. Instead of updating a record in a database, all changes are stored as individual events which, when replayed, can recreate the state of an application at any point in time. -## Real world examples +## Detailed Explanation of Event Sourcing Pattern with Real-World Examples -* [The Lmax Architecture](https://martinfowler.com/articles/lmax.html) +Real-world example -## Credits +> Consider a banking application that tracks all transactions for user accounts. In this system, every deposit, withdrawal, and transfer is recorded as an individual event in an event log. Instead of simply updating the current account balance, each transaction is stored as a discrete event. This approach allows the bank to maintain a complete and immutable history of all account activities. If a discrepancy occurs, the bank can replay the sequence of events to reconstruct the account state at any point in time. This provides a robust audit trail, facilitates debugging, and supports features like transaction rollback and historical data analysis. -* [Martin Fowler - Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) -* [Event Sourcing in Microsoft's documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) -* [Reference 3: Introducing Event Sourcing](https://msdn.microsoft.com/en-us/library/jj591559.aspx) -* [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) +In plain words + +> Event Sourcing records all state changes as a sequence of immutable events to ensure reliable state reconstruction and auditability. + +[Microsoft's documentation](https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) says + +> The Event Sourcing pattern defines an approach to handling operations on data that's driven by a sequence of events, each of which is recorded in an append-only store. Application code sends a series of events that imperatively describe each action that has occurred on the data to the event store, where they're persisted. Each event represents a set of changes to the data (such as AddedItemToOrder). + +Architecture diagram + +![Event Sourcing Architecture Diagram](./etc/event-sourcing-architecture-diagram.png) + +## Programmatic Example of Event Sourcing Pattern in Java + +In the programmatic example we transfer some money between bank accounts. + +The `Event` class manages a queue of events and controls thread operations for asynchronous processing. Each event can be seen as a state change that affects the state of the system. + +```java +public class Event { + private static final Event INSTANCE = new Event(); + + private static final int MAX_PENDING = 16; + + private int headIndex; + + private int tailIndex; + + private volatile Thread updateThread = null; + + private final EventMessage[] pendingEvents = new EventMessage[MAX_PENDING]; + + Event() {} + + public static Event getInstance() { + return INSTANCE; + } +} +``` + +The `triggerEvent` method is where the events are created. Each time an event is triggered, it is created and added to the queue. This event contains the details of the state change. + +```java +public void triggerEvent(EventMessage eventMessage) { + init(); + for(var i = headIndex; i != tailIndex; i = (i + 1) % MAX_PENDING) { + var pendingEvent = getPendingEvents()[i]; + if(pendingEvent.equals(eventMessage)) { + return; + } + } + getPendingEvents()[tailIndex] = eventMessage; + tailIndex = (tailIndex + 1) % MAX_PENDING; +} +``` + +The `init` and `startThread` methods ensure the thread is properly initialized and running. The `stopService` method is used to stop the thread when it's no longer needed. These methods manage the lifecycle of the thread used to process the events. + +```java +public synchronized void stopService() throws InterruptedException { + if(updateThread != null) { + updateThread.interrupt(); + updateThread.join(); + updateThread = null; + } +} + +public synchronized boolean isServiceRunning() { + return updateThread != null && updateThread.isAlive(); +} + +public void init() { + if(updateThread == null) { + updateThread = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + update(); + } + }); + startThread(); + } +} + +private synchronized void startThread() { + if (!updateThread.isAlive()) { + updateThread.start(); + headIndex = 0; + tailIndex = 0; + } +} +``` + +The example is driven by the `App` class and its `main` method. + +```java +@Slf4j +public class App { + + public static final int ACCOUNT_OF_DAENERYS = 1; + + public static final int ACCOUNT_OF_JON = 2; + + public static void main(String[] args) { + + var eventProcessor = new DomainEventProcessor(new JsonFileJournal()); + + LOGGER.info("Running the system first time............"); + eventProcessor.reset(); + + LOGGER.info("Creating the accounts............"); + + eventProcessor.process(new AccountCreateEvent( + 0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); + + eventProcessor.process(new AccountCreateEvent( + 1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); + + LOGGER.info("Do some money operations............"); + + eventProcessor.process(new MoneyDepositEvent( + 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); + + eventProcessor.process(new MoneyDepositEvent( + 3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); + + eventProcessor.process(new MoneyTransferEvent( + 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, + ACCOUNT_OF_JON)); + + LOGGER.info("...............State:............"); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString()); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString()); + + LOGGER.info("At that point system had a shut down, state in memory is cleared............"); + AccountAggregate.resetState(); + + LOGGER.info("Recover the system by the events in journal file............"); + + eventProcessor = new DomainEventProcessor(new JsonFileJournal()); + eventProcessor.recover(); + + LOGGER.info("...............Recovered State:............"); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString()); + LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString()); + } +} +``` + +Running the example produces the following console output. + +``` +22:40:47.982 [main] INFO com.iluwatar.event.sourcing.app.App -- Running the system first time............ +22:40:47.984 [main] INFO com.iluwatar.event.sourcing.app.App -- Creating the accounts............ +22:40:47.985 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here. +22:40:48.089 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here. +22:40:48.090 [main] INFO com.iluwatar.event.sourcing.app.App -- Do some money operations............ +22:40:48.090 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here. +22:40:48.095 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here. +22:40:48.099 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here. +22:40:48.099 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here. +22:40:48.101 [main] INFO com.iluwatar.event.sourcing.app.App -- ...............State:............ +22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=1, owner='Daenerys Targaryen', money=90000} +22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=2, owner='Jon Snow', money=10100} +22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- At that point system had a shut down, state in memory is cleared............ +22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Recover the system by the events in journal file............ +22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- ...............Recovered State:............ +22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=1, owner='Daenerys Targaryen', money=90000} +22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=2, owner='Jon Snow', money=10100} +``` + +In this example, the state of the system can be recreated at any point by replaying the events in the queue. This is a key feature of the Event Sourcing pattern. + +## When to Use the Event Sourcing Pattern in Java + +* In systems where complete audit trails and historical changes are crucial. +* In complex domains where the state of an application is derived from a series of changes. +* For systems that benefit from high availability and scalability as Event Sourcing naturally lends itself to distributed systems. + +## Real-World Applications of Event Sourcing Pattern in Java + +* Financial systems to track transactions and account balances over time. +* E-commerce applications for order and inventory management. +* Real-time data processing systems where event consistency and replayability are critical. +* [The LMAX Architecture](https://martinfowler.com/articles/lmax.html) + +## Benefits and Trade-offs of Event Sourcing Pattern + +Benefits: + +* Auditability: Each change to the state is recorded, allowing for comprehensive auditing. +* Replayability: Events can be reprocessed to recreate historical states or move to new states. +* Scalability: Events can be processed asynchronously and in parallel. + +Trade-offs + +* Complexity: Implementing and maintaining an event-sourced system can introduce additional complexity. +* Event store size: Storing every state change can lead to large data volumes. +* Event versioning: Changes in event structure over time require careful handling to ensure system integrity. + +## Related Java Design Patterns + +* [Command Query Responsibility Segregation (CQRS)](https://java-design-patterns.com/patterns/cqrs/): Often used together with Event Sourcing to separate read and write responsibilities, enhancing performance and scalability. +* Snapshot: Used to optimize Event Sourcing systems by periodically saving the current state to avoid replaying a long sequence of events. + +## References and Credits + +* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/443WfiS) +* [Implementing Domain-Driven Design](https://amzn.to/3JgvA8V) +* [Patterns, Principles, and Practices of Domain-Driven Design](https://amzn.to/3VVhfWX) +* [Event Sourcing (Martin Fowler)](https://martinfowler.com/eaaDev/EventSourcing.html) +* [Event Sourcing pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) diff --git a/event-sourcing/etc/event-sourcing-architecture-diagram.png b/event-sourcing/etc/event-sourcing-architecture-diagram.png new file mode 100644 index 000000000000..2ad8b3531e56 Binary files /dev/null and b/event-sourcing/etc/event-sourcing-architecture-diagram.png differ diff --git a/event-sourcing/pom.xml b/event-sourcing/pom.xml index 569a5fceebd8..4cfd05d7adac 100644 --- a/event-sourcing/pom.xml +++ b/event-sourcing/pom.xml @@ -34,14 +34,28 @@ event-sourcing + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + com.fasterxml.jackson.core + jackson-core + 2.18.2 + com.fasterxml.jackson.core jackson-databind + 2.18.3 diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java index 4b90c6ae0bdb..a625e9ace9d3 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java @@ -49,19 +49,14 @@ * After the shut-down, system state is recovered by re-creating the past events from event * journals. Then state is printed so a user can view the last state is same with the state before a * system shut-down. - * - *

Created by Serdar Hamzaogullari on 06.08.2017. */ @Slf4j public class App { - /** - * The constant ACCOUNT OF DAENERYS. - */ + /** The constant ACCOUNT OF DAENERYS. */ public static final int ACCOUNT_OF_DAENERYS = 1; - /** - * The constant ACCOUNT OF JON. - */ + + /** The constant ACCOUNT OF JON. */ public static final int ACCOUNT_OF_JON = 2; /** @@ -78,23 +73,24 @@ public static void main(String[] args) { LOGGER.info("Creating the accounts............"); - eventProcessor.process(new AccountCreateEvent( - 0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); + eventProcessor.process( + new AccountCreateEvent(0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); - eventProcessor.process(new AccountCreateEvent( - 1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); + eventProcessor.process( + new AccountCreateEvent(1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); LOGGER.info("Do some money operations............"); - eventProcessor.process(new MoneyDepositEvent( - 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); + eventProcessor.process( + new MoneyDepositEvent( + 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); - eventProcessor.process(new MoneyDepositEvent( - 3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); + eventProcessor.process( + new MoneyDepositEvent(3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); - eventProcessor.process(new MoneyTransferEvent( - 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, - ACCOUNT_OF_JON)); + eventProcessor.process( + new MoneyTransferEvent( + 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, ACCOUNT_OF_JON)); LOGGER.info("...............State:............"); LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString()); @@ -112,6 +108,4 @@ public static void main(String[] args) { LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString()); LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString()); } - - } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java index 177348be86a3..89f4219d740b 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java @@ -68,9 +68,13 @@ public Account copy() { @Override public String toString() { return "Account{" - + "accountNo=" + accountNo - + ", owner='" + owner + '\'' - + ", money=" + money + + "accountNo=" + + accountNo + + ", owner='" + + owner + + '\'' + + ", money=" + + money + '}'; } @@ -111,7 +115,6 @@ public void handleEvent(MoneyDepositEvent moneyDepositEvent) { handleDeposit(moneyDepositEvent.getMoney(), moneyDepositEvent.isRealTime()); } - /** * Handles the AccountCreateEvent. * @@ -141,6 +144,4 @@ public void handleTransferFromEvent(MoneyTransferEvent moneyTransferEvent) { public void handleTransferToEvent(MoneyTransferEvent moneyTransferEvent) { handleDeposit(moneyTransferEvent.getMoney(), moneyTransferEvent.isRealTime()); } - - } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java index f3f4e78e6ab3..087752cbf9c6 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java @@ -46,15 +46,17 @@ public class AccountCreateEvent extends DomainEvent { /** * Instantiates a new Account created event. * - * @param sequenceId the sequence id + * @param sequenceId the sequence id * @param createdTime the created time - * @param accountNo the account no - * @param owner the owner + * @param accountNo the account no + * @param owner the owner */ @JsonCreator - public AccountCreateEvent(@JsonProperty("sequenceId") long sequenceId, + public AccountCreateEvent( + @JsonProperty("sequenceId") long sequenceId, @JsonProperty("createdTime") long createdTime, - @JsonProperty("accountNo") int accountNo, @JsonProperty("owner") String owner) { + @JsonProperty("accountNo") int accountNo, + @JsonProperty("owner") String owner) { super(sequenceId, createdTime, "AccountCreateEvent"); this.accountNo = accountNo; this.owner = owner; diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java index 139ddd576598..f39ebda43eb0 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java @@ -44,9 +44,6 @@ public abstract class DomainEvent implements Serializable { private final String eventClassName; private boolean realTime = true; - /** - * Process. - */ + /** Process. */ public abstract void process(); - } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java index 07e88bc0ea28..4f80ec6b6dfc 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java @@ -47,15 +47,17 @@ public class MoneyDepositEvent extends DomainEvent { /** * Instantiates a new Money deposit event. * - * @param sequenceId the sequence id + * @param sequenceId the sequence id * @param createdTime the created time - * @param accountNo the account no - * @param money the money + * @param accountNo the account no + * @param money the money */ @JsonCreator - public MoneyDepositEvent(@JsonProperty("sequenceId") long sequenceId, + public MoneyDepositEvent( + @JsonProperty("sequenceId") long sequenceId, @JsonProperty("createdTime") long createdTime, - @JsonProperty("accountNo") int accountNo, @JsonProperty("money") BigDecimal money) { + @JsonProperty("accountNo") int accountNo, + @JsonProperty("money") BigDecimal money) { super(sequenceId, createdTime, "MoneyDepositEvent"); this.money = money; this.accountNo = accountNo; @@ -63,8 +65,9 @@ public MoneyDepositEvent(@JsonProperty("sequenceId") long sequenceId, @Override public void process() { - var account = Optional.ofNullable(AccountAggregate.getAccount(accountNo)) - .orElseThrow(() -> new RuntimeException("Account not found")); + var account = + Optional.ofNullable(AccountAggregate.getAccount(accountNo)) + .orElseThrow(() -> new RuntimeException("Account not found")); account.handleEvent(this); } } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java index 3b4fa1ed1bb0..e6e257dded04 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java @@ -48,16 +48,18 @@ public class MoneyTransferEvent extends DomainEvent { /** * Instantiates a new Money transfer event. * - * @param sequenceId the sequence id - * @param createdTime the created time - * @param money the money + * @param sequenceId the sequence id + * @param createdTime the created time + * @param money the money * @param accountNoFrom the account no from - * @param accountNoTo the account no to + * @param accountNoTo the account no to */ @JsonCreator - public MoneyTransferEvent(@JsonProperty("sequenceId") long sequenceId, + public MoneyTransferEvent( + @JsonProperty("sequenceId") long sequenceId, @JsonProperty("createdTime") long createdTime, - @JsonProperty("money") BigDecimal money, @JsonProperty("accountNoFrom") int accountNoFrom, + @JsonProperty("money") BigDecimal money, + @JsonProperty("accountNoFrom") int accountNoFrom, @JsonProperty("accountNoTo") int accountNoTo) { super(sequenceId, createdTime, "MoneyTransferEvent"); this.money = money; @@ -67,10 +69,12 @@ public MoneyTransferEvent(@JsonProperty("sequenceId") long sequenceId, @Override public void process() { - var accountFrom = Optional.ofNullable(AccountAggregate.getAccount(accountNoFrom)) - .orElseThrow(() -> new RuntimeException("Account not found " + accountNoFrom)); - var accountTo = Optional.ofNullable(AccountAggregate.getAccount(accountNoTo)) - .orElseThrow(() -> new RuntimeException("Account not found " + accountNoTo)); + var accountFrom = + Optional.ofNullable(AccountAggregate.getAccount(accountNoFrom)) + .orElseThrow(() -> new RuntimeException("Account not found " + accountNoFrom)); + var accountTo = + Optional.ofNullable(AccountAggregate.getAccount(accountNoTo)) + .orElseThrow(() -> new RuntimeException("Account not found " + accountNoTo)); accountFrom.handleTransferFromEvent(this); accountTo.handleTransferToEvent(this); } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java index cdfe44e336b2..6b0658b40a43 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java @@ -50,16 +50,12 @@ public void process(DomainEvent domainEvent) { eventJournal.write(domainEvent); } - /** - * Reset. - */ + /** Reset. */ public void reset() { eventJournal.reset(); } - /** - * Recover. - */ + /** Recover. */ public void recover() { DomainEvent domainEvent; while ((domainEvent = eventJournal.readNext()) != null) { diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java index 49bc89582a13..3b4cdfd3ec3e 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java @@ -1,12 +1,34 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.event.sourcing.processor; import com.iluwatar.event.sourcing.event.DomainEvent; import java.io.File; import lombok.extern.slf4j.Slf4j; -/** - * Base class for Journaling implementations. - */ +/** Base class for Journaling implementations. */ @Slf4j public abstract class EventJournal { @@ -19,9 +41,7 @@ public abstract class EventJournal { */ abstract void write(DomainEvent domainEvent); - /** - * Reset. - */ + /** Reset. */ void reset() { if (file.delete()) { LOGGER.info("File cleared successfully............"); diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java index cfde566dc2fb..106dbf95e93a 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java @@ -53,14 +53,13 @@ public class JsonFileJournal extends EventJournal { private final List events = new ArrayList<>(); private int index = 0; - /** - * Instantiates a new Json file journal. - */ + /** Instantiates a new Json file journal. */ public JsonFileJournal() { file = new File("Journal.json"); if (file.exists()) { - try (var input = new BufferedReader( - new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { + try (var input = + new BufferedReader( + new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { String line; while ((line = input.readLine()) != null) { events.add(line); @@ -73,7 +72,6 @@ public JsonFileJournal() { } } - /** * Write. * @@ -82,8 +80,9 @@ public JsonFileJournal() { @Override public void write(DomainEvent domainEvent) { var mapper = new ObjectMapper(); - try (var output = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) { + try (var output = + new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) { var eventString = mapper.writeValueAsString(domainEvent); output.write(eventString + "\r\n"); } catch (IOException e) { @@ -91,7 +90,6 @@ public void write(DomainEvent domainEvent) { } } - /** * Read the next domain event. * @@ -109,12 +107,13 @@ public DomainEvent readNext() { try { var jsonElement = mapper.readTree(event); var eventClassName = jsonElement.get("eventClassName").asText(); - domainEvent = switch (eventClassName) { - case "AccountCreateEvent" -> mapper.treeToValue(jsonElement, AccountCreateEvent.class); - case "MoneyDepositEvent" -> mapper.treeToValue(jsonElement, MoneyDepositEvent.class); - case "MoneyTransferEvent" -> mapper.treeToValue(jsonElement, MoneyTransferEvent.class); - default -> throw new RuntimeException("Journal Event not recognized"); - }; + domainEvent = + switch (eventClassName) { + case "AccountCreateEvent" -> mapper.treeToValue(jsonElement, AccountCreateEvent.class); + case "MoneyDepositEvent" -> mapper.treeToValue(jsonElement, MoneyDepositEvent.class); + case "MoneyTransferEvent" -> mapper.treeToValue(jsonElement, MoneyTransferEvent.class); + default -> throw new RuntimeException("Journal Event not recognized"); + }; } catch (JsonProcessingException jsonProcessingException) { throw new RuntimeException("Failed to convert JSON"); } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java index 4948fcd30368..80253036f223 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java @@ -38,8 +38,7 @@ public class AccountAggregate { private static Map accounts = new HashMap<>(); - private AccountAggregate() { - } + private AccountAggregate() {} /** * Put account. @@ -57,15 +56,10 @@ public static void putAccount(Account account) { * @return the copy of the account or null if not found */ public static Account getAccount(int accountNo) { - return Optional.of(accountNo) - .map(accounts::get) - .map(Account::copy) - .orElse(null); + return Optional.of(accountNo).map(accounts::get).map(Account::copy).orElse(null); } - /** - * Reset state. - */ + /** Reset state. */ public static void resetState() { accounts = new HashMap<>(); } diff --git a/event-sourcing/src/test/java/IntegrationTest.java b/event-sourcing/src/test/java/IntegrationTest.java index c737bd0da43d..89c7a77fe91e 100644 --- a/event-sourcing/src/test/java/IntegrationTest.java +++ b/event-sourcing/src/test/java/IntegrationTest.java @@ -40,46 +40,41 @@ /** * Integration Test for Event-Sourcing state recovery - *

- * Created by Serdar Hamzaogullari on 19.08.2017. + * + *

Created by Serdar Hamzaogullari on 19.08.2017. */ class IntegrationTest { - /** - * The Domain event processor. - */ + /** The Domain event processor. */ private DomainEventProcessor eventProcessor; - /** - * Initialize. - */ + /** Initialize. */ @BeforeEach void initialize() { eventProcessor = new DomainEventProcessor(new JsonFileJournal()); } - /** - * Test state recovery. - */ + /** Test state recovery. */ @Test void testStateRecovery() { eventProcessor.reset(); - eventProcessor.process(new AccountCreateEvent( - 0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); + eventProcessor.process( + new AccountCreateEvent(0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); - eventProcessor.process(new AccountCreateEvent( - 1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); + eventProcessor.process( + new AccountCreateEvent(1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); - eventProcessor.process(new MoneyDepositEvent( - 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); + eventProcessor.process( + new MoneyDepositEvent( + 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); - eventProcessor.process(new MoneyDepositEvent( - 3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); + eventProcessor.process( + new MoneyDepositEvent(3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); - eventProcessor.process(new MoneyTransferEvent( - 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, - ACCOUNT_OF_JON)); + eventProcessor.process( + new MoneyTransferEvent( + 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, ACCOUNT_OF_JON)); var accountOfDaenerysBeforeShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS); var accountOfJonBeforeShotDown = AccountAggregate.getAccount(ACCOUNT_OF_JON); @@ -92,9 +87,8 @@ void testStateRecovery() { var accountOfDaenerysAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS); var accountOfJonAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_JON); - assertEquals(accountOfDaenerysBeforeShotDown.getMoney(), - accountOfDaenerysAfterShotDown.getMoney()); + assertEquals( + accountOfDaenerysBeforeShotDown.getMoney(), accountOfDaenerysAfterShotDown.getMoney()); assertEquals(accountOfJonBeforeShotDown.getMoney(), accountOfJonAfterShotDown.getMoney()); } - } diff --git a/execute-around/README.md b/execute-around/README.md index 613f45ed4329..aea3ae9e733f 100644 --- a/execute-around/README.md +++ b/execute-around/README.md @@ -1,44 +1,57 @@ --- -title: Execute Around -category: Idiom +title: "Execute Around Pattern in Java: Encapsulating Pre and Post Execution Steps" +shortTitle: Execute Around +description: "Explore the Execute Around Pattern in Java with detailed explanations, real-world examples, and best practices. Learn how to implement this design pattern to streamline resource management." +category: Behavioral language: en tag: - - Extensibility + - Closure + - Code simplification + - Encapsulation + - Functional decomposition + - Resource management +head: + - - meta + - name: keywords + content: --- -## Intent +## Also known as -Execute Around idiom frees the user from certain actions that should always be executed before and -after the business method. A good example of this is resource allocation and deallocation leaving -the user to specify only what to do with the resource. +* Around Method Pattern +* Resource Block Management -## Explanation +## Intent of Execute Around Design Pattern + +Real-world business applications often require executing necessary operations before and after the business method invocation. The Execute Around Pattern in Java provides a way to encapsulate these operations, enhancing code readability and reusability. + +## Detailed Explanation of Execute Around Pattern with Real-World Examples Real-world example -> A class needs to be provided for writing text strings to files. To make it easy for -> the user, the service class opens and closes the file automatically. The user only has to -> specify what is written into which file. +> A real-world analogy for the Execute Around pattern can be found in the use of rental cars. When you rent a car, the rental company handles all the setup (cleaning the car, filling it with gas, ensuring it's in good condition) and cleanup (checking the car back in, inspecting it for damage, refueling it if necessary) processes for you. As a customer, you simply use the car for your intended purpose without worrying about the setup and cleanup. This pattern of abstracting away the repetitive tasks around the main operation is similar to the Execute Around pattern in software, where the setup and cleanup of resources are handled by a reusable method, allowing the main logic to be executed seamlessly. In plain words -> Execute Around idiom handles boilerplate code before and after business method. +> Execute Around idiom handles boilerplate code before and after business method. [Stack Overflow](https://stackoverflow.com/questions/341971/what-is-the-execute-around-idiom) says -> Basically it's the pattern where you write a method to do things which are always required, e.g. -> resource allocation and clean-up, and make the caller pass in "what we want to do with the -> resource". +> Basically it's the pattern where you write a method to do things which are always required, e.g. resource allocation and clean-up, and make the caller pass in "what we want to do with the resource". + +## Programmatic Example of Execute Around Pattern in Java -**Programmatic Example** +The Execute Around Pattern is a design pattern that is widely used in Java programming to manage resource allocation and deallocation. It ensures that important setup and cleanup operations are performed reliably around a core business operation. This pattern is particularly useful for resource management, such as handling files, databases, or network connections in Java applications. -`SimpleFileWriter` class implements the Execute Around idiom. It takes `FileWriterAction` as a -constructor argument allowing the user to specify what gets written into the file. +A class needs to be provided for writing text strings to files. To make it easy for the user, the service class opens and closes the file automatically. The user only has to specify what is written into which file. + +`SimpleFileWriter` class implements the Execute Around idiom. It takes `FileWriterAction` as a constructor argument allowing the user to specify what gets written into the file. ```java + @FunctionalInterface public interface FileWriterAction { - void writeFile(FileWriter writer) throws IOException; + void writeFile(FileWriter writer) throws IOException; } @Slf4j @@ -54,18 +67,21 @@ public class SimpleFileWriter { } ``` -The following code demonstrates how `SimpleFileWriter` is used. `Scanner` is used to print the file -contents after the writing finishes. +The following code demonstrates how `SimpleFileWriter` is used. `Scanner` is used to print the file contents after the writing finishes. ```java -FileWriterAction writeHello = writer -> { - writer.write("Gandalf was here"); -}; -new SimpleFileWriter("testfile.txt", writeHello); - -var scanner = new Scanner(new File("testfile.txt")); -while (scanner.hasNextLine()) { -LOGGER.info(scanner.nextLine()); + public static void main(String[] args) throws IOException { + + // create the file writer and execute the custom action + FileWriterAction writeHello = writer -> writer.write("Gandalf was here"); + new SimpleFileWriter("testfile.txt", writeHello); + + // print the file contents + try (var scanner = new Scanner(new File("testfile.txt"))) { + while (scanner.hasNextLine()) { + LOGGER.info(scanner.nextLine()); + } + } } ``` @@ -78,16 +94,43 @@ Here's the console output. 21:18:07.199 [main] INFO com.iluwatar.execute.around.App - Gandalf was here ``` -## Class diagram +## When to Use the Execute Around Pattern in Java + +When to use the Execute Around Pattern in Java: + +* Useful in scenarios requiring repetitive setup and cleanup activities, particularly in resource management (e.g., files, network connections, database sessions). +* Ideal for ensuring proper resource handling and cleanup in the face of exceptions, ensuring resources do not leak. +* Suitable in any Java application where the same preparation and finalization steps are executed around varying core functionalities. + +## Real-World Applications of Execute Around Pattern in Java + +In real-world Java applications, the Execute Around Pattern is applied in these scenarios: + +* Java's try-with-resources statement, which ensures that resources are closed after execution regardless of whether an exception was thrown. +* Frameworks like Spring for managing database transactions, where predefined cleanup or rollback operations are performed depending on the execution outcome. + +## Benefits and Trade-offs of Execute Around Pattern + +Implementing the Execute Around Pattern in Java offers several benefits and trade-offs. + +Benefits: + +* Reduces boilerplate code by abstracting routine setup and cleanup tasks. +* Increases code clarity and maintainability by separating business logic from resource management. +* Ensures robustness by automatically handling resource cleanup, even in error situations. -![alt text](./etc/execute-around.png "Execute Around") +Trade-offs: -## Applicability +* Introduces additional abstraction layers, which might increase complexity and obscure control flow for some developers. +* May require more sophisticated understanding of closures and functional interfaces in Java. -Use the Execute Around idiom when +## Related Java Design Patterns -* An API requires methods to be called in pairs such as open/close or allocate/deallocate. +* [Template Method](https://java-design-patterns.com/patterns/template-method/): Similar in concept but differs in that it uses inheritance and abstract classes, while Execute Around typically uses interfaces and lambdas. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Shares the concept of adding functionality around a core component; can be extended to wrap additional behaviors dynamically. -## Credits +## References and Credits -* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [Effective Java](https://amzn.to/4aDdWbs) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3vUGApm) +* [Functional Programming in Java](https://amzn.to/3JUIc5Q) diff --git a/execute-around/pom.xml b/execute-around/pom.xml index d5e01d31cf0d..83a9372b4fe3 100644 --- a/execute-around/pom.xml +++ b/execute-around/pom.xml @@ -34,6 +34,14 @@ execute-around + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/App.java b/execute-around/src/main/java/com/iluwatar/execute/around/App.java index a956dd57a0cc..9dc88fbfdad9 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/App.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/App.java @@ -30,26 +30,22 @@ import lombok.extern.slf4j.Slf4j; /** - * The Execute Around idiom specifies executable code before and after a method. Typically - * the idiom is used when the API has methods to be executed in pairs, such as resource + * The Execute Around idiom specifies executable code before and after a method. Typically, the + * idiom is used when the API has methods to be executed in pairs, such as resource * allocation/deallocation or lock acquisition/release. * - *

In this example, we have {@link SimpleFileWriter} class that opens and closes the file for - * the user. The user specifies only what to do with the file by providing the {@link - * FileWriterAction} implementation. + *

In this example, we have {@link SimpleFileWriter} class that opens and closes the file for the + * user. The user specifies only what to do with the file by providing the {@link FileWriterAction} + * implementation. */ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) throws IOException { // create the file writer and execute the custom action - FileWriterAction writeHello = writer -> { - writer.write("Gandalf was here"); - }; + FileWriterAction writeHello = writer -> writer.write("Gandalf was here"); new SimpleFileWriter("testfile.txt", writeHello); // print the file contents diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java b/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java index 5585bc4fac45..0627177a31f1 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java @@ -27,12 +27,9 @@ import java.io.FileWriter; import java.io.IOException; -/** - * Interface for specifying what to do with the file resource. - */ +/** Interface for specifying what to do with the file resource. */ @FunctionalInterface public interface FileWriterAction { void writeFile(FileWriter writer) throws IOException; - } diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java index 66c844aa4b77..d3bd54d4ec6b 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java @@ -35,9 +35,7 @@ @Slf4j public class SimpleFileWriter { - /** - * Constructor. - */ + /** Constructor. */ public SimpleFileWriter(String filename, FileWriterAction action) throws IOException { LOGGER.info("Opening the file"); try (var writer = new FileWriter(filename)) { diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java index 5e54732fd58b..1e06beb12d3b 100644 --- a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java +++ b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java @@ -24,22 +24,19 @@ */ package com.iluwatar.execute.around; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import java.io.File; -import java.io.IOException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Tests execute-around example. - */ +/** Tests execute-around example. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } @BeforeEach diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java index e9ca45f05362..60c4ecb6bdba 100644 --- a/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java +++ b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java @@ -31,22 +31,18 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import lombok.SneakyThrows; import org.junit.Rule; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; import org.junit.rules.TemporaryFolder; -/** - * Date: 12/12/15 - 3:21 PM - * - * @author Jeroen Meulemeester - */ +/** SimpleFileWriterTest */ @EnableRuleMigrationSupport class SimpleFileWriterTest { - @Rule - public final TemporaryFolder testFolder = new TemporaryFolder(); + @Rule public final TemporaryFolder testFolder = new TemporaryFolder(); @Test void testWriterNotNull() throws Exception { @@ -75,14 +71,18 @@ void testContentsAreWrittenToFile() throws Exception { } @Test + @SneakyThrows void testRipplesIoExceptionOccurredWhileWriting() { var message = "Some error"; - assertThrows(IOException.class, () -> { - final var temporaryFile = this.testFolder.newFile(); - new SimpleFileWriter(temporaryFile.getPath(), writer -> { - throw new IOException(message); - }); - }, message); + final var temporaryFile = this.testFolder.newFile(); + assertThrows( + IOException.class, + () -> + new SimpleFileWriter( + temporaryFile.getPath(), + writer -> { + throw new IOException("error"); + }), + message); } - } diff --git a/extension-objects/README.md b/extension-objects/README.md index 5ecc08efb394..058fce65402c 100644 --- a/extension-objects/README.md +++ b/extension-objects/README.md @@ -1,21 +1,29 @@ --- -title: Extension objects -category: Behavioral +title: "Extension Objects Pattern in Java: Enhancing Object Functionality Flexibly" +shortTitle: Extension Objects +description: "Learn about the Extension Objects Design Pattern in Java. Understand its purpose, benefits, and implementation with examples to enhance your software design." +category: Structural language: en tag: - - Extensibility + - Encapsulation + - Extensibility + - Object composition + - Polymorphism --- -# Extention Objects Pattern +## Also known as -## Intent -Anticipate that an object’s interface needs to be extended in the future. Additional -interfaces are defined by extension objects. +* Interface Extensions + +## Intent of Extension Objects Design Pattern + +The Extension Objects pattern allows for the flexible extension of an object's behavior without modifying its structure, by attaching additional objects that can dynamically add new functionality. + +## Detailed Explanation of Extension Objects Pattern with Real-World Examples -## Explanation Real-world example -> Suppose you are developing a Java-based game for a client, and in the middle of the development process, new features are suggested. The Extension Objects pattern empowers your program to adapt to unforeseen changes with minimal refactoring, especially when integrating additional functionalities into your project. +> An analogous real-world example of the Extension Objects design pattern can be found in modular kitchen appliances. Consider a base blender unit to which different attachments can be added, such as a food processor, juicer, or grinder. Each attachment adds new functionality to the blender without altering the base unit itself. Users can dynamically switch between different functionalities based on their current needs, making the blender highly versatile and adaptable to various tasks. This mirrors the Extension Objects pattern in software, where new functionalities are added to an object dynamically and contextually, enhancing flexibility and reuse. In plain words @@ -25,112 +33,153 @@ Wikipedia says > In object-oriented computer programming, an extension objects pattern is a design pattern added to an object after the original object was compiled. The modified object is often a class, a prototype or a type. Extension object patterns are features of some object-oriented programming languages. There is no syntactic difference between calling an extension method and calling a method declared in the type definition. -**Programmatic example** +## Programmatic Example of Extension Objects Pattern in Java + +The Extension Objects pattern allows for the flexible extension of an object's behavior without modifying its structure, by attaching additional objects that can dynamically add new functionality. + +In this Java implementation, we have three types of units: `SoldierUnit`, `SergeantUnit`, and `CommanderUnit`. Each unit can have extensions that provide additional functionality. The extensions are `SoldierExtension`, `SergeantExtension`, and `CommanderExtension`. -The aim of utilising the Extension Objects pattern is to implement new features/functionality without having to refactor every class. -The following examples shows utilising this pattern for an Enemy class extending Entity within a game: +The `Unit` class is the base class for all units. It has a method `getUnitExtension` that returns an extension object based on the extension name. -Primary App class to execute our program from. ```java -public class App { - public static void main(String[] args) { - Entity enemy = new Enemy("Enemy"); - checkExtensionsForEntity(enemy); - } +public abstract class Unit { + private String name; - private static void checkExtensionsForEntity(Entity entity) { - Logger logger = Logger.getLogger(App.class.getName()); - String name = entity.getName(); - Function func = (e) -> () -> logger.info(name + " without " + e); + protected Unit(String name) { + this.name = name; + } - String extension = "EnemyExtension"; - Optional.ofNullable(entity.getEntityExtension(extension)) - .map(e -> (EnemyExtension) e) - .ifPresentOrElse(EnemyExtension::extendedAction, func.apply(extension)); - } + public String getName() { + return name; + } + + public abstract UnitExtension getUnitExtension(String extensionName); } ``` -Enemy class with initial actions and extensions. -```java -class Enemy extends Entity { - public Enemy(String name) { - super(name); - } - @Override - protected void performInitialAction() { - super.performInitialAction(); - System.out.println("Enemy wants to attack you."); - } +The `UnitExtension` interface is the base interface for all extensions. Each specific extension will implement this interface. - @Override - public EntityExtension getEntityExtension(String extensionName) { - if (extensionName.equals("EnemyExtension")) { - return Optional.ofNullable(entityExtension).orElseGet(EnemyExtension::new); - } - return super.getEntityExtension(extensionName); - } +```java +public interface UnitExtension { + String getName(); } ``` -EnemyExtension class with overriding extendAction() method. + +The `SoldierUnit` class is a specific type of unit. It overrides the `getUnitExtension` method to return a `SoldierExtension` object. + ```java -class EnemyExtension implements EntityExtension { - @Override - public void extendedAction() { - System.out.println("Enemy has advanced towards you!"); +public class SoldierUnit extends Unit { + public SoldierUnit(String name) { + super(name); + } + + @Override + public UnitExtension getUnitExtension(String extensionName) { + if ("SoldierExtension".equals(extensionName)) { + return new SoldierExtension(this); } + return null; + } } ``` -Entity class which will be extended by Enemy. -```java -class Entity { - private String name; - protected EntityExtension entityExtension; - public Entity(String name) { - this.name = name; - performInitialAction(); - } +The `SoldierExtension` class is a specific type of extension. It implements the `UnitExtension` interface and provides additional functionality for the `SoldierUnit`. - protected void performInitialAction() { - System.out.println(name + " performs the initial action."); - } +```java +public class SoldierExtension implements UnitExtension { + private SoldierUnit unit; - public EntityExtension getEntityExtension(String extensionName) { - return null; - } + public SoldierExtension(SoldierUnit unit) { + this.unit = unit; + } - public String getName() { - return name; - } + @Override + public String getName() { + return "SoldierExtension"; + } + + public void soldierReady() { + // additional functionality for SoldierUnit + } } ``` -EntityExtension interface to be used by EnemyExtension. + +In the `main` application, we create different types of units and check for each unit to have an extension. If the extension exists, we call the specific method on the extension object. + ```java -interface EntityExtension { - void extendedAction(); +public class App { + public static void main(String[] args) { + var soldierUnit = new SoldierUnit("SoldierUnit1"); + var sergeantUnit = new SergeantUnit("SergeantUnit1"); + var commanderUnit = new CommanderUnit("CommanderUnit1"); + + checkExtensionsForUnit(soldierUnit); + checkExtensionsForUnit(sergeantUnit); + checkExtensionsForUnit(commanderUnit); + } + + private static void checkExtensionsForUnit(Unit unit) { + var extension = "SoldierExtension"; + Optional.ofNullable(unit.getUnitExtension(extension)) + .map(e -> (SoldierExtension) e) + .ifPresentOrElse(SoldierExtension::soldierReady, () -> System.out.println(unit.getName() + " without " + extension)); + } } ``` -Program output: -```markdown -Enemy performs the initial action. -Enemy wants to attack you. -Enemy has advanced towards you! + +This produces the following console output. + ``` -In this example, the Extension Objects pattern allows the enemy entity to perform unique initial actions and advanced actions when specific extensions are applied. This pattern provides flexibility and extensibility to the codebase while minimizing the need for major code changes. +22:58:03.779 [main] INFO concreteextensions.Soldier -- [Soldier] SoldierUnit1 is ready! +22:58:03.781 [main] INFO App -- SoldierUnit1 without SergeantExtension +22:58:03.782 [main] INFO App -- SoldierUnit1 without CommanderExtension +22:58:03.782 [main] INFO App -- SergeantUnit1 without SoldierExtension +22:58:03.783 [main] INFO concreteextensions.Sergeant -- [Sergeant] SergeantUnit1 is ready! +22:58:03.783 [main] INFO App -- SergeantUnit1 without CommanderExtension +22:58:03.783 [main] INFO App -- CommanderUnit1 without SoldierExtension +22:58:03.783 [main] INFO App -- CommanderUnit1 without SergeantExtension +22:58:03.783 [main] INFO concreteextensions.Commander -- [Commander] CommanderUnit1 is ready! +``` + +This example demonstrates how the Extension Objects pattern allows for the flexible extension of an object's behavior without modifying its structure. +## Detailed Explanation of Extension Objects Pattern with Real-World Examples -## Class diagram ![Extension_objects](./etc/extension_obj.png "Extension objects") -## Applicability -Use the Extension Objects pattern when: +## When to Use the Extension Objects Pattern in Java -* you need to support the addition of new or unforeseen interfaces to existing classes and you don't want to impact clients that don't need this new interface. Extension Objects lets you keep related operations together by defining them in a separate class -* a class representing a key abstraction plays different roles for different clients. The number of roles the class can play should be open-ended. There is a need to preserve the key abstraction itself. For example, a customer object is still a customer object even if different subsystems view it differently. -* a class should be extensible with new behavior without subclassing from it. +This pattern is applicable in scenarios where an object's functionality needs to be extended at runtime, avoiding the complications of subclassing. It's particularly useful in systems where object capabilities need to be augmented post-deployment, or where the capabilities might vary significantly across instances. -## Real world examples +## Real-World Applications of Extension Objects Pattern in Java +* Extending services in an application server without altering existing code. +* Plugins in IDEs like IntelliJ IDEA or Eclipse to add features to the base application. +* Enabling additional features in enterprise software based on license levels. * [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc) * [Object Linking and Embedding](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding) + +## Benefits and Trade-offs of Extension Objects Pattern + +Benefits: + +* Enhances flexibility by allowing dynamic extension of an object's capabilities. +* Promotes loose coupling between the base object and its extensions. +* Supports the [Open/Closed Principle](https://java-design-patterns.com/principles/#open-closed-principle) by keeping the object open for extension but closed for modification. + +Trade-offs: + +* Can increase complexity due to the management of extension objects. +* May introduce performance overhead if the interaction between objects and extensions is not efficiently designed. + +## Related Java Design Patterns + +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar in intent to add responsibilities dynamically, but uses a different structure. +* [Composite](https://java-design-patterns.com/patterns/composite/): Also manages a group of objects, which can be seen as a form of extension. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Offers an alternative way to change the behavior of an object dynamically. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/4aBMuuL) +* [Pattern-Oriented Software Architecture: A System of Patterns](https://amzn.to/3Q9YOtX) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3W6IZYQ) diff --git a/extension-objects/pom.xml b/extension-objects/pom.xml index 4e3d1e981d84..92297162fed4 100644 --- a/extension-objects/pom.xml +++ b/extension-objects/pom.xml @@ -34,6 +34,14 @@ 4.0.0 extension-objects + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/extension-objects/src/main/java/App.java b/extension-objects/src/main/java/App.java index dd67b2e1e4b1..db48032f1499 100644 --- a/extension-objects/src/main/java/App.java +++ b/extension-objects/src/main/java/App.java @@ -46,16 +46,15 @@ public class App { */ public static void main(String[] args) { - //Create 3 different units + // Create 3 different units var soldierUnit = new SoldierUnit("SoldierUnit1"); var sergeantUnit = new SergeantUnit("SergeantUnit1"); var commanderUnit = new CommanderUnit("CommanderUnit1"); - //check for each unit to have an extension + // check for each unit to have an extension checkExtensionsForUnit(soldierUnit); checkExtensionsForUnit(sergeantUnit); checkExtensionsForUnit(commanderUnit); - } private static void checkExtensionsForUnit(Unit unit) { diff --git a/extension-objects/src/main/java/abstractextensions/CommanderExtension.java b/extension-objects/src/main/java/abstractextensions/CommanderExtension.java index d366069b1939..bcad9db942c8 100644 --- a/extension-objects/src/main/java/abstractextensions/CommanderExtension.java +++ b/extension-objects/src/main/java/abstractextensions/CommanderExtension.java @@ -24,9 +24,7 @@ */ package abstractextensions; -/** - * Interface with their method. - */ +/** Interface with their method. */ public interface CommanderExtension extends UnitExtension { void commanderReady(); diff --git a/extension-objects/src/main/java/abstractextensions/SergeantExtension.java b/extension-objects/src/main/java/abstractextensions/SergeantExtension.java index 0cb04dc7034d..7eea21d0cb3f 100644 --- a/extension-objects/src/main/java/abstractextensions/SergeantExtension.java +++ b/extension-objects/src/main/java/abstractextensions/SergeantExtension.java @@ -24,9 +24,7 @@ */ package abstractextensions; -/** - * Interface with their method. - */ +/** Interface with their method. */ public interface SergeantExtension extends UnitExtension { void sergeantReady(); diff --git a/extension-objects/src/main/java/abstractextensions/SoldierExtension.java b/extension-objects/src/main/java/abstractextensions/SoldierExtension.java index 98e182de3ee7..579e3c1d0497 100644 --- a/extension-objects/src/main/java/abstractextensions/SoldierExtension.java +++ b/extension-objects/src/main/java/abstractextensions/SoldierExtension.java @@ -24,9 +24,7 @@ */ package abstractextensions; -/** - * Interface with their method. - */ +/** Interface with their method. */ public interface SoldierExtension extends UnitExtension { void soldierReady(); } diff --git a/extension-objects/src/main/java/abstractextensions/UnitExtension.java b/extension-objects/src/main/java/abstractextensions/UnitExtension.java index 8a82f0f0b916..d79868d547ba 100644 --- a/extension-objects/src/main/java/abstractextensions/UnitExtension.java +++ b/extension-objects/src/main/java/abstractextensions/UnitExtension.java @@ -24,8 +24,5 @@ */ package abstractextensions; -/** - * Other Extensions will extend this interface. - */ -public interface UnitExtension { -} +/** Other Extensions will extend this interface. */ +public interface UnitExtension {} diff --git a/extension-objects/src/main/java/concreteextensions/Commander.java b/extension-objects/src/main/java/concreteextensions/Commander.java index e3a3325c95f0..0716d2e9bb25 100644 --- a/extension-objects/src/main/java/concreteextensions/Commander.java +++ b/extension-objects/src/main/java/concreteextensions/Commander.java @@ -25,24 +25,15 @@ package concreteextensions; import abstractextensions.CommanderExtension; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import units.CommanderUnit; -/** - * Class defining Commander. - */ -@Getter -@RequiredArgsConstructor +/** Class defining Commander. */ @Slf4j -public class Commander implements CommanderExtension { - - private final CommanderUnit unit; +public record Commander(CommanderUnit unit) implements CommanderExtension { @Override public void commanderReady() { LOGGER.info("[Commander] " + unit.getName() + " is ready!"); } - } diff --git a/extension-objects/src/main/java/concreteextensions/Sergeant.java b/extension-objects/src/main/java/concreteextensions/Sergeant.java index beaf30aa73c9..fb8f815c8c38 100644 --- a/extension-objects/src/main/java/concreteextensions/Sergeant.java +++ b/extension-objects/src/main/java/concreteextensions/Sergeant.java @@ -25,24 +25,15 @@ package concreteextensions; import abstractextensions.SergeantExtension; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import units.SergeantUnit; -/** - * Class defining Sergeant. - */ -@Getter -@RequiredArgsConstructor +/** Class defining Sergeant. */ @Slf4j -public class Sergeant implements SergeantExtension { - - private final SergeantUnit unit; +public record Sergeant(SergeantUnit unit) implements SergeantExtension { @Override public void sergeantReady() { LOGGER.info("[Sergeant] " + unit.getName() + " is ready!"); } - } diff --git a/extension-objects/src/main/java/concreteextensions/Soldier.java b/extension-objects/src/main/java/concreteextensions/Soldier.java index ae17665d4e0b..dd2338a5389f 100644 --- a/extension-objects/src/main/java/concreteextensions/Soldier.java +++ b/extension-objects/src/main/java/concreteextensions/Soldier.java @@ -25,24 +25,15 @@ package concreteextensions; import abstractextensions.SoldierExtension; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import units.SoldierUnit; -/** - * Class defining Soldier. - */ -@Getter -@RequiredArgsConstructor +/** Class defining Soldier. */ @Slf4j -public class Soldier implements SoldierExtension { - - private final SoldierUnit unit; +public record Soldier(SoldierUnit unit) implements SoldierExtension { @Override public void soldierReady() { LOGGER.info("[Soldier] " + unit.getName() + " is ready!"); } - } diff --git a/extension-objects/src/main/java/units/CommanderUnit.java b/extension-objects/src/main/java/units/CommanderUnit.java index cf62aaec84f1..94e92807f30d 100644 --- a/extension-objects/src/main/java/units/CommanderUnit.java +++ b/extension-objects/src/main/java/units/CommanderUnit.java @@ -28,9 +28,7 @@ import concreteextensions.Commander; import java.util.Optional; -/** - * Class defining CommanderUnit. - */ +/** Class defining CommanderUnit. */ public class CommanderUnit extends Unit { public CommanderUnit(String name) { diff --git a/extension-objects/src/main/java/units/SergeantUnit.java b/extension-objects/src/main/java/units/SergeantUnit.java index 7645a7495525..ae882d92943a 100644 --- a/extension-objects/src/main/java/units/SergeantUnit.java +++ b/extension-objects/src/main/java/units/SergeantUnit.java @@ -28,9 +28,7 @@ import concreteextensions.Sergeant; import java.util.Optional; -/** - * Class defining SergeantUnit. - */ +/** Class defining SergeantUnit. */ public class SergeantUnit extends Unit { public SergeantUnit(String name) { diff --git a/extension-objects/src/main/java/units/SoldierUnit.java b/extension-objects/src/main/java/units/SoldierUnit.java index e82e9fac421e..b1db8930e625 100644 --- a/extension-objects/src/main/java/units/SoldierUnit.java +++ b/extension-objects/src/main/java/units/SoldierUnit.java @@ -28,9 +28,7 @@ import concreteextensions.Soldier; import java.util.Optional; -/** - * Class defining SoldierUnit. - */ +/** Class defining SoldierUnit. */ public class SoldierUnit extends Unit { public SoldierUnit(String name) { diff --git a/extension-objects/src/main/java/units/Unit.java b/extension-objects/src/main/java/units/Unit.java index 1bb3a8dac7d7..1957771821b7 100644 --- a/extension-objects/src/main/java/units/Unit.java +++ b/extension-objects/src/main/java/units/Unit.java @@ -28,9 +28,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Class defining Unit, other units will extend this class. - */ +/** Class defining Unit, other units will extend this class. */ @Setter @Getter public class Unit { diff --git a/extension-objects/src/test/java/AppTest.java b/extension-objects/src/test/java/AppTest.java index 92db1702a7d9..c80e472b4052 100644 --- a/extension-objects/src/test/java/AppTest.java +++ b/extension-objects/src/test/java/AppTest.java @@ -22,18 +22,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Created by Srdjan on 03-May-17. - */ +import org.junit.jupiter.api.Test; + +/** Created by Srdjan on 03-May-17. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/concreteextensions/CommanderTest.java b/extension-objects/src/test/java/concreteextensions/CommanderTest.java index 56343ba4b4c6..5271d7ea7563 100644 --- a/extension-objects/src/test/java/concreteextensions/CommanderTest.java +++ b/extension-objects/src/test/java/concreteextensions/CommanderTest.java @@ -24,22 +24,18 @@ */ package concreteextensions; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; +import java.util.List; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; import units.CommanderUnit; -import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Created by Srdjan on 03-May-17. - * - * Modified by ToxicDreamz on 15-Aug-20 - */ +/** CommanderTest */ class CommanderTest { @Test @@ -56,10 +52,8 @@ void shouldExecuteCommanderReady() { commander.commanderReady(); List logsList = listAppender.list; - assertEquals("[Commander] " + commander.getUnit().getName() + " is ready!", logsList.get(0) - .getMessage()); - assertEquals(Level.INFO, logsList.get(0) - .getLevel()); + assertEquals( + "[Commander] " + commander.unit().getName() + " is ready!", logsList.get(0).getMessage()); + assertEquals(Level.INFO, logsList.get(0).getLevel()); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/concreteextensions/SergeantTest.java b/extension-objects/src/test/java/concreteextensions/SergeantTest.java index 2c75e3ef5d7a..83b2cb46db5d 100644 --- a/extension-objects/src/test/java/concreteextensions/SergeantTest.java +++ b/extension-objects/src/test/java/concreteextensions/SergeantTest.java @@ -24,20 +24,18 @@ */ package concreteextensions; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; +import java.util.List; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; import units.SergeantUnit; -import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SergeantTest { @Test @@ -54,10 +52,8 @@ void sergeantReady() { sergeant.sergeantReady(); List logsList = listAppender.list; - assertEquals("[Sergeant] " + sergeant.getUnit().getName() + " is ready!", logsList.get(0) - .getMessage()); - assertEquals(Level.INFO, logsList.get(0) - .getLevel()); + assertEquals( + "[Sergeant] " + sergeant.unit().getName() + " is ready!", logsList.get(0).getMessage()); + assertEquals(Level.INFO, logsList.get(0).getLevel()); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/concreteextensions/SoldierTest.java b/extension-objects/src/test/java/concreteextensions/SoldierTest.java index 9b30f803e197..da3490c2bad9 100644 --- a/extension-objects/src/test/java/concreteextensions/SoldierTest.java +++ b/extension-objects/src/test/java/concreteextensions/SoldierTest.java @@ -24,21 +24,18 @@ */ package concreteextensions; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; +import java.util.List; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; import units.SoldierUnit; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SoldierTest { @Test @@ -55,10 +52,8 @@ void soldierReady() { soldier.soldierReady(); List logsList = listAppender.list; - assertEquals("[Soldier] " + soldier.getUnit().getName() + " is ready!", logsList.get(0) - .getMessage()); - assertEquals(Level.INFO, logsList.get(0) - .getLevel()); + assertEquals( + "[Soldier] " + soldier.unit().getName() + " is ready!", logsList.get(0).getMessage()); + assertEquals(Level.INFO, logsList.get(0).getLevel()); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/CommanderUnitTest.java b/extension-objects/src/test/java/units/CommanderUnitTest.java index 52e343d5c4ea..1b4793e66e71 100644 --- a/extension-objects/src/test/java/units/CommanderUnitTest.java +++ b/extension-objects/src/test/java/units/CommanderUnitTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class CommanderUnitTest { @Test @@ -42,5 +40,4 @@ void getUnitExtension() { assertNull(unit.getUnitExtension("SergeantExtension")); assertNotNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/SergeantUnitTest.java b/extension-objects/src/test/java/units/SergeantUnitTest.java index 59255f3b870b..b211483b15e9 100644 --- a/extension-objects/src/test/java/units/SergeantUnitTest.java +++ b/extension-objects/src/test/java/units/SergeantUnitTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SergeantUnitTest { @Test @@ -42,5 +40,4 @@ void getUnitExtension() { assertNotNull(unit.getUnitExtension("SergeantExtension")); assertNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/SoldierUnitTest.java b/extension-objects/src/test/java/units/SoldierUnitTest.java index c32e741171ba..4a3ff803e15a 100644 --- a/extension-objects/src/test/java/units/SoldierUnitTest.java +++ b/extension-objects/src/test/java/units/SoldierUnitTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SoldierUnitTest { @Test @@ -42,5 +40,4 @@ void getUnitExtension() { assertNull(unit.getUnitExtension("SergeantExtension")); assertNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/UnitTest.java b/extension-objects/src/test/java/units/UnitTest.java index 6700101a856a..9cd0d29d71b7 100644 --- a/extension-objects/src/test/java/units/UnitTest.java +++ b/extension-objects/src/test/java/units/UnitTest.java @@ -29,13 +29,11 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class UnitTest { @Test - void testConstGetSet() throws Exception { + void testConstGetSet() { final var name = "testName"; final var unit = new Unit(name); assertEquals(name, unit.getName()); @@ -44,11 +42,9 @@ void testConstGetSet() throws Exception { unit.setName(newName); assertEquals(newName, unit.getName()); - assertNull(unit.getUnitExtension("")); assertNull(unit.getUnitExtension("SoldierExtension")); assertNull(unit.getUnitExtension("SergeantExtension")); assertNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/facade/README.md b/facade/README.md index 4efa8f7b76c7..ea27e4afa31c 100644 --- a/facade/README.md +++ b/facade/README.md @@ -1,25 +1,29 @@ --- -title: Facade +title: "Facade Pattern in Java: Simplifying Complex System Interfaces" +shortTitle: Facade +description: "Learn how to implement the Facade Design Pattern in Java to create a unified interface for complex subsystems. Simplify your code and enhance maintainability with practical examples and use cases." category: Structural language: en tag: - - Gang Of Four - - Decoupling + - Abstraction + - API design + - Code simplification + - Decoupling + - Encapsulation + - Gang Of Four + - Interface + - Object composition --- -## Intent +## Intent of Facade Design Pattern -Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level -interface that makes the subsystem easier to use. +The Facade Design Pattern provides a unified interface to a set of interfaces in a subsystem. This Java design pattern simplifies complex system interactions. -## Explanation +## Detailed Explanation of Facade Pattern with Real-World Examples Real-world example -> How does a goldmine work? "Well, the miners go down there and dig gold!" you say. That is what you -> believe because you are using a simple interface that goldmine provides on the outside, internally -> it has to do a lot of stuff to make it happen. This simple interface to the complex subsystem is a -> facade. +> Imagine a home theater system with multiple components: a DVD player, projector, surround sound system, and lights. Each component has a complex interface with numerous functions and settings. To simplify the use of the home theater system, a remote control (the Facade) is provided. The remote control offers a unified interface with simple buttons like "Play Movie," "Stop," "Pause," and "Volume Up/Down," which internally communicate with the various components, managing their interactions. This makes the system easier to use without needing to understand the detailed operations of each component. In plain words @@ -27,13 +31,15 @@ In plain words Wikipedia says -> A facade is an object that provides a simplified interface to a larger body of code, such as a -> class library. +> A facade is an object that provides a simplified interface to a larger body of code, such as a class library. -**Programmatic Example** +## Programmatic Example of Facade Pattern in Java -Let's take our goldmine example from above. Here we have the dwarven mine worker hierarchy. First -there's a base class `DwarvenMineWorker`: +Here's an example of the Facade Design Pattern in a goldmine scenario, demonstrating how a Java facade can streamline complex operations. + +How does a goldmine work? "Well, the miners go down there and dig gold!" you say. That is what you believe because you are using a simple interface that goldmine provides on the outside, internally it has to do a lot of stuff to make it happen. This simple interface to the complex subsystem is a facade. + +Here we have the dwarven mine worker hierarchy. First, there's a base class `DwarvenMineWorker`: ```java @@ -81,50 +87,50 @@ public abstract class DwarvenMineWorker { } ``` -Then we have the concrete dwarf classes `DwarvenTunnelDigger`, `DwarvenGoldDigger` and -`DwarvenCartOperator`: +Then we have the concrete dwarf classes `DwarvenTunnelDigger`, `DwarvenGoldDigger` and `DwarvenCartOperator`: ```java + @Slf4j public class DwarvenTunnelDigger extends DwarvenMineWorker { - @Override - public void work() { - LOGGER.info("{} creates another promising tunnel.", name()); - } + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } - @Override - public String name() { - return "Dwarven tunnel digger"; - } + @Override + public String name() { + return "Dwarven tunnel digger"; + } } @Slf4j public class DwarvenGoldDigger extends DwarvenMineWorker { - @Override - public void work() { - LOGGER.info("{} digs for gold.", name()); - } + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } - @Override - public String name() { - return "Dwarf gold digger"; - } + @Override + public String name() { + return "Dwarf gold digger"; + } } @Slf4j public class DwarvenCartOperator extends DwarvenMineWorker { - @Override - public void work() { - LOGGER.info("{} moves gold chunks out of the mine.", name()); - } + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } - @Override - public String name() { - return "Dwarf cart operator"; - } + @Override + public String name() { + return "Dwarf cart operator"; + } } ``` @@ -134,94 +140,107 @@ To operate all these goldmine workers we have the `DwarvenGoldmineFacade`: ```java public class DwarvenGoldmineFacade { - private final List workers; + private final List workers; - public DwarvenGoldmineFacade() { - workers = List.of( - new DwarvenGoldDigger(), - new DwarvenCartOperator(), - new DwarvenTunnelDigger()); - } + public DwarvenGoldmineFacade() { + workers = List.of( + new DwarvenGoldDigger(), + new DwarvenCartOperator(), + new DwarvenTunnelDigger()); + } - public void startNewDay() { - makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); - } + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } - public void digOutGold() { - makeActions(workers, DwarvenMineWorker.Action.WORK); - } + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } - public void endDay() { - makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); - } + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } - private static void makeActions(Collection workers, - DwarvenMineWorker.Action... actions) { - workers.forEach(worker -> worker.action(actions)); - } + private static void makeActions(Collection workers, + DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } } ``` Now let's use the facade: ```java -var facade = new DwarvenGoldmineFacade(); -facade.startNewDay(); -facade.digOutGold(); -facade.endDay(); +public static void main(String[] args) { + var facade = new DwarvenGoldmineFacade(); + facade.startNewDay(); + facade.digOutGold(); + facade.endDay(); +} ``` Program output: -```java -// Dwarf gold digger wakes up. -// Dwarf gold digger goes to the mine. -// Dwarf cart operator wakes up. -// Dwarf cart operator goes to the mine. -// Dwarven tunnel digger wakes up. -// Dwarven tunnel digger goes to the mine. -// Dwarf gold digger digs for gold. -// Dwarf cart operator moves gold chunks out of the mine. -// Dwarven tunnel digger creates another promising tunnel. -// Dwarf gold digger goes home. -// Dwarf gold digger goes to sleep. -// Dwarf cart operator goes home. -// Dwarf cart operator goes to sleep. -// Dwarven tunnel digger goes home. -// Dwarven tunnel digger goes to sleep. ``` +06:07:20.676 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf gold digger wakes up. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf gold digger goes to the mine. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf cart operator wakes up. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf cart operator goes to the mine. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarven tunnel digger wakes up. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarven tunnel digger goes to the mine. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenGoldDigger -- Dwarf gold digger digs for gold. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenCartOperator -- Dwarf cart operator moves gold chunks out of the mine. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenTunnelDigger -- Dwarven tunnel digger creates another promising tunnel. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf gold digger goes home. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf gold digger goes to sleep. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf cart operator goes home. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarf cart operator goes to sleep. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarven tunnel digger goes home. +06:07:20.678 [main] INFO com.iluwatar.facade.DwarvenMineWorker -- Dwarven tunnel digger goes to sleep. +``` + +## When to Use the Facade Pattern in Java + +Use the Facade pattern in Java when: + +* You want to provide a simple interface to a complex subsystem. +* Subsystems are getting more complex and depend on multiple classes, but most clients only need a part of the functionality. +* There is a need to layer your subsystems. Use a facade to define an entry point to each subsystem level. +* You want to reduce dependencies and enhance code readability in Java development. + +## Facade Pattern Java Tutorials + +* [Facade Design Pattern in Java (DigitalOcean)](https://www.digitalocean.com/community/tutorials/facade-design-pattern-in-java) +* [Facade (Refactoring Guru)](https://refactoring.guru/design-patterns/facade) +* [Facade Method Design Pattern (GeekforGeeks)](https://www.geeksforgeeks.org/facade-design-pattern-introduction/) +* [Design Patterns - Facade Pattern (TutorialsPoint)](https://www.tutorialspoint.com/design_pattern/facade_pattern.htm) + +## Real-World Applications of Facade Pattern in Java -## Class diagram +* Java libraries such as java.net.URL and javax.faces.context.FacesContext use Facade to simplify complex underlying classes. +* In many Java frameworks, facades are used to simplify the usage of APIs by providing a simpler interface to more complex underlying code structures. -![alt text](./etc/facade.urm.png "Facade pattern class diagram") +## Benefits and Trade-offs of Facade Pattern -## Applicability +Benefits: -Use the Facade pattern when +Implementing the Facade Design Pattern in Java: -* You want to provide a simple interface to a complex subsystem. Subsystems often get more complex -as they evolve. Most patterns, when applied, result in more and smaller classes. This makes the -subsystem more reusable and easier to customize, but it also becomes harder to use for clients that -don't need to customize it. A facade can provide a simple default view of the subsystem that is good -enough for most clients. Only clients needing more customization will need to look beyond the -facade. -* There are many dependencies between clients and the implementation classes of an abstraction. -Introduce a facade to decouple the subsystem from clients and other subsystems, thereby promoting -subsystem independence and portability. -* You want to layer your subsystems. Use a facade to define an entry point to each subsystem level. -If subsystems are dependent, then you can simplify the dependencies between them by making them -communicate with each other solely through their facades. +* Isolates clients from subsystem components, making it easier to use and reducing dependencies. +* Promotes weak coupling between the subsystem and its clients. +* Often simplifies the API of complex systems. -## Tutorials +Trade-offs: -*[DigitalOcean](https://www.digitalocean.com/community/tutorials/facade-design-pattern-in-java) -* [Refactoring Guru](https://refactoring.guru/design-patterns/facade) -* [GeekforGeeks](https://www.geeksforgeeks.org/facade-design-pattern-introduction/) -* [Tutorialspoint](https://www.tutorialspoint.com/design_pattern/facade_pattern.htm) +* A facade can become a god object coupled to all classes of an app if not implemented correctly. +## Related Java Design Patterns +* [Adapter](https://java-design-patterns.com/patterns/adapter/): Facade provides a unified interface while Adapter makes two existing interfaces work together. +* [Mediator](https://java-design-patterns.com/patterns/mediator/): Facade defines a simpler interface to a subsystem while Mediator centralizes complex communications and control between objects. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3QbO7qN) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG) diff --git a/facade/pom.xml b/facade/pom.xml index c61e03e169e3..916a3b0c45b8 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -34,6 +34,14 @@ facade + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java b/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java index 82ec4ef3aca9..9b077b242973 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java @@ -1,44 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenCartOperator is one of the goldmine subsystems. - */ -@Slf4j -public class DwarvenCartOperator extends DwarvenMineWorker { - - @Override - public void work() { - LOGGER.info("{} moves gold chunks out of the mine.", name()); - } - - @Override - public String name() { - return "Dwarf cart operator"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import lombok.extern.slf4j.Slf4j; + +/** DwarvenCartOperator is one of the goldmine subsystems. */ +@Slf4j +public class DwarvenCartOperator extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java index 2f95c015b50f..29cdc7424667 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java @@ -1,44 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenGoldDigger is one of the goldmine subsystems. - */ -@Slf4j -public class DwarvenGoldDigger extends DwarvenMineWorker { - - @Override - public void work() { - LOGGER.info("{} digs for gold.", name()); - } - - @Override - public String name() { - return "Dwarf gold digger"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import lombok.extern.slf4j.Slf4j; + +/** DwarvenGoldDigger is one of the goldmine subsystems. */ +@Slf4j +public class DwarvenGoldDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java index 28c1a58a946b..3e4151674b51 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java @@ -1,69 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import java.util.Collection; -import java.util.List; - -/** - * DwarvenGoldmineFacade provides a single interface through which users can operate the - * subsystems. - * - *

This makes the goldmine easier to operate and cuts the dependencies from the goldmine user to - * the subsystems. - */ -public class DwarvenGoldmineFacade { - - private final List workers; - - /** - * Constructor. - */ - public DwarvenGoldmineFacade() { - workers = List.of( - new DwarvenGoldDigger(), - new DwarvenCartOperator(), - new DwarvenTunnelDigger()); - } - - public void startNewDay() { - makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); - } - - public void digOutGold() { - makeActions(workers, DwarvenMineWorker.Action.WORK); - } - - public void endDay() { - makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); - } - - private static void makeActions( - Collection workers, - DwarvenMineWorker.Action... actions - ) { - workers.forEach(worker -> worker.action(actions)); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import java.util.Collection; +import java.util.List; + +/** + * DwarvenGoldmineFacade provides a single interface through which users can operate the subsystems. + * + *

This makes the goldmine easier to operate and cuts the dependencies from the goldmine user to + * the subsystems. + */ +public class DwarvenGoldmineFacade { + + private final List workers; + + /** Constructor. */ + public DwarvenGoldmineFacade() { + workers = + List.of(new DwarvenGoldDigger(), new DwarvenCartOperator(), new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions( + Collection workers, DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java index e0c50a2064f2..08af7cc2c878 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java @@ -1,77 +1,77 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import java.util.Arrays; -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenMineWorker is one of the goldmine subsystems. - */ -@Slf4j -public abstract class DwarvenMineWorker { - - public void goToSleep() { - LOGGER.info("{} goes to sleep.", name()); - } - - public void wakeUp() { - LOGGER.info("{} wakes up.", name()); - } - - public void goHome() { - LOGGER.info("{} goes home.", name()); - } - - public void goToMine() { - LOGGER.info("{} goes to the mine.", name()); - } - - private void action(Action action) { - switch (action) { - case GO_TO_SLEEP -> goToSleep(); - case WAKE_UP -> wakeUp(); - case GO_HOME -> goHome(); - case GO_TO_MINE -> goToMine(); - case WORK -> work(); - default -> LOGGER.info("Undefined action"); - } - } - - /** - * Perform actions. - */ - public void action(Action... actions) { - Arrays.stream(actions).forEach(this::action); - } - - public abstract void work(); - - public abstract String name(); - - enum Action { - GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import java.util.Arrays; +import lombok.extern.slf4j.Slf4j; + +/** DwarvenMineWorker is one of the goldmine subsystems. */ +@Slf4j +public abstract class DwarvenMineWorker { + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP -> goToSleep(); + case WAKE_UP -> wakeUp(); + case GO_HOME -> goHome(); + case GO_TO_MINE -> goToMine(); + case WORK -> work(); + default -> LOGGER.info("Undefined action"); + } + } + + /** Perform actions. */ + public void action(Action... actions) { + Arrays.stream(actions).forEach(this::action); + } + + public abstract void work(); + + public abstract String name(); + + enum Action { + GO_TO_SLEEP, + WAKE_UP, + GO_HOME, + GO_TO_MINE, + WORK + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java b/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java index b496b0ebd0e0..4a8fa5a89959 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java @@ -1,44 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenTunnelDigger is one of the goldmine subsystems. - */ -@Slf4j -public class DwarvenTunnelDigger extends DwarvenMineWorker { - - @Override - public void work() { - LOGGER.info("{} creates another promising tunnel.", name()); - } - - @Override - public String name() { - return "Dwarven tunnel digger"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import lombok.extern.slf4j.Slf4j; + +/** DwarvenTunnelDigger is one of the goldmine subsystems. */ +@Slf4j +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} diff --git a/facade/src/test/java/com/iluwatar/facade/AppTest.java b/facade/src/test/java/com/iluwatar/facade/AppTest.java index 38c4b9b7827c..41d6d2a942b7 100644 --- a/facade/src/test/java/com/iluwatar/facade/AppTest.java +++ b/facade/src/test/java/com/iluwatar/facade/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.facade; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java index 4236557577ab..55baf907da56 100644 --- a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java +++ b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java @@ -37,11 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/9/15 - 9:40 PM - * - * @author Jeroen Meulemeester - */ +/** DwarvenGoldmineFacadeTest */ class DwarvenGoldmineFacadeTest { private InMemoryAppender appender; @@ -60,8 +56,8 @@ void tearDown() { * Test a complete day cycle in the gold mine by executing all three different steps: {@link * DwarvenGoldmineFacade#startNewDay()}, {@link DwarvenGoldmineFacade#digOutGold()} and {@link * DwarvenGoldmineFacade#endDay()}. - *

- * See if the workers are doing what's expected from them on each step. + * + *

See if the workers are doing what's expected from them on each step. */ @Test void testFullWorkDay() { @@ -84,7 +80,7 @@ void testFullWorkDay() { // Now do some actual work, start digging gold! goldMine.digOutGold(); - // Since we gave the dig command, every worker should be doing it's job ... + // Since we gave the dig command, every worker should be doing its job ... assertTrue(appender.logContains("Dwarf gold digger digs for gold.")); assertTrue(appender.logContains("Dwarf cart operator moves gold chunks out of the mine.")); assertTrue(appender.logContains("Dwarven tunnel digger creates another promising tunnel.")); @@ -128,11 +124,7 @@ public int getLogSize() { } public boolean logContains(String message) { - return log.stream() - .map(ILoggingEvent::getFormattedMessage) - .anyMatch(message::equals); + return log.stream().map(ILoggingEvent::getFormattedMessage).anyMatch(message::equals); } } - - } diff --git a/factory-kit/README.md b/factory-kit/README.md index 392e8586dd82..d13379a4ec8a 100644 --- a/factory-kit/README.md +++ b/factory-kit/README.md @@ -1,31 +1,42 @@ --- -title: Factory Kit +title: "Factory Kit Pattern in Java: Crafting Flexible Component Assemblies" +shortTitle: Factory Kit +description: "Learn about the Factory Kit Pattern in Java with detailed explanations, real-world examples, and practical applications. Improve your Java skills with our comprehensive guide." category: Creational language: en tag: - - Extensibility + - Abstraction + - Decoupling + - Encapsulation + - Generic + - Instantiation + - Object composition --- -## Also Known As -Abstract-Factory +## Also known as -## Intent +* Object Kit +* Toolkit -Define a factory of immutable content with separated builder and factory interfaces. +## Intent of Factory Kit Design Pattern -## Explanation +The Factory Kit Pattern in Java is a powerful design pattern that helps in creating factories with separated builder and factory interfaces. This pattern is essential for managing complex object creation scenarios. + +## Detailed Explanation of Factory Kit Pattern with Real-World Examples Real-world example -> Imagine a magical weapon factory that can create any type of weapon wished for. When the factory -> is unboxed, the master recites the weapon types needed to prepare it. After that, any of those -> weapon types can be summoned in an instant. +> An analogous real-world example of the Factory Kit Pattern is a restaurant kitchen where different types of dishes are prepared efficiently. This setup promotes flexibility and consistency, similar to how the Factory Kit Pattern operates in Java. Imagine the kitchen has a central station with various ingredients and recipes registered for different dishes. When an order comes in, the chef consults this central station to gather the necessary ingredients and follow the registered recipe to prepare the dish. This setup allows the kitchen to efficiently manage and switch between different dish preparations without the need for each chef to know the specifics of every recipe, promoting flexibility and consistency in the cooking process. In plain words > Factory kit is a configurable object builder, a factory to create factories. -**Programmatic Example** +## Programmatic Example of Factory Kit Pattern in Java + +Imagine a magical weapon factory in Java capable of creating any desired weapon using the Factory Kit Pattern. This pattern allows for configurable object builders, making it ideal for scenarios where the types of objects are not known upfront. + +Upon activation, the master recites the names of the weapon types needed to configure it. Once set up, any of these weapon types can be summoned instantly. Let's first define the simple `Weapon` hierarchy. @@ -47,84 +58,97 @@ public class Sword implements Weapon { } } -// Axe, Bow, and Spear are defined similarly +// Axe, Bow, and Spear are defined similarly... ``` Next, we define a functional interface that allows adding a builder with a name to the factory. ```java public interface Builder { - void add(WeaponType name, Supplier supplier); + void add(WeaponType name, Supplier supplier); } ``` -The meat of the example is the `WeaponFactory` interface that effectively implements the factory -kit pattern. The method `#factory` is used to configure the factory with the classes it needs to -be able to construct. The method `#create` is then used to create object instances. +The meat of the example is the `WeaponFactory` interface that effectively implements the factory kit pattern. The method `#factory` is used to configure the factory with the classes it needs to be able to construct. The method `#create` is then used to create object instances. ```java public interface WeaponFactory { - static WeaponFactory factory(Consumer consumer) { - var map = new HashMap>(); - consumer.accept(map::put); - return name -> map.get(name).get(); - } - - Weapon create(WeaponType name); + static WeaponFactory factory(Consumer consumer) { + var map = new HashMap>(); + consumer.accept(map::put); + return name -> map.get(name).get(); + } + + Weapon create(WeaponType name); } ``` Now, we can show how `WeaponFactory` can be used. ```java -var factory = WeaponFactory.factory(builder -> { - builder.add(WeaponType.SWORD, Sword::new); - builder.add(WeaponType.AXE, Axe::new); - builder.add(WeaponType.SPEAR, Spear::new); - builder.add(WeaponType.BOW, Bow::new); -}); -var list = new ArrayList(); -list.add(factory.create(WeaponType.AXE)); -list.add(factory.create(WeaponType.SPEAR)); -list.add(factory.create(WeaponType.SWORD)); -list.add(factory.create(WeaponType.BOW)); -list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString())); + public static void main(String[] args) { + var factory = WeaponFactory.factory(builder -> { + builder.add(WeaponType.SWORD, Sword::new); + builder.add(WeaponType.AXE, Axe::new); + builder.add(WeaponType.SPEAR, Spear::new); + builder.add(WeaponType.BOW, Bow::new); + }); + var list = new ArrayList(); + list.add(factory.create(WeaponType.AXE)); + list.add(factory.create(WeaponType.SPEAR)); + list.add(factory.create(WeaponType.SWORD)); + list.add(factory.create(WeaponType.BOW)); + list.forEach(weapon -> LOGGER.info("{}", weapon.toString())); +} ``` Here is the console output when the example is run. ``` -21:15:49.709 [main] INFO com.iluwatar.factorykit.App - Axe -21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Spear -21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Sword -21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Bow +06:32:23.026 [main] INFO com.iluwatar.factorykit.App -- Axe +06:32:23.029 [main] INFO com.iluwatar.factorykit.App -- Spear +06:32:23.029 [main] INFO com.iluwatar.factorykit.App -- Sword +06:32:23.029 [main] INFO com.iluwatar.factorykit.App -- Bow ``` -## Class diagram +## When to Use the Factory Kit Pattern in Java + +Use the Factory Kit Pattern when + +* The factory class cannot anticipate the types of objects it must create, and a new instance of a custom builder is needed. +* A new instance of a custom builder is needed instead of a global one. +* The types of objects that the factory can build need to be defined outside the class. +* The builder and creator interfaces need to be separated. +* Game developments and other applications that have user customization. + +## Factory Kit Pattern Java Tutorials + +* [Factory Kit Pattern (Diego Pacheco)](https://diego-pacheco.medium.com/factory-kit-pattern-66d5ccb0c405) + +## Real-World Applications of Factory Kit Pattern in Java -![alt text](./etc/factory-kit.png "Factory Kit") +* In Java libraries such as the Java Development Kit (JDK) where different rendering engines might be instantiated based on the runtime environment. +* Frameworks like Spring or applications where dependency injection is heavily used, often implement this pattern to manage object creation more flexibly. -## Applicability +## Benefits and Trade-offs of Factory Kit Pattern -Use the Factory Kit pattern when +Benefits: -* The factory class can't anticipate the types of objects it must create -* A new instance of a custom builder is needed instead of a global one -* The types of objects that the factory can build need to be defined outside the class -* The builder and creator interfaces need to be separated -* Game developments and other applications that have user customisation +* The Factory Kit Pattern in Java promotes loose coupling by eliminating the need to bind application-specific classes into the code. +* It simplifies the code by shifting the responsibility of instantiation to a factory object, making the development process more efficient. -## Related patterns +Trade-offs: -* [Builder](https://java-design-patterns.com/patterns/builder/) -* [Factory](https://java-design-patterns.com/patterns/factory/) -* [Abstract-Factory](https://java-design-patterns.com/patterns/abstract-factory/) +* Can introduce complexity into the code by requiring additional classes and interfaces. +* Sometimes can lead to dependency issues if not properly managed. -## Tutorials +## Related Java Design Patterns -* [Factory kit implementation tutorial](https://diego-pacheco.medium.com/factory-kit-pattern-66d5ccb0c405) +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Often used together with the Factory Kit to create families of related objects. +* [Builder](https://java-design-patterns.com/patterns/builder/): Can be used to construct complex objects step-by-step using a similar approach. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Objects that are created by cloning a prototypical instance often use a factory to manage it. -## Credits +## References and Credits -* [Design Pattern Reloaded by Remi Forax](https://www.youtube.com/watch?v=-k2X7guaArU) +* [Design Pattern Reloaded (Remi Forax)](https://www.youtube.com/watch?v=-k2X7guaArU) diff --git a/factory-kit/pom.xml b/factory-kit/pom.xml index 0edb889ea600..729ea8c6b12d 100644 --- a/factory-kit/pom.xml +++ b/factory-kit/pom.xml @@ -34,6 +34,14 @@ factory-kit + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/App.java b/factory-kit/src/main/java/com/iluwatar/factorykit/App.java index 416b3d39b16a..e545c313e564 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/App.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/App.java @@ -35,9 +35,9 @@ *

In the given example {@link WeaponFactory} represents the factory kit, that contains four * {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface. * - *

Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with - * an input representing an instance of {@link WeaponType} that needs to be mapped explicitly with - * desired class type in the factory instance. + *

Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with an input + * representing an instance of {@link WeaponType} that needs to be mapped explicitly with desired + * class type in the factory instance. */ @Slf4j public class App { @@ -48,12 +48,14 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var factory = WeaponFactory.factory(builder -> { - builder.add(WeaponType.SWORD, Sword::new); - builder.add(WeaponType.AXE, Axe::new); - builder.add(WeaponType.SPEAR, Spear::new); - builder.add(WeaponType.BOW, Bow::new); - }); + var factory = + WeaponFactory.factory( + builder -> { + builder.add(WeaponType.SWORD, Sword::new); + builder.add(WeaponType.AXE, Axe::new); + builder.add(WeaponType.SPEAR, Spear::new); + builder.add(WeaponType.BOW, Bow::new); + }); var list = new ArrayList(); list.add(factory.create(WeaponType.AXE)); list.add(factory.create(WeaponType.SPEAR)); diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java index d8de7ffe2279..583fa44a515b 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Axe. - */ +/** Class representing Axe. */ public class Axe implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java index bd1644b788c5..f052db8109a9 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Bows. - */ +/** Class representing Bows. */ public class Bow implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java index 927f2b63162b..541932e8c8f1 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java @@ -26,9 +26,7 @@ import java.util.function.Supplier; -/** - * Functional interface that allows adding builder with name to the factory. - */ +/** Functional interface that allows adding builder with name to the factory. */ public interface Builder { void add(WeaponType name, Supplier supplier); } diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java index ad9b682a5a4a..9dfd97a07573 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Spear. - */ +/** Class representing Spear. */ public class Spear implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java index 2aa49ce58a9e..7f0320a3bc06 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Swords. - */ +/** Class representing Swords. */ public class Sword implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java index e02da7484164..8501fd59f635 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java @@ -24,8 +24,5 @@ */ package com.iluwatar.factorykit; -/** - * Interface representing weapon. - */ -public interface Weapon { -} +/** Interface representing weapon. */ +public interface Weapon {} diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java index 997ac8fadc68..74bf0623b1a9 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java @@ -29,11 +29,11 @@ import java.util.function.Supplier; /** - * Functional interface, an example of the factory-kit design pattern. - *
Instance created locally gives an opportunity to strictly define - * which objects types the instance of a factory will be able to create. - *
Factory is a placeholder for {@link Builder}s - * with {@link WeaponFactory#create(WeaponType)} method to initialize new objects. + * Functional interface, an example of the factory-kit design pattern.
+ * Instance created locally gives an opportunity to strictly define which objects types the instance + * of a factory will be able to create.
+ * Factory is a placeholder for {@link Builder}s with {@link WeaponFactory#create(WeaponType)} + * method to initialize new objects. */ public interface WeaponFactory { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java index 4d292f46106a..eee7e1136b29 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Enumerates {@link Weapon} types. - */ +/** Enumerates {@link Weapon} types. */ public enum WeaponType { SWORD, AXE, diff --git a/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java b/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java index 781b3f4da390..afd4f6404711 100644 --- a/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java +++ b/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java @@ -24,19 +24,16 @@ */ package com.iluwatar.factorykit.app; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.factorykit.App; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Application Test Entrypoint - */ +/** Application Test Entrypoint */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } - diff --git a/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java b/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java index 152ce2bd036c..02862a5d8cc3 100644 --- a/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java +++ b/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java @@ -35,20 +35,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Factory Kit Pattern - */ +/** Test Factory Kit Pattern */ class FactoryKitTest { private WeaponFactory factory; @BeforeEach void init() { - factory = WeaponFactory.factory(builder -> { - builder.add(WeaponType.SPEAR, Spear::new); - builder.add(WeaponType.AXE, Axe::new); - builder.add(WeaponType.SWORD, Sword::new); - }); + factory = + WeaponFactory.factory( + builder -> { + builder.add(WeaponType.SPEAR, Spear::new); + builder.add(WeaponType.AXE, Axe::new); + builder.add(WeaponType.SWORD, Sword::new); + }); } /** @@ -71,7 +71,6 @@ void testAxeWeapon() { verifyWeapon(weapon, Axe.class); } - /** * Testing {@link WeaponFactory} to produce a SWORD asserting that the Weapon is an instance of * {@link Sword} @@ -86,7 +85,7 @@ void testWeapon() { * This method asserts that the weapon object that is passed is an instance of the clazz * * @param weapon weapon object which is to be verified - * @param clazz expected class of the weapon + * @param clazz expected class of the weapon */ private void verifyWeapon(Weapon weapon, Class clazz) { assertTrue(clazz.isInstance(weapon), "Weapon must be an object of: " + clazz.getName()); diff --git a/factory-method/README.md b/factory-method/README.md index f6819617c8d4..6aad53596c93 100644 --- a/factory-method/README.md +++ b/factory-method/README.md @@ -1,27 +1,32 @@ --- -title: Factory Method +title: "Factory Method Pattern in Java: Enhancing Flexibility with Polymorphic Manufacturing" +shortTitle: Factory Method +description: "Learn about the Factory Method pattern in Java. Explore examples, uses, benefits, and how it enhances code flexibility and maintenance." category: Creational language: en tag: - - Extensibility - - Gang of Four + - Encapsulation + - Gang of Four + - Instantiation + - Object composition + - Polymorphism --- ## Also known as -Virtual Constructor +* Virtual Constructor -## Intent +## Intent of Factory Method Design Pattern -Define an interface for creating an object, but let subclasses decide which class to instantiate. -Factory Method lets a class defer instantiation to subclasses. +Define an interface for creating an object using the Factory Method Pattern, but let subclasses decide which class to instantiate. This creational design pattern lets a class defer instantiation to subclasses, enhancing code flexibility and maintenance. -## Explanation +## Detailed Explanation of Factory Method Pattern with Real-World Examples Real-world example -> Blacksmith manufactures weapons. Elves require Elvish weapons and orcs require Orcish weapons. -> Depending on the customer at hand the right type of blacksmith is summoned. +> Imagine a logistics company that needs to deliver different types of packages: standard, express, and oversized. The company has a central system that processes delivery requests but does not know the specifics of how each package type is handled. To manage this, the company uses a Factory Method pattern. +> +> In this setup, there is a central `DeliveryRequest` class with a method `createPackage()`. This method is overridden in subclasses like `StandardDelivery`, `ExpressDelivery`, and `OversizedDelivery`, each of which knows how to create and manage the respective package type. This way, the central system can handle delivery requests without needing to know the details of how each package type is created and processed, allowing for flexibility and easier maintenance. In plain words @@ -29,16 +34,15 @@ In plain words Wikipedia says -> In class-based programming, the factory method pattern is a creational pattern that uses factory -> methods to deal with the problem of creating objects without having to specify the exact class of -> the object that will be created. This is done by creating objects by calling a factory method -> — either specified in an interface and implemented by child classes, or implemented in a base -> class and optionally overridden by derived classes—rather than by calling a constructor. +> In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method — either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor. - **Programmatic Example** +## Programmatic Example of Factory Method Pattern in Java -Taking our blacksmith example above. First of all, we have a `Blacksmith` interface and some -implementations for it: +The Factory Method approach is pivotal in Java Design Patterns for achieving flexible and maintainable code as we see in the following example. + +Blacksmith manufactures weapons. Elves require Elvish weapons and orcs require Orcish weapons. Depending on the customer at hand the right type of blacksmith is summoned. + +First of all, we have a `Blacksmith` interface and some implementations for it: ```java public interface Blacksmith { @@ -58,45 +62,42 @@ public class OrcBlacksmith implements Blacksmith { } ``` -When the customers come, the correct type of blacksmith is summoned and requested weapons are -manufactured: +When the customers come, the correct type of blacksmith is summoned and requested weapons are manufactured: ```java -Blacksmith blacksmith = new OrcBlacksmith(); -Weapon weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); -LOGGER.info("{} manufactured {}", blacksmith, weapon); -weapon = blacksmith.manufactureWeapon(WeaponType.AXE); -LOGGER.info("{} manufactured {}", blacksmith, weapon); - -blacksmith = new ElfBlacksmith(); -weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); -LOGGER.info("{} manufactured {}", blacksmith, weapon); -weapon = blacksmith.manufactureWeapon(WeaponType.AXE); -LOGGER.info("{} manufactured {}", blacksmith, weapon); +public static void main(String[] args) { + + Blacksmith blacksmith = new OrcBlacksmith(); + Weapon weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + weapon = blacksmith.manufactureWeapon(WeaponType.AXE); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + + blacksmith = new ElfBlacksmith(); + weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + weapon = blacksmith.manufactureWeapon(WeaponType.AXE); + LOGGER.info(MANUFACTURED, blacksmith, weapon); +} ``` Program output: ``` -The orc blacksmith manufactured an orcish spear -The orc blacksmith manufactured an orcish axe -The elf blacksmith manufactured an elven spear -The elf blacksmith manufactured an elven axe +06:40:07.269 [main] INFO com.iluwatar.factory.method.App -- The orc blacksmith manufactured an orcish spear +06:40:07.271 [main] INFO com.iluwatar.factory.method.App -- The orc blacksmith manufactured an orcish axe +06:40:07.272 [main] INFO com.iluwatar.factory.method.App -- The elf blacksmith manufactured an elven spear +06:40:07.272 [main] INFO com.iluwatar.factory.method.App -- The elf blacksmith manufactured an elven axe ``` -## Class diagram +## When to Use the Factory Method Pattern in Java -![alt text](./etc/factory-method.urm.png "Factory Method pattern class diagram") - -## Applicability - -Use the Factory Method pattern when: +Use the Factory Method Pattern in Java when: * Class cannot anticipate the class of objects it must create. * Class wants its subclasses to specify the objects it creates. -* Classes delegate responsibility to one of several helper subclasses, and you want to localize the -knowledge of which helper subclass is the delegate. +* Classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate. -## Known uses +## Real-World Applications of Factory Method Pattern in Java * [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) * [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) @@ -105,9 +106,27 @@ knowledge of which helper subclass is the delegate. * [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-) * [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-) * [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) +* Frameworks that run application components, configured dynamically at runtime. + +## Benefits and Trade-offs of Factory Method Pattern + +Benefits: + +* The Factory Method Pattern provides hooks for subclasses, enhancing code flexibility and maintainability. +* Connects parallel class hierarchies. +* Eliminates the need to bind application-specific classes into the code. The code only deals with the product interface; hence it can work with any user-defined concrete product classes. + +Trade-offs: + +* Can complicate the code by requiring the addition of new subclasses to implement the extended factory methods. + +## Related Java Design Patterns + +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Factory methods are often called within Abstract Factory patterns. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): A factory method that returns a new instance of a class that is a clone of a prototype class. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0Rk5y) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG) +* [Patterns of Enterprise Application Architecture](https://amzn.to/4b2ZxoM) diff --git a/factory-method/pom.xml b/factory-method/pom.xml index ea8e842c59d9..bdcfcea3cfcb 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -34,6 +34,14 @@ factory-method + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/App.java b/factory-method/src/main/java/com/iluwatar/factory/method/App.java index 5ca71f45a44e..2094d2d6928a 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/App.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/App.java @@ -34,10 +34,9 @@ * derived classes—rather than by calling a constructor. * *

In this Factory Method example we have an interface ({@link Blacksmith}) with a method for - * creating objects ({@link Blacksmith#manufactureWeapon}). The concrete subclasses ( - * {@link OrcBlacksmith}, {@link ElfBlacksmith}) then override the method to produce objects of - * their liking. - * + * creating objects ({@link Blacksmith#manufactureWeapon}). The concrete subclasses ( {@link + * OrcBlacksmith}, {@link ElfBlacksmith}) then override the method to produce objects of their + * liking. */ @Slf4j public class App { @@ -46,6 +45,7 @@ public class App { /** * Program entry point. + * * @param args command line args */ public static void main(String[] args) { diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java index 15fb24b6505e..b027763ed673 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java @@ -24,11 +24,8 @@ */ package com.iluwatar.factory.method; -/** - * The interface containing method for producing objects. - */ +/** The interface containing method for producing objects. */ public interface Blacksmith { Weapon manufactureWeapon(WeaponType weaponType); - } diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java index 411663ad1259..881b82a6a3f4 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java @@ -28,9 +28,7 @@ import java.util.EnumMap; import java.util.Map; -/** - * Concrete subclass for creating new objects. - */ +/** Concrete subclass for creating new objects. */ public class ElfBlacksmith implements Blacksmith { private static final Map ELFARSENAL; diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java index b076650eefb8..58b188e2996c 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java @@ -24,17 +24,8 @@ */ package com.iluwatar.factory.method; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * ElfWeapon. - */ -@RequiredArgsConstructor -@Getter -public class ElfWeapon implements Weapon { - - private final WeaponType weaponType; +/** ElfWeapon. */ +public record ElfWeapon(WeaponType weaponType) implements Weapon { @Override public String toString() { diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java index 4d856bba537b..6657b66a4974 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java @@ -28,9 +28,7 @@ import java.util.EnumMap; import java.util.Map; -/** - * Concrete subclass for creating new objects. - */ +/** Concrete subclass for creating new objects. */ public class OrcBlacksmith implements Blacksmith { private static final Map ORCARSENAL; diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java index 901e2028841f..c7bf473ce275 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java @@ -24,17 +24,8 @@ */ package com.iluwatar.factory.method; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * OrcWeapon. - */ -@RequiredArgsConstructor -@Getter -public class OrcWeapon implements Weapon { - - private final WeaponType weaponType; +/** OrcWeapon. */ +public record OrcWeapon(WeaponType weaponType) implements Weapon { @Override public String toString() { diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java index 37c528bedee6..0d5f12e488e0 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java @@ -24,11 +24,8 @@ */ package com.iluwatar.factory.method; -/** - * Weapon interface. - */ +/** Weapon interface. */ public interface Weapon { - WeaponType getWeaponType(); - + WeaponType weaponType(); } diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java b/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java index 156afae5523d..e1ab11a1f22a 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * WeaponType enumeration. - */ +/** WeaponType enumeration. */ @RequiredArgsConstructor public enum WeaponType { - SHORT_SWORD("short sword"), SPEAR("spear"), AXE("axe"), diff --git a/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java b/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java index cd597444b7b2..88fe6f6d7aa2 100644 --- a/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java +++ b/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.factory.method; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Factory Method example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Factory Method example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java b/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java index 9aa89d455cb1..7cd4dbeb234d 100644 --- a/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java +++ b/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java @@ -36,10 +36,8 @@ * and implemented by child classes, or implemented in a base class and optionally overridden by * derived classes—rather than by calling a constructor. * - *

Factory produces the object of its liking. - * The weapon {@link Weapon} manufactured by the blacksmith depends on the kind of factory - * implementation it is referring to. - *

+ *

Factory produces the object of its liking. The weapon {@link Weapon} manufactured by the + * blacksmith depends on the kind of factory implementation it is referring to. */ class FactoryMethodTest { @@ -91,13 +89,15 @@ void testElfBlacksmithWithSpear() { * This method asserts that the weapon object that is passed is an instance of the clazz and the * weapon is of type expectedWeaponType. * - * @param weapon weapon object which is to be verified + * @param weapon weapon object which is to be verified * @param expectedWeaponType expected WeaponType of the weapon - * @param clazz expected class of the weapon + * @param clazz expected class of the weapon */ private void verifyWeapon(Weapon weapon, WeaponType expectedWeaponType, Class clazz) { assertTrue(clazz.isInstance(weapon), "Weapon must be an object of: " + clazz.getName()); - assertEquals(expectedWeaponType, weapon - .getWeaponType(), "Weapon must be of weaponType: " + expectedWeaponType); + assertEquals( + expectedWeaponType, + weapon.weaponType(), + "Weapon must be of weaponType: " + expectedWeaponType); } } diff --git a/factory/README.md b/factory/README.md index ed841bd0e2da..da5b220e914e 100644 --- a/factory/README.md +++ b/factory/README.md @@ -1,44 +1,44 @@ --- -title: Factory +title: "Factory Pattern in Java: Streamlining Object Creation" +shortTitle: Factory +description: "Learn the Factory Design Pattern in Java with detailed examples and explanations. Understand how to create flexible and scalable code using the Factory Pattern. Ideal for developers looking to improve their object-oriented design skills." category: Creational language: en tag: - - Gang of Four + - Abstraction + - Encapsulation + - Gang of Four + - Instantiation + - Polymorphism --- -## Also known as +## Intent of Factory Design Pattern -* Simple Factory -* Static Factory Method +The Factory Design Pattern in Java is a creational pattern that defines an interface for creating an object but allows subclasses to alter the type of objects that will be created. This pattern promotes flexibility and scalability in your codebase. -## Intent - -Providing a static method encapsulated in a class called the factory, to hide the implementation -logic and make client code focus on usage rather than initializing new objects. - -## Explanation +## Detailed Explanation of Factory Pattern with Real-World Examples Real-world example -> Imagine an alchemist who is about to manufacture coins. The alchemist must be able to create both -> gold and copper coins and switching between them must be possible without modifying the existing -> source code. The factory pattern makes it possible by providing a static construction method which -> can be called with relevant parameters. +> Imagine a scenario in a bakery where different types of cakes are made using a Factory Design Pattern. The bakery's `CakeFactory` handles the creation process, allowing easy addition of new cake types without altering the core cake-making process. The `CakeFactory` can produce various types of cakes such as chocolate cake, vanilla cake, and strawberry cake. Instead of the bakery staff manually selecting ingredients and following specific recipes for each type of cake, they use the `CakeFactory` to handle the process. The customer simply requests a cake type, and the `CakeFactory` determines the appropriate ingredients and recipe to use, then creates the specific type of cake. This setup allows the bakery to easily add new cake types without modifying the core cake-making process, promoting flexibility and scalability. Wikipedia says -> Factory is an object for creating other objects – formally a factory is a function or method that -> returns objects of a varying prototype or class. +> Factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class. + +## Programmatic Example of Factory Pattern in Java -**Programmatic Example** +Imagine an alchemist who is about to manufacture coins. The alchemist must be able to create both gold and copper coins and switching between them must be possible without modifying the existing source code. The factory pattern makes it possible by providing a static construction method which can be called with relevant parameters. -We have an interface `Coin` and two implementations `GoldCoin` and `CopperCoin`. +In Java, you can implement the Factory Pattern by defining an interface `Coin` and its implementations `GoldCoin` and `CopperCoin`. The `CoinFactory` class provides a static method `getCoin` to create coin objects based on the type. ```java public interface Coin { String getDescription(); } +``` +```java public class GoldCoin implements Coin { static final String DESCRIPTION = "This is a gold coin."; @@ -48,7 +48,9 @@ public class GoldCoin implements Coin { return DESCRIPTION; } } +``` +```java public class CopperCoin implements Coin { static final String DESCRIPTION = "This is a copper coin."; @@ -74,8 +76,7 @@ public enum CoinType { } ``` -Then we have the static method `getCoin` to create coin objects encapsulated in the factory class -`CoinFactory`. +Then we have the static method `getCoin` to create coin objects encapsulated in the factory class `CoinFactory`. ```java public class CoinFactory { @@ -86,43 +87,33 @@ public class CoinFactory { } ``` -Now on the client code we can create different types of coins using the factory class. +Now, in the client code, we can generate various types of coins using the factory class. ```java -LOGGER.info("The alchemist begins his work."); -var coin1 = CoinFactory.getCoin(CoinType.COPPER); -var coin2 = CoinFactory.getCoin(CoinType.GOLD); -LOGGER.info(coin1.getDescription()); -LOGGER.info(coin2.getDescription()); +public static void main(String[] args) { + LOGGER.info("The alchemist begins his work."); + var coin1 = CoinFactory.getCoin(CoinType.COPPER); + var coin2 = CoinFactory.getCoin(CoinType.GOLD); + LOGGER.info(coin1.getDescription()); + LOGGER.info(coin2.getDescription()); +} ``` Program output: -```java -The alchemist begins his work. -This is a copper coin. -This is a gold coin. +``` +06:19:53.530 [main] INFO com.iluwatar.factory.App -- The alchemist begins his work. +06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a copper coin. +06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a gold coin. ``` -## Class Diagram - -![alt text](./etc/factory.urm.png "Factory pattern class diagram") - -## Applicability - -Use the factory pattern when you only care about the creation of an object, not how to create -and manage it. - -Pros - -* Allows keeping all objects creation in one place and avoid of spreading 'new' keyword across codebase. -* Allows to write loosely coupled code. Some of its main advantages include better testability, easy-to-understand code, swappable components, scalability and isolated features. - -Cons +## When to Use the Factory Pattern in Java -* The code becomes more complicated than it should be. +* Use the Factory Design Pattern in Java when the class does not know beforehand the exact types and dependencies of the objects it needs to create. +* When a method returns one of several possible classes that share a common super class and wants to encapsulate the logic of which object to create. +* The pattern is commonly used when designing frameworks or libraries to give the best flexibility and isolation from concrete class types. -## Known uses +## Real-World Applications of Factory Pattern in Java * [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) * [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) @@ -131,9 +122,29 @@ Cons * [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) (returns different singleton objects, depending on a protocol) * [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E)) * [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) and other similar methods. +* JavaFX uses Factory patterns for creating various UI controls tailored to the specifics of the user's environment. + +## Benefits and Trade-offs of Factory Pattern + +Benefits: + +* Implementing the Factory Pattern in your Java application reduces coupling between the implementation and the classes it uses. +* Supports the [Open/Closed Principle](https://java-design-patterns.com/principles/#open-closed-principle), as the system can introduce new types without changing existing code. + +Trade-offs: + +* The code can become more complicated due to the introduction of multiple additional classes. +* Overuse can make the code less readable if the underlying complexity of the object creation is low or unnecessary. + +## Related Java Design Patterns + +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Can be considered a kind of Factory that works with groups of products. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used in conjunction with Factory to ensure that a class has only one instance. +* [Builder](https://java-design-patterns.com/patterns/builder/): Separates the construction of a complex object from its representation, similar to how factories manage instantiation. +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): Is a factory of immutable content with separated builder and factory interfaces. -## Related patterns +## References and Credits -* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) -* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) -* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0Rk5y) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG) diff --git a/factory/pom.xml b/factory/pom.xml index 5468af6cdebf..7280d8b6d64a 100644 --- a/factory/pom.xml +++ b/factory/pom.xml @@ -34,6 +34,14 @@ factory + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/factory/src/main/java/com/iluwatar/factory/App.java b/factory/src/main/java/com/iluwatar/factory/App.java index 3162828e1003..ce9f205b0f15 100644 --- a/factory/src/main/java/com/iluwatar/factory/App.java +++ b/factory/src/main/java/com/iluwatar/factory/App.java @@ -27,20 +27,17 @@ import lombok.extern.slf4j.Slf4j; /** - * Factory is an object for creating other objects. It provides a static method to - * create and return objects of varying classes, in order to hide the implementation logic - * and makes client code focus on usage rather than objects initialization and management. + * Factory is an object for creating other objects. It provides a static method to create and return + * objects of varying classes, in order to hide the implementation logic and makes client code focus + * on usage rather than objects initialization and management. * - *

In this example an alchemist manufactures coins. CoinFactory is the factory class and it + *

In this example an alchemist manufactures coins. CoinFactory is the factory class, and it * provides a static method to create different types of coins. */ - @Slf4j public class App { - /** - * Program main entry point. - */ + /** Program main entry point. */ public static void main(String[] args) { LOGGER.info("The alchemist begins his work."); var coin1 = CoinFactory.getCoin(CoinType.COPPER); diff --git a/factory/src/main/java/com/iluwatar/factory/Coin.java b/factory/src/main/java/com/iluwatar/factory/Coin.java index 4adef8ea78e7..b8824222e5a8 100644 --- a/factory/src/main/java/com/iluwatar/factory/Coin.java +++ b/factory/src/main/java/com/iluwatar/factory/Coin.java @@ -24,11 +24,8 @@ */ package com.iluwatar.factory; -/** - * Coin interface. - */ +/** Coin interface. */ public interface Coin { String getDescription(); - } diff --git a/factory/src/main/java/com/iluwatar/factory/CoinFactory.java b/factory/src/main/java/com/iluwatar/factory/CoinFactory.java index 212bef4b947d..8940e3ade568 100644 --- a/factory/src/main/java/com/iluwatar/factory/CoinFactory.java +++ b/factory/src/main/java/com/iluwatar/factory/CoinFactory.java @@ -24,14 +24,10 @@ */ package com.iluwatar.factory; -/** - * Factory of coins. - */ +/** Factory of coins. */ public class CoinFactory { - /** - * Factory method takes as a parameter the coin type and calls the appropriate class. - */ + /** Factory method takes as a parameter the coin type and calls the appropriate class. */ public static Coin getCoin(CoinType type) { return type.getConstructor().get(); } diff --git a/factory/src/main/java/com/iluwatar/factory/CoinType.java b/factory/src/main/java/com/iluwatar/factory/CoinType.java index 069bd458644d..de1583bf9099 100644 --- a/factory/src/main/java/com/iluwatar/factory/CoinType.java +++ b/factory/src/main/java/com/iluwatar/factory/CoinType.java @@ -28,13 +28,10 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Enumeration for different types of coins. - */ +/** Enumeration for different types of coins. */ @RequiredArgsConstructor @Getter public enum CoinType { - COPPER(CopperCoin::new), GOLD(GoldCoin::new); diff --git a/factory/src/main/java/com/iluwatar/factory/CopperCoin.java b/factory/src/main/java/com/iluwatar/factory/CopperCoin.java index b4c586468a42..fa6f3f2d8ad3 100644 --- a/factory/src/main/java/com/iluwatar/factory/CopperCoin.java +++ b/factory/src/main/java/com/iluwatar/factory/CopperCoin.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factory; -/** - * CopperCoin implementation. - */ +/** CopperCoin implementation. */ public class CopperCoin implements Coin { static final String DESCRIPTION = "This is a copper coin."; diff --git a/factory/src/main/java/com/iluwatar/factory/GoldCoin.java b/factory/src/main/java/com/iluwatar/factory/GoldCoin.java index 4e693a4d350f..b5e1aef1a010 100644 --- a/factory/src/main/java/com/iluwatar/factory/GoldCoin.java +++ b/factory/src/main/java/com/iluwatar/factory/GoldCoin.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factory; -/** - * GoldCoin implementation. - */ +/** GoldCoin implementation. */ public class GoldCoin implements Coin { static final String DESCRIPTION = "This is a gold coin."; diff --git a/factory/src/test/java/com/iluwatar/factory/AppTest.java b/factory/src/test/java/com/iluwatar/factory/AppTest.java index 544a9bc46da3..702481525d45 100644 --- a/factory/src/test/java/com/iluwatar/factory/AppTest.java +++ b/factory/src/test/java/com/iluwatar/factory/AppTest.java @@ -32,7 +32,6 @@ class AppTest { @Test void shouldExecuteWithoutExceptions() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/fanout-fanin/README.md b/fanout-fanin/README.md index 5d519528c6fb..d92373760d97 100644 --- a/fanout-fanin/README.md +++ b/fanout-fanin/README.md @@ -1,117 +1,173 @@ --- -title: Fan-Out/Fan-In -category: Integration +title: "Fan-Out Fan-In Pattern in Java: Maximizing Concurrency for Efficient Data Processing" +shortTitle: Fan-Out/Fan-In +description: "Learn how the Fan-Out/Fan-In design pattern in Java can optimize concurrency and processing efficiency. Explore real-world examples, detailed explanations, and programmatic implementations." +category: Concurrency language: en tag: -- Microservices + - Asynchronous + - Data processing + - Decoupling + - Scalability --- -## Intent -The pattern is used when a source system needs to run one or more long-running processes that will fetch some data. -The source will not block itself waiting for the reply.
The pattern will run the same function in multiple -services or machines to fetch the data. This is equivalent to invoking the function multiple times on different chunks of data. +## Also known as -## Explanation -The FanOut/FanIn service will take in a list of requests and a consumer. Each request might complete at a different time. -FanOut/FanIn service will accept the input params and returns the initial system an ID to acknowledge that the pattern -service has received the requests. Now the caller will not wait or expect the result in the same connection. +* Scatter-Gather -Meanwhile, the pattern service will invoke the requests that have come. The requests might complete at different time. -These requests will be processed in different instances of the same function in different machines or services. As the -requests get completed, a callback service every time is called that transforms the result into a common single object format -that gets pushed to a consumer. The caller will be at the other end of the consumer receiving the result. +## Intent of Fan-Out/Fan-In Design Pattern -**Programmatic Example** +The Fan-Out/Fan-In design pattern in Java aims to improve concurrency and optimize processing time by dividing a task into multiple sub-tasks that can be processed in parallel (fan-out) and then combining the results of these sub-tasks into a single outcome (fan-in). -The implementation provided has a list of numbers and end goal is to square the numbers and add them to a single result. -`FanOutFanIn` class receives the list of numbers in the form of list of `SquareNumberRequest` and a `Consumer` instance -that collects the results as the requests get over. `SquareNumberRequest` will square the number with a random delay -to give the impression of a long-running process that can complete at any time. `Consumer` instance will add the results from -different `SquareNumberRequest` that will come random time instances. +## Detailed Explanation of Fan-Out/Fan-In Pattern with Real-World Examples -Let's look at `FanOutFanIn` class that fans out the requests in async processes. +Real-world example + +> A real-world example of the Fan-Out/Fan-In pattern in Java is a food delivery service like UberEats or DoorDash. When a customer places an order, the service (fan-out) sends out individual tasks to different restaurants to prepare the various items. Each restaurant works independently to prepare its part of the order. Once all restaurants have completed their tasks, the delivery service (fan-in) aggregates the items from different restaurants into a single order, ensuring that everything is delivered together to the customer. This parallel processing improves efficiency and ensures timely delivery. + +In plain words + +> The Fan-Out/Fan-In pattern distributes tasks across multiple concurrent processes or threads and then aggregates the results. + +Wikipedia says + +> In message-oriented middleware, the fan-out pattern models information exchange by delivering messages to one or multiple destinations in parallel, without waiting for responses. This allows a process to distribute tasks to various receivers simultaneously. +> +> The fan-in concept, on the other hand, typically refers to the aggregation of multiple inputs. In digital electronics, it describes the number of inputs a logic gate can handle. Combining these concepts, the Fan-Out/Fan-In pattern in software engineering involves distributing tasks (fan-out) and then aggregating the results (fan-in). + +## Programmatic Example of Fan-Out/Fan-In Pattern in Java + +The provided implementation involves a list of numbers with the objective to square them and aggregate the results. The `FanOutFanIn` class receives the list of numbers as `SquareNumberRequest` objects and a `Consumer` instance that collects the squared results as the requests complete. Each `SquareNumberRequest` squares its number with a random delay, simulating a long-running process that finishes at unpredictable times. The `Consumer` instance gathers the results from the various `SquareNumberRequest` objects as they become available at different times. + +Here's the `FanOutFanIn` class in Java that demonstrates the Fan-Out/Fan-In pattern by asynchronously distributing the requests. ```java public class FanOutFanIn { - public static Long fanOutFanIn( - final List requests, final Consumer consumer) { + public static Long fanOutFanIn(final List requests, final Consumer consumer) { - ExecutorService service = Executors.newFixedThreadPool(requests.size()); + ExecutorService service = Executors.newFixedThreadPool(requests.size()); - // fanning out - List> futures = - requests.stream() - .map( - request -> - CompletableFuture.runAsync(() -> request.delayedSquaring(consumer), service)) - .collect(Collectors.toList()); + // fanning out + List> futures = requests + .stream() + .map(request -> CompletableFuture.runAsync(() -> request.delayedSquaring(consumer), service)) + .collect(Collectors.toList()); - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - return consumer.getSumOfSquaredNumbers().get(); - } + return consumer.getSumOfSquaredNumbers().get(); + } } ``` -`Consumer` is used a callback class that will be called when a request is completed. This will aggregate -the result from all requests. +`Consumer` is used a callback class that will be called when a request is completed. This will aggregate the result from all requests. ```java public class Consumer { - private final AtomicLong sumOfSquaredNumbers; + private final AtomicLong sumOfSquaredNumbers; - Consumer(Long init) { - sumOfSquaredNumbers = new AtomicLong(init); - } + Consumer(Long init) { + sumOfSquaredNumbers = new AtomicLong(init); + } - public Long add(final Long num) { - return sumOfSquaredNumbers.addAndGet(num); - } + public Long add(final Long num) { + return sumOfSquaredNumbers.addAndGet(num); + } } ``` -Request is represented as a `SquareNumberRequest` that squares the number with random delay and calls the -`Consumer` once it is squared. +Request is represented as a `SquareNumberRequest` that squares the number with random delay and calls the `Consumer` once it is squared. ```java public class SquareNumberRequest { - private final Long number; - public void delayedSquaring(final Consumer consumer) { + private final Long number; - var minTimeOut = 5000L; + public void delayedSquaring(final Consumer consumer) { - SecureRandom secureRandom = new SecureRandom(); - var randomTimeOut = secureRandom.nextInt(2000); + var minTimeOut = 5000L; - try { - // this will make the thread sleep from 5-7s. - Thread.sleep(minTimeOut + randomTimeOut); - } catch (InterruptedException e) { - LOGGER.error("Exception while sleep ", e); - Thread.currentThread().interrupt(); - } finally { - consumer.add(number * number); + SecureRandom secureRandom = new SecureRandom(); + var randomTimeOut = secureRandom.nextInt(2000); + + try { + // this will make the thread sleep from 5-7s. + Thread.sleep(minTimeOut + randomTimeOut); + } catch (InterruptedException e) { + LOGGER.error("Exception while sleep ", e); + Thread.currentThread().interrupt(); + } finally { + consumer.add(number * number); + } } - } } ``` -## Class diagram -![alt-text](./etc/fanout-fanin.png) +Here is the App class with main method to drive the example. + +```java +public static void main(String[] args) { + final List numbers = Arrays.asList(1L, 3L, 4L, 7L, 8L); + + LOGGER.info("Numbers to be squared and get sum --> {}", numbers); + + final List requests = + numbers.stream().map(SquareNumberRequest::new).toList(); + + var consumer = new Consumer(0L); + + // Pass the request and the consumer to fanOutFanIn or sometimes referred as Orchestrator + // function + final Long sumOfSquaredNumbers = FanOutFanIn.fanOutFanIn(requests, consumer); + + LOGGER.info("Sum of all squared numbers --> {}", sumOfSquaredNumbers); +} +``` + +Running the example produces the following console output. + +``` +06:52:04.622 [main] INFO com.iluwatar.fanout.fanin.App -- Numbers to be squared and get sum --> [1, 3, 4, 7, 8] +06:52:11.465 [main] INFO com.iluwatar.fanout.fanin.App -- Sum of all squared numbers --> 139 +``` + +## When to Use the Fan-Out/Fan-In Pattern in Java + +The Fan-Out/Fan-In design pattern in Java is appropriate in scenarios where tasks can be broken down and executed in parallel, especially suitable for data processing, batch processing, and situations requiring aggregation of results from various sources. + +## Fan-Out/Fan-In Pattern Java Tutorials + +* [Fan-out/fan-in scenario in Durable Functions - Cloud backup example (Microsoft)](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-cloud-backup) +* [Understanding Azure Durable Functions - Part 8: The Fan Out/Fan In Pattern (Don't Code Tired)](http://dontcodetired.com/blog/post/Understanding-Azure-Durable-Functions-Part-8-The-Fan-OutFan-In-Pattern) +* [Understanding the Fan-Out/Fan-In API Integration Pattern (DZone)](https://dzone.com/articles/understanding-the-fan-out-fan-in-api-integration-p) + +## Real-World Applications of Fan-Out/Fan-In Pattern in Java + +* The Fan-Out/Fan-In pattern in Java is widely used in large-scale data processing applications. +* Services requiring aggregation from multiple sources before delivering a response, such as in distributed caching or load balancing systems. + +## Benefits and Trade-offs of Fan-Out/Fan-In Pattern + +Benefits: + +* Enhances performance by parallel processing. +* Increases responsiveness of systems. +* Efficient utilization of multi-core processor architectures. -## Applicability +Trade-offs: -Use this pattern when you can divide the workload into multiple chunks that can be dealt with separately. +* Increased complexity in error handling. +* Potential for increased overhead due to task synchronization and result aggregation. +* Dependency on the underlying infrastructure's ability to support concurrent execution. -## Related patterns +## Related Java Design Patterns -* [Aggregator Microservices](https://java-design-patterns.com/patterns/aggregator-microservices/) -* [API Gateway](https://java-design-patterns.com/patterns/api-gateway/) +* MapReduce: Similar to Fan-Out/Fan-In, MapReduce also involves distributing tasks across a number of workers (map) and aggregating the results (reduce), which is particularly useful for processing large data sets. +* [Command](https://java-design-patterns.com/patterns/command/): Command Pattern facilitates the decoupling of the sender and the receiver, akin to how Fan-Out/Fan-In decouples task submission from task processing. +* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Works synergistically with Fan-Out/Fan-In by organizing task execution where producers distribute tasks that are processed by multiple consumers, and results are then combined, enhancing throughput and efficiency in data processing. -## Credits +## References and Credits -* [Understanding Azure Durable Functions - Part 8: The Fan Out/Fan In Pattern](http://dontcodetired.com/blog/post/Understanding-Azure-Durable-Functions-Part-8-The-Fan-OutFan-In-Pattern) -* [Fan-out/fan-in scenario in Durable Functions - Cloud backup example](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-cloud-backup) -* [Understanding the Fan-Out/Fan-In API Integration Pattern](https://dzone.com/articles/understanding-the-fan-out-fan-in-api-integration-p) \ No newline at end of file +* [Java Concurrency in Practice](https://amzn.to/3vXytsb) +* [Patterns of Enterprise Application Architecture](https://amzn.to/49QQcPD) diff --git a/fanout-fanin/pom.xml b/fanout-fanin/pom.xml index 2cc2c117f39d..1052918ee83f 100644 --- a/fanout-fanin/pom.xml +++ b/fanout-fanin/pom.xml @@ -34,6 +34,14 @@ 4.0.0 fanout-fanin + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java index 83f51f647048..7a34c22149b5 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java @@ -28,8 +28,6 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; - - /** * FanOut/FanIn pattern is a concurrency pattern that refers to executing multiple instances of the * activity function concurrently. The "fan out" part is essentially splitting the data into diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java index 90b7c63855e3..95b7c663abe2 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java @@ -27,8 +27,6 @@ import java.util.concurrent.atomic.AtomicLong; import lombok.Getter; - - /** * Consumer or callback class that will be called every time a request is complete This will * aggregate individual result to form a final result. diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java index 9274583219b5..9239e03f46ee 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java @@ -30,7 +30,7 @@ import java.util.concurrent.Executors; /** - * FanOutFanIn class processes long running requests, when any of the processes gets over, result is + * FanOutFanIn class processes long-running requests, when any of the processes gets over, result is * passed over to the consumer or the callback function. Consumer will aggregate the results as they * keep on completing. */ @@ -38,6 +38,7 @@ public class FanOutFanIn { /** * the main fanOutFanIn function or orchestrator function. + * * @param requests List of numbers that need to be squared and summed up * @param consumer Takes in the squared number from {@link SquareNumberRequest} and sums it up * @return Aggregated sum of all squared numbers. diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java index 4ae062a62b38..7f12e842031e 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java @@ -29,7 +29,7 @@ import lombok.extern.slf4j.Slf4j; /** - * Squares the number with a little timeout to give impression of long running process that return + * Squares the number with a little timeout to give impression of long-running process that return * at different times. */ @Slf4j @@ -39,10 +39,11 @@ public class SquareNumberRequest { private final Long number; /** - * Squares the number with a little timeout to give impression of long running process that return + * Squares the number with a little timeout to give impression of long-running process that return * at different times. + * * @param consumer callback class that takes the result after the delay. - * */ + */ public void delayedSquaring(final Consumer consumer) { var minTimeOut = 5000L; diff --git a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java index 3f2bee396357..0fe64096fddb 100644 --- a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java +++ b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.fanout.fanin; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { - @Test - void shouldLaunchApp() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldLaunchApp() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java index bd31108b702d..55260c36aa3c 100644 --- a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java +++ b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java @@ -24,10 +24,10 @@ */ package com.iluwatar.fanout.fanin; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; class FanOutFanInTest { diff --git a/feature-toggle/README.md b/feature-toggle/README.md index 982108bc7440..b010cba5977e 100644 --- a/feature-toggle/README.md +++ b/feature-toggle/README.md @@ -1,70 +1,113 @@ --- -title: Feature Toggle +title: "Feature Toggle Pattern in Java: Managing Features in Production Seamlessly" +shortTitle: Feature Toggle +description: "Learn how to implement the Feature Toggle design pattern in Java. This guide covers dynamic feature management, benefits, use cases, and practical examples to help you enhance your software development process." category: Behavioral language: en tag: - - Extensibility + - Decoupling + - Extensibility + - Feature management + - Scalability --- ## Also known as -Feature Flag -## Intent -A technique used in software development to control and manage the rollout of specific features or functionality in a -program without changing the code. It can act as an on/off switch for features depending on the status or properties of -other values in the program. This is similar to A/B testing, where features are rolled out based on properties such as -location or device. Implementing this design pattern can increase code complexity, and it is important to remember to -remove redundant code if this design pattern is being used to phase out a system or feature. +* Feature Flag +* Feature Switch + +## Intent of Feature Toggle Design Pattern + +To enable or disable features in a software application dynamically without deploying new code. + +## Detailed Explanation of Feature Toggle Pattern with Real-World Examples -## Explanation Real-world Example -> This design pattern works really well in any sort of development, in particular mobile development. Say you want to -> introduce a feature such as dark mode, but you want to ensure that the feature works correctly and don't want to roll -> out the feature to everyone immediately. You write in the code, and have it switched off as default. From here, it is -> easy to turn on the code for specific users based on selection criteria, or randomly. This will also allow the feature -> to be turned off easily without any drastic changes to the code, or any need for redeployment or updates. + +> A real-world example of the Feature Toggle pattern is Netflix's rollout of new user interface features. When Netflix decides to introduce a new feature, such as a redesigned homepage layout or a new recommendation algorithm, they use feature toggles to control the release. Initially, the new feature is toggled off for most users, allowing only a small group of users (e.g., beta testers) to experience and provide feedback on the feature. Based on the feedback and performance metrics, Netflix can quickly toggle the feature on for a broader audience or turn it off if issues are detected, all without redeploying the application. This approach allows Netflix to continuously innovate and improve their platform while minimizing risk and ensuring a stable user experience. In plain words -> Feature Toggle is a way to introduce new features gradually instead of deployment all at once. + +> The Feature Toggle design pattern in Java allows developers to introduce new features gradually instead of deploying them all at once, facilitating better dynamic feature management. Wikipedia says -> A feature toggle in software development provides an alternative to maintaining multiple feature branches in source -> code. A condition within the code enables or disables a feature during runtime. In agile settings the toggle is -> used in production, to switch on the feature on demand, for some or all the users. -## Programmatic Example -This example shows Java code that allows a feature to show when it is enabled by the developer, and when a user is a -Premium member of the application. This is useful for subscription locked features. +> A feature toggle in software development provides an alternative to maintaining multiple feature branches in source code. A condition within the code enables or disables a feature during runtime. In agile settings the toggle is used in production, to switch on the feature on demand, for some or all the users. + +## Programmatic Example of Feature Toggle Pattern in Java + +This Java code example demonstrates how to display a feature when it is enabled by the developer and the user is a Premium member of the application. This approach is useful for managing subscription-locked features. + +The Feature Toggle pattern enables the seamless activation or deactivation of entire code executions. This allows features to be managed dynamically based on user information or configuration properties. + +Key Components: + +1. `PropertiesFeatureToggleVersion`: This class uses properties to control the feature toggle. The properties determine whether the enhanced version of the welcome message, which is personalized, is turned on or off. + +2. `TieredFeatureToggleVersion`: This class uses user information to control the feature toggle. The feature of the personalized welcome message is dependent on the user group the user is in. + +3. `User`: This class represents the user of the application. + +4. `UserGroup`: This class represents the group the user belongs to. + ```java -public class FeatureToggleExample { - // Bool for feature enabled or disabled - private static boolean isNewFeatureEnabled = false; - - public static void main(String[] args) { - boolean userIsPremium = true; // Example: Check if the user is a premium user - - // Check if the new feature should be enabled for the user - if (userIsPremium && isNewFeatureEnabled) { - // User is premium and the new feature is enabled - showNewFeature(); - } - } - - private static void showNewFeature() { - // If user is allowed to see locked feature, this is where the code would go - } +public static void main(String[] args) { + + // Demonstrates the PropertiesFeatureToggleVersion running with properties + // that set the feature toggle to enabled. + + final var properties = new Properties(); + properties.put("enhancedWelcome", true); + var service = new PropertiesFeatureToggleVersion(properties); + final var welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); + LOGGER.info(welcomeMessage); + + // Demonstrates the PropertiesFeatureToggleVersion running with properties + // that set the feature toggle to disabled. Note the difference in the printed welcome message + // where the username is not included. + + final var turnedOff = new Properties(); + turnedOff.put("enhancedWelcome", false); + var turnedOffService = new PropertiesFeatureToggleVersion(turnedOff); + final var welcomeMessageturnedOff = + turnedOffService.getWelcomeMessage(new User("Jamie No Code")); + LOGGER.info(welcomeMessageturnedOff); + + // Demonstrates the TieredFeatureToggleVersion setup with + // two users: one on the free tier and the other on the paid tier. When the + // Service#getWelcomeMessage(User) method is called with the paid user, the welcome + // message includes their username. In contrast, calling the same service with the free tier user results + // in a more generic welcome message without the username. + + var service2 = new TieredFeatureToggleVersion(); + + final var paidUser = new User("Jamie Coder"); + final var freeUser = new User("Alan Defect"); + + UserGroup.addUserToPaidGroup(paidUser); + UserGroup.addUserToFreeGroup(freeUser); + + final var welcomeMessagePaidUser = service2.getWelcomeMessage(paidUser); + final var welcomeMessageFreeUser = service2.getWelcomeMessage(freeUser); + LOGGER.info(welcomeMessageFreeUser); + LOGGER.info(welcomeMessagePaidUser); } ``` -The code shows how simple it is to implement this design pattern, and the criteria can be further refined or broadened -should the developers choose to do so. -## Class diagram -![alt text](./etc/feature-toggle.png "Feature Toggle") +Running the example produces the following output. + +``` +07:31:50.802 [main] INFO com.iluwatar.featuretoggle.App -- Welcome Jamie No Code. You're using the enhanced welcome message. +07:31:50.804 [main] INFO com.iluwatar.featuretoggle.App -- Welcome to the application. +07:31:50.804 [main] INFO com.iluwatar.featuretoggle.App -- I suppose you can use this software. +07:31:50.804 [main] INFO com.iluwatar.featuretoggle.App -- You're amazing Jamie Coder. Thanks for paying for this awesome software. +``` + +## When to Use the Feature Toggle Pattern in Java -## Applicability -Use the Feature Toggle pattern when +Use the Feature Toggle Pattern in Java when: -* Giving different features to different users. +* Dynamic feature management to different users and groups. * Rolling out a new feature incrementally. * Switching between development and production environments. * Quickly disable problematic features @@ -72,15 +115,33 @@ Use the Feature Toggle pattern when * Ability to maintain multiple version releases of a feature * 'Hidden' deployment, releasing a feature in code for designated testing but not publicly making it available -## Consequences -Consequences involved with using the Feature Toggle pattern +## Real-World Applications of Feature Toggle Pattern in Java + +* Many web development platforms utilize the Feature Toggle design pattern to gradually roll out new features to users, ensuring stability and effective dynamic feature management. +* Enterprise applications use feature toggles to enable or disable features during runtime to cater to different market needs. + +## Benefits and Trade-offs of Feature Toggle Pattern + +Benefits: + +* Facilitates A/B testing and canary releases. +* Allows for quicker rollback and minimal risk deployments. +* Enables conditional feature execution without redeploying the application. + +Trade-offs: + +* Code complexity is increased. +* Testing of multiple states is harder and more time-consuming. +* Potential for technical debt if toggles remain in the code longer than necessary. +* Risk of toggle misconfiguration leading to unexpected behavior. + +## Related Java Design Patterns -* Code complexity is increased -* Testing of multiple states is harder and more time-consuming -* Confusion between friends on why features are missing -* Keeping documentation up to date with all features can be difficult +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Both patterns allow changing the behavior of software at runtime. The Feature Toggle changes features dynamically, while the Strategy allows switching algorithms or strategies. +* [Observer](https://java-design-patterns.com/patterns/observer/): Useful for implementing feature toggles by notifying components of feature state changes, which allows dynamic feature modification without restarts. -## Credits +## References and Credits -* [Martin Fowler 29 October 2010 (2010-10-29).](http://martinfowler.com/bliki/FeatureToggle.html) -* [Feature Toggle - Java Design Patterns](https://java-design-patterns.com/patterns/feature-toggle/) +* [Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation](https://amzn.to/4488ESM) +* [Release It! Design and Deploy Production-Ready Software](https://amzn.to/3UoeJY4) +* [Feature Toggle (Martin Fowler)](http://martinfowler.com/bliki/FeatureToggle.html) diff --git a/feature-toggle/pom.xml b/feature-toggle/pom.xml index 8a60b4876175..c21080827a57 100644 --- a/feature-toggle/pom.xml +++ b/feature-toggle/pom.xml @@ -34,6 +34,14 @@ 4.0.0 feature-toggle + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java index 421f86e4c2eb..30c729dd8992 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java @@ -71,13 +71,18 @@ public class App { */ public static void main(String[] args) { + // Demonstrates the PropertiesFeatureToggleVersion running with properties + // that set the feature toggle to enabled. + final var properties = new Properties(); properties.put("enhancedWelcome", true); var service = new PropertiesFeatureToggleVersion(properties); final var welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); LOGGER.info(welcomeMessage); - // --------------------------------------------- + // Demonstrates the PropertiesFeatureToggleVersion running with properties + // that set the feature toggle to disabled. Note the difference in the printed welcome message + // where the username is not included. final var turnedOff = new Properties(); turnedOff.put("enhancedWelcome", false); @@ -86,7 +91,12 @@ public static void main(String[] args) { turnedOffService.getWelcomeMessage(new User("Jamie No Code")); LOGGER.info(welcomeMessageturnedOff); - // -------------------------------------------- + // Demonstrates the TieredFeatureToggleVersion setup with + // two users: one on the free tier and the other on the paid tier. When the + // Service#getWelcomeMessage(User) method is called with the paid user, the welcome + // message includes their username. In contrast, calling the same service with the free tier + // user results + // in a more generic welcome message without the username. var service2 = new TieredFeatureToggleVersion(); diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java index 4dcc45b1300c..7c8287622bda 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java @@ -28,8 +28,8 @@ /** * Simple interfaces to allow the calling of the method to generate the welcome message for a given - * user. While there is a helper method to gather the status of the feature toggle. In some - * cases there is no need for the {@link Service#isEnhanced()} in {@link + * user. While there is a helper method to gather the status of the feature toggle. In some cases + * there is no need for the {@link Service#isEnhanced()} in {@link * com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} where the toggle is * determined by the actual {@link User}. * @@ -53,5 +53,4 @@ public interface Service { * @return Boolean {@code true} if enhanced. */ boolean isEnhanced(); - } diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java index ce7886a10039..cfc2ab56b293 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java @@ -46,8 +46,8 @@ public class PropertiesFeatureToggleVersion implements Service { /** - * True if the welcome message to be returned is the enhanced venison or not. For - * this service it will see the value of the boolean that was set in the constructor {@link + * True if the welcome message to be returned is the enhanced venison or not. For this service it + * will see the value of the boolean that was set in the constructor {@link * PropertiesFeatureToggleVersion#PropertiesFeatureToggleVersion(Properties)} */ private final boolean enhanced; @@ -77,12 +77,12 @@ public PropertiesFeatureToggleVersion(final Properties properties) { /** * Generate a welcome message based on the user being passed and the status of the feature toggle. * If the enhanced version is enabled, then the message will be personalised with the name of the - * passed {@link User}. However if disabled then a generic version fo the message is returned. + * passed {@link User}. However, if disabled then a generic version fo the message is returned. * * @param user the {@link User} to be displayed in the message if the enhanced version is enabled - * see {@link PropertiesFeatureToggleVersion#isEnhanced()}. If the enhanced version is - * enabled, then the message will be personalised with the name of the passed {@link - * User}. However if disabled then a generic version fo the message is returned. + * see {@link PropertiesFeatureToggleVersion#isEnhanced()}. If the enhanced version is + * enabled, then the message will be personalised with the name of the passed {@link User}. + * However, if disabled then a generic version fo the message is returned. * @return Resulting welcome message. * @see User */ diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java index 74eb44340940..f5342da01a57 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java @@ -30,10 +30,10 @@ /** * This example of the Feature Toggle pattern shows how it could be implemented based on a {@link - * User}. Therefore showing its use within a tiered application where the paying users get access to - * different content or better versions of features. So in this instance a {@link User} is passed in - * and if they are found to be on the {@link UserGroup#isPaid(User)} they are welcomed with a - * personalised message. While the other is more generic. However this pattern is limited to simple + * User}. Therefore, showing its use within a tiered application where the paying users get access + * to different content or better versions of features. So in this instance a {@link User} is passed + * in and if they are found to be on the {@link UserGroup#isPaid(User)} they are welcomed with a + * personalised message. While the other is more generic. However, this pattern is limited to simple * examples such as the one below. * * @see Service @@ -49,8 +49,8 @@ public class TieredFeatureToggleVersion implements Service { * the enhanced version of the welcome message will be returned where the username is displayed. * * @param user the {@link User} to generate the welcome message for, different messages are - * displayed if the user is in the {@link UserGroup#isPaid(User)} or {@link - * UserGroup#freeGroup} + * displayed if the user is in the {@link UserGroup#isPaid(User)} or {@link + * UserGroup#freeGroup} * @return Resulting welcome message. * @see User * @see UserGroup @@ -66,7 +66,7 @@ public String getWelcomeMessage(User user) { /** * Method that checks if the welcome message to be returned is the enhanced version. For this - * instance as the logic is driven by the user group. This method is a little redundant. However + * instance as the logic is driven by the user group. This method is a little redundant. However, * can be used to show that there is an enhanced version available. * * @return Boolean value {@code true} if enhanced. @@ -75,5 +75,4 @@ public String getWelcomeMessage(User user) { public boolean isEnhanced() { return true; } - } diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java index c8abdedcd7a9..52badacaba2b 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java @@ -33,25 +33,23 @@ import java.util.Properties; import org.junit.jupiter.api.Test; -/** - * Test Properties Toggle - */ +/** Test Properties Toggle */ class PropertiesFeatureToggleVersionTest { @Test void testNullPropertiesPassed() { - assertThrows(IllegalArgumentException.class, () -> { - new PropertiesFeatureToggleVersion(null); - }); + assertThrows(IllegalArgumentException.class, () -> new PropertiesFeatureToggleVersion(null)); } @Test void testNonBooleanProperty() { - assertThrows(IllegalArgumentException.class, () -> { - final var properties = new Properties(); - properties.setProperty("enhancedWelcome", "Something"); - new PropertiesFeatureToggleVersion(properties); - }); + assertThrows( + IllegalArgumentException.class, + () -> { + final var properties = new Properties(); + properties.setProperty("enhancedWelcome", "Something"); + new PropertiesFeatureToggleVersion(properties); + }); } @Test @@ -61,7 +59,8 @@ void testFeatureTurnedOn() { var service = new PropertiesFeatureToggleVersion(properties); assertTrue(service.isEnhanced()); final var welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); - assertEquals("Welcome Jamie No Code. You're using the enhanced welcome message.", welcomeMessage); + assertEquals( + "Welcome Jamie No Code. You're using the enhanced welcome message.", welcomeMessage); } @Test diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java index 7eb03b833260..67314f4041e3 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Tiered Feature Toggle - */ +/** Test Tiered Feature Toggle */ class TieredFeatureToggleVersionTest { final User paidUser = new User("Jamie Coder"); diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java index b605cec5f0fb..b9bfb0befed4 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Test User Group specific feature - */ +/** Test User Group specific feature */ class UserGroupTest { @Test @@ -53,17 +51,13 @@ void testAddUserToPaidGroup() { void testAddUserToPaidWhenOnFree() { var user = new User("Paid User"); UserGroup.addUserToFreeGroup(user); - assertThrows(IllegalArgumentException.class, () -> { - UserGroup.addUserToPaidGroup(user); - }); + assertThrows(IllegalArgumentException.class, () -> UserGroup.addUserToPaidGroup(user)); } @Test void testAddUserToFreeWhenOnPaid() { var user = new User("Free User"); UserGroup.addUserToPaidGroup(user); - assertThrows(IllegalArgumentException.class, () -> { - UserGroup.addUserToFreeGroup(user); - }); + assertThrows(IllegalArgumentException.class, () -> UserGroup.addUserToFreeGroup(user)); } } diff --git a/filterer/README.md b/filterer/README.md index 09ca7a38e575..be895411ba25 100644 --- a/filterer/README.md +++ b/filterer/README.md @@ -1,52 +1,57 @@ --- -title: Filterer +title: "Filterer Pattern in Java: Streamlining Data Processing with Dynamic Filters" +shortTitle: Filterer +description: "Learn about the Filterer design pattern in Java, which enhances data processing flexibility by applying a series of filters to data objects. Ideal for dynamic and scalable filtering solutions." language: en -category: Functional +category: Behavioral tag: - - Extensibility + - Data processing + - Data transformation + - Decoupling + - Functional decomposition + - Object composition + - Performance + - Runtime --- -## Name / classification +## Also known as -Filterer +* Filters +* Pipes and Filters -## Intent +## Intent of Filterer Design Pattern -The intent of this design pattern is to introduce a functional interface that will add a -functionality for container-like objects to easily return filtered versions of themselves. +The Filterer design pattern in Java is essential for creating dynamic and scalable filtering solutions. This pattern allows the application of a series of filters to data objects, enhancing data processing flexibility and scalability. -## Explanation +## Detailed Explanation of Filterer Pattern with Real-World Examples -Real world example +Real-world example -> We are designing a threat (malware) detection software which can analyze target systems for -> threats that are present in it. In the design we have to take into consideration that new -> Threat types can be added later. Additionally, there is a requirement that the threat detection -> system can filter the detected threats based on different criteria (the target system acts as -> container-like object for threats). +> Imagine a library system employing the Filterer pattern to dynamically combine filter criteria such as genre, author, and availability. This Java pattern makes the system more maintainable and scalable. Instead of writing separate methods for each possible combination of criteria, the library system employs the Filterer design pattern. Each filter criterion is encapsulated as an object, and these filter objects can be combined dynamically at runtime to create complex filtering logic. For example, a user can search for books that are both available and published after 2010 by combining the availability filter and the publication year filter. This approach makes the system more flexible and easier to maintain, as new filtering criteria can be added without modifying existing code. In plain words -> Filterer pattern is a design pattern that helps container-like objects return filtered versions -> of themselves. +> Filterer pattern is a design pattern that helps container-like objects return filtered versions of themselves. -**Programmatic Example** +## Programmatic Example of Filterer Pattern in Java -To model the threat detection example presented above we introduce `Threat` and `ThreatAwareSystem` -interfaces. +To illustrate, we use the Filterer design pattern for a malware detection system in Java. This system can filter threats based on various criteria, showcasing the pattern’s flexibility and dynamic nature. In the design we have to take into consideration that new Threat types can be added later. Additionally, there is a requirement that the threat detection system can filter the detected threats based on different criteria (the target system acts as container-like object for threats). + +To model the threat detection system, we introduce `Threat` and `ThreatAwareSystem` interfaces. ```java public interface Threat { - String name(); - int id(); - ThreatType type(); + String name(); + int id(); + ThreatType type(); } +``` +```java public interface ThreatAwareSystem { - String systemId(); - List threats(); - Filterer filtered(); - + String systemId(); + List threats(); + Filterer filtered(); } ``` @@ -55,181 +60,208 @@ Notice the `filtered` method that returns instance of `Filterer` interface which ```java @FunctionalInterface public interface Filterer { - G by(Predicate predicate); + G by(Predicate predicate); } ``` -It is used to fulfill the requirement for system to be able to filter itself based on threat -properties. The container-like object (`ThreatAwareSystem` in our case) needs to have a method that -returns an instance of `Filterer`. This helper interface gives ability to covariantly specify a -lower bound of contravariant `Predicate` in the subinterfaces of interfaces representing the -container-like objects. - -In our example we will be able to pass a predicate that takes `? extends Threat` object and -return `? extends ThreatAwareSystem` from `Filtered::by` method. A simple implementation -of `ThreatAwareSystem`: +A simple implementation of `ThreatAwareSystem`: ```java public class SimpleThreatAwareSystem implements ThreatAwareSystem { - private final String systemId; - private final ImmutableList issues; - - public SimpleThreatAwareSystem(final String systemId, final List issues) { - this.systemId = systemId; - this.issues = ImmutableList.copyOf(issues); - } - - @Override - public String systemId() { - return systemId; - } - - @Override - public List threats() { - return new ArrayList<>(issues); - } - - @Override - public Filterer filtered() { - return this::filteredGroup; - } - - private ThreatAwareSystem filteredGroup(Predicate predicate) { - return new SimpleThreatAwareSystem(this.systemId, filteredItems(predicate)); - } - - private List filteredItems(Predicate predicate) { - return this.issues.stream() - .filter(predicate) - .collect(Collectors.toList()); - } + private final String systemId; + private final ImmutableList issues; + + public SimpleThreatAwareSystem(final String systemId, final List issues) { + this.systemId = systemId; + this.issues = ImmutableList.copyOf(issues); + } + + @Override + public String systemId() { + return systemId; + } + + @Override + public List threats() { + return new ArrayList<>(issues); + } + + @Override + public Filterer filtered() { + return this::filteredGroup; + } + + private ThreatAwareSystem filteredGroup(Predicate predicate) { + return new SimpleThreatAwareSystem(this.systemId, filteredItems(predicate)); + } + + private List filteredItems(Predicate predicate) { + return this.issues.stream() + .filter(predicate) + .collect(Collectors.toList()); + } } ``` -The `filtered` method is overridden to filter the threats list by given predicate. - -Now if we introduce a new subtype of `Threat` interface that adds probability with which given -threat can appear: +Now if we introduce a new subtype of `Threat` interface that adds probability with which given threat can appear: ```java public interface ProbableThreat extends Threat { - double probability(); + double probability(); } ``` -We can also introduce a new interface that represents a system that is aware of threats with their -probabilities: +We can also introduce a new interface that represents a system that is aware of threats with their probabilities: -````java +```java public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem { - @Override - List threats(); - - @Override - Filterer filtered(); + @Override + List threats(); + @Override + Filterer filtered(); } -```` +``` -Notice how we override the `filtered` method in `ProbabilisticThreatAwareSystem` and specify -different return covariant type by specifying different generic types. Our interfaces are clean and -not cluttered by default implementations. We we will be able to filter -`ProbabilisticThreatAwareSystem` by `ProbableThreat` properties: +We will be able to filter `ProbabilisticThreatAwareSystem` by `ProbableThreat` properties: ```java public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreatAwareSystem { - private final String systemId; - private final ImmutableList threats; + private final String systemId; + private final ImmutableList threats; + + public SimpleProbabilisticThreatAwareSystem(final String systemId, final List threats) { + this.systemId = systemId; + this.threats = ImmutableList.copyOf(threats); + } + + @Override + public String systemId() { + return systemId; + } + + @Override + public List threats() { + return threats; + } + + @Override + public Filterer filtered() { + return this::filteredGroup; + } + + private ProbabilisticThreatAwareSystem filteredGroup(final Predicate predicate) { + return new SimpleProbabilisticThreatAwareSystem(this.systemId, filteredItems(predicate)); + } + + private List filteredItems(final Predicate predicate) { + return this.threats.stream() + .filter(predicate) + .collect(Collectors.toList()); + } +} +``` - public SimpleProbabilisticThreatAwareSystem(final String systemId, final List threats) { - this.systemId = systemId; - this.threats = ImmutableList.copyOf(threats); - } +Now let's see the full example in action showing how the filterer pattern works in practice. - @Override - public String systemId() { - return systemId; - } +```java +@Slf4j +public class App { - @Override - public List threats() { - return threats; + public static void main(String[] args) { + filteringSimpleThreats(); + filteringSimpleProbableThreats(); } - @Override - public Filterer filtered() { - return this::filteredGroup; - } + private static void filteringSimpleProbableThreats() { + LOGGER.info("### Filtering ProbabilisticThreatAwareSystem by probability ###"); - private ProbabilisticThreatAwareSystem filteredGroup(final Predicate predicate) { - return new SimpleProbabilisticThreatAwareSystem(this.systemId, filteredItems(predicate)); - } + var trojanArcBomb = new SimpleProbableThreat("Trojan-ArcBomb", 1, ThreatType.TROJAN, 0.99); + var rootkit = new SimpleProbableThreat("Rootkit-Kernel", 2, ThreatType.ROOTKIT, 0.8); - private List filteredItems(final Predicate predicate) { - return this.threats.stream() - .filter(predicate) - .collect(Collectors.toList()); + List probableThreats = List.of(trojanArcBomb, rootkit); + + var probabilisticThreatAwareSystem = + new SimpleProbabilisticThreatAwareSystem("Sys-1", probableThreats); + + LOGGER.info("Filtering ProbabilisticThreatAwareSystem. Initial : " + + probabilisticThreatAwareSystem); + + //Filtering using filterer + var filteredThreatAwareSystem = probabilisticThreatAwareSystem.filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); + + LOGGER.info("Filtered by probability = 0.99 : " + filteredThreatAwareSystem); } -} -``` -Now if we want filter `ThreatAwareSystem` by threat type we can do: + private static void filteringSimpleThreats() { + LOGGER.info("### Filtering ThreatAwareSystem by ThreatType ###"); -```java -Threat rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit"); -Threat trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan"); -List threats = List.of(rootkit, trojan); + var rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit"); + var trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan"); + List threats = List.of(rootkit, trojan); -ThreatAwareSystem threatAwareSystem = new SimpleThreatAwareSystem("System-1", threats); + var threatAwareSystem = new SimpleThreatAwareSystem("Sys-1", threats); -ThreatAwareSystem rootkitThreatAwareSystem = threatAwareSystem.filtered() - .by(threat -> threat.type() == ThreatType.ROOTKIT); -``` + LOGGER.info("Filtering ThreatAwareSystem. Initial : " + threatAwareSystem); -Or if we want to filter `ProbabilisticThreatAwareSystem`: + //Filtering using Filterer + var rootkitThreatAwareSystem = threatAwareSystem.filtered() + .by(threat -> threat.type() == ThreatType.ROOTKIT); -```java -ProbableThreat malwareTroyan = new SimpleProbableThreat("Troyan-ArcBomb", 1, ThreatType.TROJAN, 0.99); -ProbableThreat rootkit = new SimpleProbableThreat("Rootkit-System", 2, ThreatType.ROOTKIT, 0.8); -List probableThreats = List.of(malwareTroyan, rootkit); + LOGGER.info("Filtered by threatType = ROOTKIT : " + rootkitThreatAwareSystem); + } +} +``` -ProbabilisticThreatAwareSystem simpleProbabilisticThreatAwareSystem =new SimpleProbabilisticThreatAwareSystem("System-1", probableThreats); +Running the example produces the following console output. -ProbabilisticThreatAwareSystem filtered = simpleProbabilisticThreatAwareSystem.filtered() - .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); ``` +08:33:23.568 [main] INFO com.iluwatar.filterer.App -- ### Filtering ThreatAwareSystem by ThreatType ### +08:33:23.574 [main] INFO com.iluwatar.filterer.App -- Filtering ThreatAwareSystem. Initial : SimpleThreatAwareSystem(systemId=Sys-1, issues=[SimpleThreat(threatType=ROOTKIT, id=1, name=Simple-Rootkit), SimpleThreat(threatType=TROJAN, id=2, name=Simple-Trojan)]) +08:33:23.576 [main] INFO com.iluwatar.filterer.App -- Filtered by threatType = ROOTKIT : SimpleThreatAwareSystem(systemId=Sys-1, issues=[SimpleThreat(threatType=ROOTKIT, id=1, name=Simple-Rootkit)]) +08:33:23.576 [main] INFO com.iluwatar.filterer.App -- ### Filtering ProbabilisticThreatAwareSystem by probability ### +08:33:23.581 [main] INFO com.iluwatar.filterer.App -- Filtering ProbabilisticThreatAwareSystem. Initial : SimpleProbabilisticThreatAwareSystem(systemId=Sys-1, threats=[SimpleProbableThreat{probability=0.99} SimpleThreat(threatType=TROJAN, id=1, name=Trojan-ArcBomb), SimpleProbableThreat{probability=0.8} SimpleThreat(threatType=ROOTKIT, id=2, name=Rootkit-Kernel)]) +08:33:23.581 [main] INFO com.iluwatar.filterer.App -- Filtered by probability = 0.99 : SimpleProbabilisticThreatAwareSystem(systemId=Sys-1, threats=[SimpleProbableThreat{probability=0.99} SimpleThreat(threatType=TROJAN, id=1, name=Trojan-ArcBomb)]) +``` + +## When to Use the Filterer Pattern in Java + +* Use the Filterer pattern when dynamic and flexible filtering of a collection of objects is needed. +* This Java design pattern is ideal for applications where filtering logic frequently changes or requires combination in various ways. +* Ideal for scenarios requiring separation of filtering logic from the core business logic. -## Class diagram +## Filterer Pattern Java Tutorials -![Filterer](./etc/filterer.png "Filterer") +* [Filterer Pattern (Tomasz Linkowski)](https://blog.tlinkowski.pl/2018/filterer-pattern/) +* [Filterer Pattern in 10 Steps (Java Code Geeks)](https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html) -## Applicability +## Real-World Applications of Filterer Pattern in Java -Pattern can be used when working with container-like objects that use subtyping, instead of -parametrizing (generics) for extensible class structure. It enables you to easily extend filtering -ability of container-like objects as business requirements change. +* Stream processing libraries in Java, such as Apache Kafka Streams, utilize this pattern to build complex data processing pipelines. +* Image processing software often uses filters to apply effects or transformations to images sequentially. -## Tutorials +## Benefits and Trade-offs of Filterer Pattern -* [Article about Filterer pattern posted on it's author's blog](https://blog.tlinkowski.pl/2018/filterer-pattern/) -* [Application of Filterer pattern in domain of text analysis](https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html) +Benefits: -## Known uses +* Increases flexibility by allowing different filters to be added or reorganized without affecting other parts of the system. +* Enhances testability, as filters can be tested independently. +* Promotes loose coupling between the stages of data processing. -One of the uses is present on the blog presented in -[this](https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html) link. It presents how -to use `Filterer` pattern to create text issue analyzer with support for test cases used for unit -testing. +Trade-offs: -## Consequences +* Potential performance overhead from continuous data passing between filters. +* Complexity can increase with the number of filters, potentially affecting maintainability. -Pros: - * You can easily introduce new subtypes for container-like objects and subtypes for objects that are contained within them and still be able to filter easily be new properties of those new subtypes. +## Related Java Design Patterns -Cons: - * Covariant return types mixed with generics can be sometimes tricky +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Filters can be seen as a specialized form of the Chain of Responsibility, where each filter decides if and how to process the input data and whether to pass it along the chain. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar to Decorator in that both modify behavior dynamically; however, filters focus more on data transformation than on adding responsibilities. -## Credits +## References and Credits -* Author of the pattern : [Tomasz Linkowski](https://tlinkowski.pl/) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3W8sn2W) +* [Kafka: The Definitive Guide: Real-Time Data and Stream Processing at Scale](https://amzn.to/49N3nRU) +* [Java Performance: The Definitive Guide](https://amzn.to/3vRW3qj) diff --git a/filterer/pom.xml b/filterer/pom.xml index 23f0093f9896..a0d438394cb5 100644 --- a/filterer/pom.xml +++ b/filterer/pom.xml @@ -34,9 +34,17 @@ 4.0.0 filterer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine test diff --git a/filterer/src/main/java/com/iluwatar/filterer/App.java b/filterer/src/main/java/com/iluwatar/filterer/App.java index 3fe22a4b896a..c64dce891fb6 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/App.java +++ b/filterer/src/main/java/com/iluwatar/filterer/App.java @@ -39,10 +39,10 @@ /** * This demo class represent how {@link com.iluwatar.filterer.domain.Filterer} pattern is used to * filter container-like objects to return filtered versions of themselves. The container like - * objects are systems that are aware of threats that they can be vulnerable to. We would like - * to have a way to create copy of different system objects but with filtered threats. - * The thing is to keep it simple if we add new subtype of {@link Threat} - * (for example {@link ProbableThreat}) - we still need to be able to filter by it's properties. + * objects are systems that are aware of threats that they can be vulnerable to. We would like to + * have a way to create copy of different system objects but with filtered threats. The thing is to + * keep it simple if we add new subtype of {@link Threat} (for example {@link ProbableThreat}) - we + * still need to be able to filter by its properties. */ @Slf4j public class App { @@ -55,8 +55,8 @@ public static void main(String[] args) { /** * Demonstrates how to filter {@link com.iluwatar.filterer.threat.ProbabilisticThreatAwareSystem} * based on probability property. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} - * method is able to use {@link com.iluwatar.filterer.threat.ProbableThreat} - * as predicate argument. + * method is able to use {@link com.iluwatar.filterer.threat.ProbableThreat} as predicate + * argument. */ private static void filteringSimpleProbableThreats() { LOGGER.info("### Filtering ProbabilisticThreatAwareSystem by probability ###"); @@ -69,20 +69,22 @@ private static void filteringSimpleProbableThreats() { var probabilisticThreatAwareSystem = new SimpleProbabilisticThreatAwareSystem("Sys-1", probableThreats); - LOGGER.info("Filtering ProbabilisticThreatAwareSystem. Initial : " - + probabilisticThreatAwareSystem); + LOGGER.info( + "Filtering ProbabilisticThreatAwareSystem. Initial : " + probabilisticThreatAwareSystem); - //Filtering using filterer - var filteredThreatAwareSystem = probabilisticThreatAwareSystem.filtered() - .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); + // Filtering using filterer + var filteredThreatAwareSystem = + probabilisticThreatAwareSystem + .filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); LOGGER.info("Filtered by probability = 0.99 : " + filteredThreatAwareSystem); } /** - * Demonstrates how to filter {@link ThreatAwareSystem} based on startingOffset property - * of {@link SimpleThreat}. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} - * method is able to use {@link Threat} as predicate argument. + * Demonstrates how to filter {@link ThreatAwareSystem} based on startingOffset property of {@link + * SimpleThreat}. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} method is able + * to use {@link Threat} as predicate argument. */ private static void filteringSimpleThreats() { LOGGER.info("### Filtering ThreatAwareSystem by ThreatType ###"); @@ -95,11 +97,10 @@ private static void filteringSimpleThreats() { LOGGER.info("Filtering ThreatAwareSystem. Initial : " + threatAwareSystem); - //Filtering using Filterer - var rootkitThreatAwareSystem = threatAwareSystem.filtered() - .by(threat -> threat.type() == ThreatType.ROOTKIT); + // Filtering using Filterer + var rootkitThreatAwareSystem = + threatAwareSystem.filtered().by(threat -> threat.type() == ThreatType.ROOTKIT); LOGGER.info("Filtered by threatType = ROOTKIT : " + rootkitThreatAwareSystem); } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java b/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java index 091db7071b43..58da1a7e563b 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java +++ b/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java @@ -28,10 +28,11 @@ /** * Filterer helper interface. + * * @param type of the container-like object. * @param type of the elements contained within this container-like object. */ @FunctionalInterface public interface Filterer { G by(Predicate predicate); -} \ No newline at end of file +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java index 79446d4fed63..a63728d74360 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java @@ -27,13 +27,12 @@ import com.iluwatar.filterer.domain.Filterer; import java.util.List; -/** - * Represents system that is aware of it's threats with given probability of their occurrence. - */ +/** Represents system that is aware of its threats with given probability of their occurrence. */ public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem { /** * {@inheritDoc} + * * @return {@link ProbableThreat} */ @Override @@ -41,9 +40,9 @@ public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem { /** * {@inheritDoc} + * * @return {@link Filterer} */ @Override Filterer filtered(); } - diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java index fc4efce51a68..8562136b50dd 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java @@ -24,13 +24,12 @@ */ package com.iluwatar.filterer.threat; -/** - * Represents threat that might be a threat with given probability. - */ +/** Represents threat that might be a threat with given probability. */ public interface ProbableThreat extends Threat { /** * Returns probability of occurrence of given threat. + * * @return probability of occurrence of given threat. */ double probability(); -} \ No newline at end of file +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java index def1918bb725..0584a031be7f 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java @@ -27,14 +27,11 @@ import com.iluwatar.filterer.domain.Filterer; import java.util.List; import java.util.function.Predicate; -import java.util.stream.Collectors; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; -/** - * {@inheritDoc} - */ +/** {@inheritDoc} */ @ToString @EqualsAndHashCode @RequiredArgsConstructor @@ -43,25 +40,19 @@ public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreat private final String systemId; private final List threats; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public String systemId() { return systemId; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public List threats() { return threats; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Filterer filtered() { return this::filteredGroup; @@ -72,11 +63,7 @@ private ProbabilisticThreatAwareSystem filteredGroup( return new SimpleProbabilisticThreatAwareSystem(this.systemId, filteredItems(predicate)); } - private List filteredItems( - final Predicate predicate) { - return this.threats.stream() - .filter(predicate) - .toList(); + private List filteredItems(final Predicate predicate) { + return this.threats.stream().filter(predicate).toList(); } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java index bd8997de31a5..044e02471755 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java @@ -26,23 +26,19 @@ import lombok.EqualsAndHashCode; -/** - * {@inheritDoc} - */ +/** {@inheritDoc} */ @EqualsAndHashCode(callSuper = false) public class SimpleProbableThreat extends SimpleThreat implements ProbableThreat { private final double probability; - public SimpleProbableThreat(final String name, final int id, final ThreatType threatType, - final double probability) { + public SimpleProbableThreat( + final String name, final int id, final ThreatType threatType, final double probability) { super(threatType, id, name); this.probability = probability; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public double probability() { return probability; @@ -50,9 +46,6 @@ public double probability() { @Override public String toString() { - return "SimpleProbableThreat{" - + "probability=" + probability - + "} " - + super.toString(); + return "SimpleProbableThreat{" + "probability=" + probability + "} " + super.toString(); } } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java index 6285bddc99be..034c6970be48 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java @@ -28,9 +28,7 @@ import lombok.RequiredArgsConstructor; import lombok.ToString; -/** - * Represents a simple threat. - */ +/** Represents a simple threat. */ @ToString @EqualsAndHashCode @RequiredArgsConstructor @@ -40,28 +38,21 @@ public class SimpleThreat implements Threat { private final int id; private final String name; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public String name() { return name; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public int id() { return id; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public ThreatType type() { return threatType; } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java index 7a27e23286f9..c547afe8f5df 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java @@ -28,14 +28,11 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; -import java.util.stream.Collectors; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; -/** - * {@inheritDoc} - */ +/** {@inheritDoc} */ @ToString @EqualsAndHashCode @RequiredArgsConstructor @@ -44,25 +41,19 @@ public class SimpleThreatAwareSystem implements ThreatAwareSystem { private final String systemId; private final List issues; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public String systemId() { return systemId; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public List threats() { return new ArrayList<>(issues); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Filterer filtered() { return this::filteredGroup; @@ -73,8 +64,6 @@ private ThreatAwareSystem filteredGroup(Predicate predicate) { } private List filteredItems(Predicate predicate) { - return this.issues.stream() - .filter(predicate).toList(); + return this.issues.stream().filter(predicate).toList(); } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java index f8aff1c84dc2..1a138956d9cd 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java @@ -24,9 +24,7 @@ */ package com.iluwatar.filterer.threat; -/** - * Represents a threat that can be detected in given system. - */ +/** Represents a threat that can be detected in given system. */ public interface Threat { /** * Returns name of the threat. @@ -44,6 +42,7 @@ public interface Threat { /** * Returns threat type. + * * @return {@link ThreatType} */ ThreatType type(); diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java index 6e9bf7af6df6..49ef8560fb68 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java @@ -27,10 +27,8 @@ import com.iluwatar.filterer.domain.Filterer; import java.util.List; -/** - * Represents system that is aware of threats that are present in it. - */ -public interface ThreatAwareSystem { +/** Represents system that is aware of threats that are present in it. */ +public interface ThreatAwareSystem { /** * Returns the system id. @@ -41,15 +39,16 @@ public interface ThreatAwareSystem { /** * Returns list of threats for this system. + * * @return list of threats for this system. */ - List threats(); + List threats(); /** - * Returns the instance of {@link Filterer} helper interface that allows to covariantly - * specify lower bound for predicate that we want to filter by. + * Returns the instance of {@link Filterer} helper interface that allows to covariantly specify + * lower bound for predicate that we want to filter by. + * * @return an instance of {@link Filterer} helper interface. */ - Filterer filtered(); - + Filterer, T> filtered(); } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java index 72293ce13ac9..30dcaefa18b1 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java @@ -24,9 +24,7 @@ */ package com.iluwatar.filterer.threat; -/** - * Enum class representing Threat types. - */ +/** Enum class representing Threat types. */ public enum ThreatType { TROJAN, WORM, diff --git a/filterer/src/test/java/com/iluwatar/filterer/AppTest.java b/filterer/src/test/java/com/iluwatar/filterer/AppTest.java index 7885feefd1b3..a35f03ea5cf0 100644 --- a/filterer/src/test/java/com/iluwatar/filterer/AppTest.java +++ b/filterer/src/test/java/com/iluwatar/filterer/AppTest.java @@ -32,6 +32,6 @@ class AppTest { @Test void shouldLaunchApp() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java index bf5a3ed3923b..3705009f170f 100644 --- a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java +++ b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java @@ -33,7 +33,7 @@ class SimpleProbabilisticThreatAwareSystemTest { @Test void shouldFilterByProbability() { - //given + // given var trojan = new SimpleProbableThreat("Troyan-ArcBomb", 1, ThreatType.TROJAN, 0.99); var rootkit = new SimpleProbableThreat("Rootkit-System", 2, ThreatType.ROOTKIT, 0.8); List probableThreats = List.of(trojan, rootkit); @@ -41,12 +41,14 @@ void shouldFilterByProbability() { var simpleProbabilisticThreatAwareSystem = new SimpleProbabilisticThreatAwareSystem("System-1", probableThreats); - //when - var filtered = simpleProbabilisticThreatAwareSystem.filtered() - .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); + // when + var filtered = + simpleProbabilisticThreatAwareSystem + .filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); - //then + // then assertEquals(filtered.threats().size(), 1); assertEquals(filtered.threats().get(0), trojan); } -} \ No newline at end of file +} diff --git a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java index 5865b558d625..d763edbf3e1c 100644 --- a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java +++ b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java @@ -24,27 +24,27 @@ */ package com.iluwatar.filterer.threat; -import org.junit.jupiter.api.Test; -import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import org.junit.jupiter.api.Test; class SimpleThreatAwareSystemTest { @Test void shouldFilterByThreatType() { - //given + // given var rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit"); var trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan"); List threats = List.of(rootkit, trojan); var threatAwareSystem = new SimpleThreatAwareSystem("System-1", threats); - //when - var rootkitThreatAwareSystem = threatAwareSystem.filtered() - .by(threat -> threat.type() == ThreatType.ROOTKIT); + // when + var rootkitThreatAwareSystem = + threatAwareSystem.filtered().by(threat -> threat.type() == ThreatType.ROOTKIT); - //then + // then assertEquals(rootkitThreatAwareSystem.threats().size(), 1); assertEquals(rootkitThreatAwareSystem.threats().get(0), rootkit); } -} \ No newline at end of file +} diff --git a/fluent-interface/README.md b/fluent-interface/README.md new file mode 100644 index 000000000000..8b2a0442061e --- /dev/null +++ b/fluent-interface/README.md @@ -0,0 +1,201 @@ +--- +title: "Fluent Interface Pattern in Java: Enhancing Code Expressiveness with Fluent APIs" +shortTitle: Fluent Interface +description: "Learn how to implement the Fluent Interface design pattern in Java. Explore method chaining and Fluent API with practical examples and improve your code readability and maintainability." +category: Behavioral +language: en +tag: + - API design + - Code simplification + - Decoupling + - Object composition + - Reactive +--- + +## Also known as + +* Fluent API +* Method Chaining + +## Intent of Fluent Interface Design Pattern + +The primary goal of the Fluent Interface pattern is to provide an easily readable and flowing API by chaining method calls, often referred to as method chaining. This approach is ideal for building complex objects step-by-step and improving the overall developer experience. + +## Detailed Explanation of Fluent Interface Pattern with Real-World Examples + +Real-world example + +> Imagine you are at a coffee shop customizing your order step-by-step. This approach is similar to how the Fluent Interface design pattern works in Java, allowing you to chain method calls to build and configure objects sequentially. Instead of telling the barista everything at once, you specify each customization step-by-step in a way that flows naturally. For instance, you might say, "I'd like a large coffee, add two shots of espresso, no sugar, and top it with almond milk." This approach is similar to the Fluent Interface design pattern, where you chain together method calls to configure an object in a readable and intuitive manner. Just as you specify each part of your coffee order sequentially, a Fluent Interface allows you to chain method calls to build and configure objects step-by-step in code. + +In plain words + +> Fluent Interface pattern provides easily readable flowing interface to code. + +Wikipedia says + +> In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). + +## Programmatic Example of Fluent Interface Pattern in Java + +We need to select numbers based on different criteria from the list. It's a great chance to utilize fluent interface pattern to provide readable easy-to-use developer experience. + +In this example two implementations of a `FluentIterable` interface are given. + +```java +public interface FluentIterable extends Iterable { + + FluentIterable filter(Predicate predicate); + + Optional first(); + + FluentIterable first(int count); + + Optional last(); + + FluentIterable last(int count); + + FluentIterable map(Function function); + + List asList(); + + static List copyToList(Iterable iterable) { + var copy = new ArrayList(); + iterable.forEach(copy::add); + return copy; + } +} +``` + +The `SimpleFluentIterable` evaluates eagerly and would be too costly for real world applications. + +```java +public class SimpleFluentIterable implements FluentIterable { + // ... +} +``` + +The `LazyFluentIterable` is evaluated on termination. + +```java +public class LazyFluentIterable implements FluentIterable { + // ... +} +``` + +Their usage is demonstrated with a simple number list that is filtered, transformed and collected. The result is printed afterward. + +```java +public static void main(String[] args) { + + var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68); + + prettyPrint("The initial list contains: ", integerList); + + var firstFiveNegatives = SimpleFluentIterable + .fromCopyOf(integerList) + .filter(negatives()) + .first(3) + .asList(); + prettyPrint("The first three negative values are: ", firstFiveNegatives); + + + var lastTwoPositives = SimpleFluentIterable + .fromCopyOf(integerList) + .filter(positives()) + .last(2) + .asList(); + prettyPrint("The last two positive values are: ", lastTwoPositives); + + SimpleFluentIterable + .fromCopyOf(integerList) + .filter(number -> number % 2 == 0) + .first() + .ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber)); + + + var transformedList = SimpleFluentIterable + .fromCopyOf(integerList) + .filter(negatives()) + .map(transformToString()) + .asList(); + prettyPrint("A string-mapped list of negative numbers contains: ", transformedList); + + + var lastTwoOfFirstFourStringMapped = LazyFluentIterable + .from(integerList) + .filter(positives()) + .first(4) + .last(2) + .map(number -> "String[" + number + "]") + .asList(); + prettyPrint("The lazy list contains the last two of the first four positive numbers " + + "mapped to Strings: ", lastTwoOfFirstFourStringMapped); + + LazyFluentIterable + .from(integerList) + .filter(negatives()) + .first(2) + .last() + .ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number)); +} +``` + +Program output: + +``` +08:50:08.260 [main] INFO com.iluwatar.fluentinterface.app.App -- The initial list contains: 1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68. +08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The first three negative values are: -61, -22, -87. +08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The last two positive values are: 23, 2. +08:50:08.266 [main] INFO com.iluwatar.fluentinterface.app.App -- The first even number is: 14 +08:50:08.267 [main] INFO com.iluwatar.fluentinterface.app.App -- A string-mapped list of negative numbers contains: String[-61], String[-22], String[-87], String[-82], String[-98], String[-68]. +08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- The lazy list contains the last two of the first four positive numbers mapped to Strings: String[18], String[6]. +08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- Last amongst first two negatives: -22 +``` + +## When to Use the Fluent Interface Pattern in Java + +Use the Fluent Interface Pattern in Java when + +* Designing APIs that are heavily used and where readability of client code is of high importance. +* Building complex objects step-by-step, and there is a need to make the code more intuitive and less error-prone. +* Enhancing code clarity and reducing the boilerplate code, especially in configurations and object-building scenarios. + +## Fluent Interface Pattern Java Tutorials + +* [An Approach to Internal Domain-Specific Languages in Java (InfoQ)](http://www.infoq.com/articles/internal-dsls-java) + +## Real-World Applications of Fluent Interface Pattern in Java + +* [Java 8 Stream API](http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html) +* [Google Guava FluentIterable](https://github.com/google/guava/wiki/FunctionalExplained) +* [JOOQ](http://www.jooq.org/doc/3.0/manual/getting-started/use-cases/jooq-as-a-standalone-sql-builder/) +* [Mockito](http://mockito.org/) +* [Java Hamcrest](http://code.google.com/p/hamcrest/wiki/Tutorial) +* Builders in libraries like Apache Camel for integration workflows. + +## Benefits and Trade-offs of Fluent Interface Pattern + +Benefits: + +* Adopting the Fluent Interface pattern in your Java projects can significantly enhance code readability and maintainability. +* Encourages building immutable objects since methods typically return new instances. +* Reduces the need for variables as the context is maintained in the chain. + +Trade-offs: + +* Can lead to less intuitive code for those unfamiliar with the pattern. +* Debugging can be challenging due to the chaining of method calls. +* Overuse can lead to complex and hard-to-maintain code structures. + +## Related Java Design Patterns + +* [Builder](https://java-design-patterns.com/patterns/builder/): Often implemented using a Fluent Interface to construct objects step-by-step. The Builder Pattern focuses on constructing complex objects, while Fluent Interface emphasizes the method chaining mechanism. +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Fluent Interfaces can be seen as a specific utilization of the Chain of Responsibility, where each method in the chain handles a part of the task and then delegates to the next method. + +## References and Credits + +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3UrXkh2) +* [Domain Specific Languages](https://amzn.to/3R1UYDA) +* [Effective Java](https://amzn.to/4d4azvL) +* [Java Design Pattern Essentials](https://amzn.to/44bs6hG) +* [Fluent Interface (Martin Fowler)](http://www.martinfowler.com/bliki/FluentInterface.html) diff --git a/fluent-interface/etc/fluent-interface.urm.puml b/fluent-interface/etc/fluent-interface.urm.puml new file mode 100644 index 000000000000..d343a478bff0 --- /dev/null +++ b/fluent-interface/etc/fluent-interface.urm.puml @@ -0,0 +1,72 @@ +@startuml +package com.iluwatar.fluentinterface.fluentiterable.simple { + class SimpleFluentIterable { + - iterable : Iterable + + SimpleFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + forEach(action : Consumer) + + from(iterable : Iterable) : FluentIterable {static} + + fromCopyOf(iterable : Iterable) : FluentIterable {static} + + getRemainingElementsCount() : int + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + + spliterator() : Spliterator + + toList(iterator : Iterator) : List {static} + } +} +package com.iluwatar.fluentinterface.app { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + - negatives() : Predicate {static} + - positives() : Predicate {static} + - prettyPrint(delimiter : String, prefix : String, iterable : Iterable) {static} + - prettyPrint(prefix : String, iterable : Iterable) {static} + - transformToString() : Function {static} + } +} +package com.iluwatar.fluentinterface.fluentiterable.lazy { + abstract class DecoratingIterator { + # fromIterator : Iterator + - next : E + + DecoratingIterator(fromIterator : Iterator) + + computeNext() : E {abstract} + + hasNext() : boolean + + next() : E + } + class LazyFluentIterable { + - iterable : Iterable + # LazyFluentIterable() + + LazyFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + from(iterable : Iterable) : FluentIterable {static} + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + } +} +package com.iluwatar.fluentinterface.fluentiterable { + interface FluentIterable { + + asList() : List {abstract} + + copyToList(iterable : Iterable) : List {static} + + filter(Predicate) : FluentIterable {abstract} + + first() : Optional {abstract} + + first(int) : FluentIterable {abstract} + + last() : Optional {abstract} + + last(int) : FluentIterable {abstract} + + map(Function) : FluentIterable {abstract} + } +} +LazyFluentIterable ..|> FluentIterable +SimpleFluentIterable ..|> FluentIterable +@enduml \ No newline at end of file diff --git a/fluentinterface/etc/fluentinterface.png b/fluent-interface/etc/fluentinterface.png similarity index 100% rename from fluentinterface/etc/fluentinterface.png rename to fluent-interface/etc/fluentinterface.png diff --git a/fluentinterface/etc/fluentinterface.ucls b/fluent-interface/etc/fluentinterface.ucls similarity index 100% rename from fluentinterface/etc/fluentinterface.ucls rename to fluent-interface/etc/fluentinterface.ucls diff --git a/fluentinterface/etc/fluentinterface.urm.puml b/fluent-interface/etc/fluentinterface.urm.puml similarity index 100% rename from fluentinterface/etc/fluentinterface.urm.puml rename to fluent-interface/etc/fluentinterface.urm.puml diff --git a/fluentinterface/pom.xml b/fluent-interface/pom.xml similarity index 90% rename from fluentinterface/pom.xml rename to fluent-interface/pom.xml index c6b4d7b5a9a7..005fa9f7b831 100644 --- a/fluentinterface/pom.xml +++ b/fluent-interface/pom.xml @@ -32,8 +32,16 @@ 1.26.0-SNAPSHOT 4.0.0 - fluentinterface + fluent-interface + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java similarity index 76% rename from fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java rename to fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java index 129674375de0..a36bef68b645 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java @@ -42,62 +42,51 @@ * {@link SimpleFluentIterable} evaluates eagerly and would be too costly for real world * applications. The {@link LazyFluentIterable} is evaluated on termination. Their usage is * demonstrated with a simple number list that is filtered, transformed and collected. The result is - * printed afterwards. + * printed afterward. */ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68); prettyPrint("The initial list contains: ", integerList); - var firstFiveNegatives = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(negatives()) - .first(3) - .asList(); + var firstFiveNegatives = + SimpleFluentIterable.fromCopyOf(integerList).filter(negatives()).first(3).asList(); prettyPrint("The first three negative values are: ", firstFiveNegatives); - - var lastTwoPositives = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(positives()) - .last(2) - .asList(); + var lastTwoPositives = + SimpleFluentIterable.fromCopyOf(integerList).filter(positives()).last(2).asList(); prettyPrint("The last two positive values are: ", lastTwoPositives); - SimpleFluentIterable - .fromCopyOf(integerList) + SimpleFluentIterable.fromCopyOf(integerList) .filter(number -> number % 2 == 0) .first() .ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber)); - - var transformedList = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(negatives()) - .map(transformToString()) - .asList(); + var transformedList = + SimpleFluentIterable.fromCopyOf(integerList) + .filter(negatives()) + .map(transformToString()) + .asList(); prettyPrint("A string-mapped list of negative numbers contains: ", transformedList); - - var lastTwoOfFirstFourStringMapped = LazyFluentIterable - .from(integerList) - .filter(positives()) - .first(4) - .last(2) - .map(number -> "String[" + number + "]") - .asList(); - prettyPrint("The lazy list contains the last two of the first four positive numbers " - + "mapped to Strings: ", lastTwoOfFirstFourStringMapped); - - LazyFluentIterable - .from(integerList) + var lastTwoOfFirstFourStringMapped = + LazyFluentIterable.from(integerList) + .filter(positives()) + .first(4) + .last(2) + .map(number -> "String[" + number + "]") + .asList(); + prettyPrint( + "The lazy list contains the last two of the first four positive numbers " + + "mapped to Strings: ", + lastTwoOfFirstFourStringMapped); + + LazyFluentIterable.from(integerList) .filter(negatives()) .first(2) .last() @@ -120,10 +109,7 @@ private static void prettyPrint(String prefix, Iterable iterable) { prettyPrint(", ", prefix, iterable); } - private static void prettyPrint( - String delimiter, String prefix, - Iterable iterable - ) { + private static void prettyPrint(String delimiter, String prefix, Iterable iterable) { var joiner = new StringJoiner(delimiter, prefix, "."); iterable.forEach(e -> joiner.add(e.toString())); LOGGER.info(joiner.toString()); diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java similarity index 95% rename from fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java rename to fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java index d97289e789ef..2ab128afa633 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java @@ -44,7 +44,7 @@ public interface FluentIterable extends Iterable { * the predicate. * * @param predicate the condition to test with for the filtering. If the test is negative, the - * tested object is removed by the iterator. + * tested object is removed by the iterator. * @return a filtered FluentIterable */ FluentIterable filter(Predicate predicate); @@ -82,7 +82,7 @@ public interface FluentIterable extends Iterable { * Transforms this FluentIterable into a new one containing objects of the type T. * * @param function a function that transforms an instance of E into an instance of T - * @param the target type of the transformation + * @param the target type of the transformation * @return a new FluentIterable of the new type */ FluentIterable map(Function function); @@ -98,7 +98,7 @@ public interface FluentIterable extends Iterable { * Utility method that iterates over iterable and adds the contents to a list. * * @param iterable the iterable to collect - * @param the type of the objects to iterate + * @param the type of the objects to iterate * @return a list with all objects of the given iterator */ static List copyToList(Iterable iterable) { diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java similarity index 97% rename from fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java rename to fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java index aaf00b935e3c..12a0304a8db2 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java @@ -38,9 +38,7 @@ public abstract class DecoratingIterator implements Iterator { private E next; - /** - * Creates an iterator that decorates the given iterator. - */ + /** Creates an iterator that decorates the given iterator. */ public DecoratingIterator(Iterator fromIterator) { this.fromIterator = fromIterator; } diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java similarity index 97% rename from fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java rename to fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java index f624179e05a9..971ac9a02207 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java @@ -44,9 +44,7 @@ public class LazyFluentIterable implements FluentIterable { private final Iterable iterable; - /** - * This constructor can be used to implement anonymous subclasses of the LazyFluentIterable. - */ + /** This constructor can be used to implement anonymous subclasses of the LazyFluentIterable. */ protected LazyFluentIterable() { iterable = this; } @@ -56,7 +54,7 @@ protected LazyFluentIterable() { * the predicate. * * @param predicate the condition to test with for the filtering. If the test is negative, the - * tested object is removed by the iterator. + * tested object is removed by the iterator. * @return a new FluentIterable object that decorates the source iterable */ @Override @@ -183,7 +181,7 @@ private void initialize() { * Transforms this FluentIterable into a new one containing objects of the type T. * * @param function a function that transforms an instance of E into an instance of T - * @param the target type of the transformation + * @param the target type of the transformation * @return a new FluentIterable of the new type */ @Override @@ -236,5 +234,4 @@ public E computeNext() { public static FluentIterable from(Iterable iterable) { return new LazyFluentIterable<>(iterable); } - } diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java similarity index 98% rename from fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java rename to fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java index b3c912f1f7fd..a44cfb44cbfe 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java @@ -51,7 +51,7 @@ public class SimpleFluentIterable implements FluentIterable { * the predicate. * * @param predicate the condition to test with for the filtering. If the test is negative, the - * tested object is removed by the iterator. + * tested object is removed by the iterator. * @return the same FluentIterable with a filtered collection */ @Override @@ -139,7 +139,7 @@ public final FluentIterable last(int count) { * Transforms this FluentIterable into a new one containing objects of the type T. * * @param function a function that transforms an instance of E into an instance of T - * @param the target type of the transformation + * @param the target type of the transformation * @return a new FluentIterable of the new type */ @Override @@ -183,7 +183,6 @@ public void forEach(Consumer action) { iterable.forEach(action); } - @Override public Spliterator spliterator() { return iterable.spliterator(); diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java similarity index 94% rename from fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java rename to fluent-interface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java index d637923dc6b9..003e4a12c950 100644 --- a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.fluentinterface.app; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Test Entry - */ +import org.junit.jupiter.api.Test; + +/** Application Test Entry */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java similarity index 89% rename from fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java rename to fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java index d885a4e4a87b..d85b64109dd0 100644 --- a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java @@ -38,11 +38,7 @@ import java.util.function.Consumer; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 7:00 PM - * - * @author Jeroen Meulemeester - */ +/** FluentIterableTest */ public abstract class FluentIterableTest { /** @@ -73,9 +69,7 @@ void testFirstEmptyCollection() { @Test void testFirstCount() { final var integers = List.of(1, 2, 3, 10, 9, 8); - final var first4 = createFluentIterable(integers) - .first(4) - .asList(); + final var first4 = createFluentIterable(integers).first(4).asList(); assertNotNull(first4); assertEquals(4, first4.size()); @@ -89,9 +83,7 @@ void testFirstCount() { @Test void testFirstCountLessItems() { final var integers = List.of(1, 2, 3); - final var first4 = createFluentIterable(integers) - .first(4) - .asList(); + final var first4 = createFluentIterable(integers).first(4).asList(); assertNotNull(first4); assertEquals(3, first4.size()); @@ -121,9 +113,7 @@ void testLastEmptyCollection() { @Test void testLastCount() { final var integers = List.of(1, 2, 3, 10, 9, 8); - final var last4 = createFluentIterable(integers) - .last(4) - .asList(); + final var last4 = createFluentIterable(integers).last(4).asList(); assertNotNull(last4); assertEquals(4, last4.size()); @@ -136,9 +126,7 @@ void testLastCount() { @Test void testLastCountLessItems() { final var integers = List.of(1, 2, 3); - final var last4 = createFluentIterable(integers) - .last(4) - .asList(); + final var last4 = createFluentIterable(integers).last(4).asList(); assertNotNull(last4); assertEquals(3, last4.size()); @@ -151,9 +139,7 @@ void testLastCountLessItems() { @Test void testFilter() { final var integers = List.of(1, 2, 3, 10, 9, 8); - final var evenItems = createFluentIterable(integers) - .filter(i -> i % 2 == 0) - .asList(); + final var evenItems = createFluentIterable(integers).filter(i -> i % 2 == 0).asList(); assertNotNull(evenItems); assertEquals(3, evenItems.size()); @@ -165,9 +151,7 @@ void testFilter() { @Test void testMap() { final var integers = List.of(1, 2, 3); - final var longs = createFluentIterable(integers) - .map(Integer::longValue) - .asList(); + final var longs = createFluentIterable(integers).map(Integer::longValue).asList(); assertNotNull(longs); assertEquals(integers.size(), longs.size()); @@ -187,14 +171,12 @@ void testForEach() { verify(consumer, times(1)).accept(2); verify(consumer, times(1)).accept(3); verifyNoMoreInteractions(consumer); - } @Test - void testSpliterator() throws Exception { + void testSpliterator() { final var integers = List.of(1, 2, 3); final var split = createFluentIterable(integers).spliterator(); assertNotNull(split); } - -} \ No newline at end of file +} diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java similarity index 95% rename from fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java rename to fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java index 453eac1ea1d0..2db7ef534d03 100644 --- a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java @@ -27,16 +27,11 @@ import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; -/** - * Date: 12/12/15 - 7:56 PM - * - * @author Jeroen Meulemeester - */ +/** LazyFluentIterableTest */ class LazyFluentIterableTest extends FluentIterableTest { @Override protected FluentIterable createFluentIterable(Iterable integers) { return LazyFluentIterable.from(integers); } - } diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java similarity index 96% rename from fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java rename to fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java index 5c1d7ed53a6f..5b1cc374d619 100644 --- a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java @@ -27,16 +27,11 @@ import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; -/** - * Date: 12/12/15 - 7:56 PM - * - * @author Jeroen Meulemeester - */ +/** SimpleFluentIterableTest */ class SimpleFluentIterableTest extends FluentIterableTest { @Override protected FluentIterable createFluentIterable(Iterable integers) { return SimpleFluentIterable.fromCopyOf(integers); } - } diff --git a/fluentinterface/README.md b/fluentinterface/README.md deleted file mode 100644 index f582ddf5542f..000000000000 --- a/fluentinterface/README.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: Fluent Interface -category: Functional -language: en -tag: - - Reactive ---- - -## Intent - -A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific -language. Using this pattern results in code that can be read nearly as human language. - -## Explanation - -The Fluent Interface pattern is useful when you want to provide an easy readable, flowing API. Those -interfaces tend to mimic domain specific languages, so they can nearly be read as human languages. - -A fluent interface can be implemented using any of - - * Method chaining - calling a method returns some object on which further methods can be called. - * Static factory methods and imports. - * Named parameters - can be simulated in Java using static factory methods. - -Real world example - -> We need to select numbers based on different criteria from the list. It's a great chance to -> utilize fluent interface pattern to provide readable easy-to-use developer experience. - -In plain words - -> Fluent Interface pattern provides easily readable flowing interface to code. - -Wikipedia says - -> In software engineering, a fluent interface is an object-oriented API whose design relies -> extensively on method chaining. Its goal is to increase code legibility by creating a -> domain-specific language (DSL). - -**Programmatic Example** - -In this example two implementations of a `FluentIterable` interface are given. - -```java -public interface FluentIterable extends Iterable { - - FluentIterable filter(Predicate predicate); - - Optional first(); - - FluentIterable first(int count); - - Optional last(); - - FluentIterable last(int count); - - FluentIterable map(Function function); - - List asList(); - - static List copyToList(Iterable iterable) { - var copy = new ArrayList(); - iterable.forEach(copy::add); - return copy; - } -} -``` - -The `SimpleFluentIterable` evaluates eagerly and would be too costly for real world applications. - -```java -public class SimpleFluentIterable implements FluentIterable { - ... -} -``` - -The `LazyFluentIterable` is evaluated on termination. - -```java -public class LazyFluentIterable implements FluentIterable { - ... -} -``` - -Their usage is demonstrated with a simple number list that is filtered, transformed and collected. The -result is printed afterwards. - -```java - var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68); - - prettyPrint("The initial list contains: ", integerList); - - var firstFiveNegatives = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(negatives()) - .first(3) - .asList(); - prettyPrint("The first three negative values are: ", firstFiveNegatives); - - - var lastTwoPositives = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(positives()) - .last(2) - .asList(); - prettyPrint("The last two positive values are: ", lastTwoPositives); - - SimpleFluentIterable - .fromCopyOf(integerList) - .filter(number -> number % 2 == 0) - .first() - .ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber)); - - - var transformedList = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(negatives()) - .map(transformToString()) - .asList(); - prettyPrint("A string-mapped list of negative numbers contains: ", transformedList); - - - var lastTwoOfFirstFourStringMapped = LazyFluentIterable - .from(integerList) - .filter(positives()) - .first(4) - .last(2) - .map(number -> "String[" + valueOf(number) + "]") - .asList(); - prettyPrint("The lazy list contains the last two of the first four positive numbers " - + "mapped to Strings: ", lastTwoOfFirstFourStringMapped); - - LazyFluentIterable - .from(integerList) - .filter(negatives()) - .first(2) - .last() - .ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number)); -``` - -Program output: - -```java -The initial list contains: 1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68. -The first three negative values are: -61, -22, -87. -The last two positive values are: 23, 2. -The first even number is: 14 -A string-mapped list of negative numbers contains: String[-61], String[-22], String[-87], String[-82], String[-98], String[-68]. -The lazy list contains the last two of the first four positive numbers mapped to Strings: String[18], String[6]. -Last amongst first two negatives: -22 -``` - -## Class diagram - -![Fluent Interface](./etc/fluentinterface.png "Fluent Interface") - -## Applicability - -Use the Fluent Interface pattern when - -* You provide an API that would benefit from a DSL-like usage. -* You have objects that are difficult to configure or use. - -## Known uses - -* [Java 8 Stream API](http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html) -* [Google Guava FluentIterable](https://github.com/google/guava/wiki/FunctionalExplained) -* [JOOQ](http://www.jooq.org/doc/3.0/manual/getting-started/use-cases/jooq-as-a-standalone-sql-builder/) -* [Mockito](http://mockito.org/) -* [Java Hamcrest](http://code.google.com/p/hamcrest/wiki/Tutorial) - -## Credits - -* [Fluent Interface - Martin Fowler](http://www.martinfowler.com/bliki/FluentInterface.html) -* [Evolutionary architecture and emergent design: Fluent interfaces - Neal Ford](http://www.ibm.com/developerworks/library/j-eaed14/) -* [Internal DSL](http://www.infoq.com/articles/internal-dsls-java) -* [Domain Specific Languages](https://www.amazon.com/gp/product/0321712943/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=0321712943&linkId=ad8351d6f5be7d8b7ecdb650731f85df) diff --git a/flux/README.md b/flux/README.md index 5e3aeb17e125..e7702c839784 100644 --- a/flux/README.md +++ b/flux/README.md @@ -1,25 +1,121 @@ --- -title: Flux -category: Structural +title: "Flux Pattern in Java: Streamlining Complex UIs with Unidirectional Data Flow" +shortTitle: Flux +description: "Learn how the Flux design pattern simplifies data flow in Java applications through unidirectional architecture. Explore examples, benefits, and real-world applications." +category: Architectural language: en tag: - - Decoupling + - Client-server + - Decoupling + - Event-driven + - Publish/subscribe + - Reactive --- -## Intent -Flux eschews MVC in favor of a unidirectional data flow. When a -user interacts with a view, the view propagates an action through a central -dispatcher, to the various stores that hold the application's data and business -logic, which updates all of the views that are affected. +## Intent of Flux Design Pattern -## Class diagram -![alt text](./etc/flux.png "Flux") +The Flux design pattern is intended to manage the flow of data in Java applications, particularly client-side web applications, by enforcing a unidirectional data flow. It aims to simplify the management of complex data interactions and promote a more predictable state behavior across components. -## Applicability -Use the Flux pattern when +## Detailed Explanation of Flux Pattern with Real-World Examples -* you want to focus on creating explicit and understandable update paths for your application's data, which makes tracing changes during development simpler and makes bugs easier to track down and fix. +Real-world example -## Credits +> Consider a busy restaurant kitchen as an analogy for the Flux design pattern. In this scenario, the kitchen operates based on customer orders (actions) received and processed through a single point of control (the dispatcher), which could be represented by the head chef. When an order arrives, the head chef assigns specific tasks to various sections of the kitchen (stores), such as the grill, the salad station, or the dessert team. Each section updates the progress of their tasks (state changes) back to the head chef, who ensures that all parts of the order are coordinated and completed in a synchronized manner before the dishes are sent out to the customer (the view). -* [Flux - Application architecture for building user interfaces](http://facebook.github.io/flux/) +In plain words + +> The Flux design pattern manages data flow in applications through a unidirectional architecture, coordinating actions, dispatchers, stores, and views to ensure stable and predictable state management. This pattern is particularly useful in Java design patterns for developing responsive client-side web applications. + +Wikipedia says + +> To support React's concept of unidirectional data flow (which might be contrasted with AngularJS's bidirectional flow), the Flux architecture was developed as an alternative to the popular model–view–controller architecture. Flux features actions which are sent through a central dispatcher to a store, and changes to the store are propagated back to the view. + +Architecture diagram + +![Flux Architecture Diagram](./etc/flux-architecture-diagram.png) + +## Programmatic Example of Flux Pattern in Java + +The Flux design pattern is used for building client-side web applications. It advocates for a unidirectional data flow. When a user interacts with a view, the view propagates an action through a central dispatcher, to the various stores that hold the application's data and business logic, which updates all the views that are affected. + +In the provided code, we can see an example of the Flux pattern in the `App` and `MenuStore` classes. + +The `App` class is the entry point of the application. It initializes and wires the system, registers the stores with the dispatcher, registers the views with the stores, and triggers the initial rendering of the views. When a menu item is clicked, it triggers events through the dispatcher. + +```java +public class App { + + public static void main(String[] args) { + + var menuStore = new MenuStore(); + Dispatcher.getInstance().registerStore(menuStore); + var contentStore = new ContentStore(); + Dispatcher.getInstance().registerStore(contentStore); + var menuView = new MenuView(); + menuStore.registerView(menuView); + var contentView = new ContentView(); + contentStore.registerView(contentView); + + menuView.render(); + contentView.render(); + + menuView.itemClicked(MenuItem.COMPANY); + } +} +``` + +The `MenuStore` class is a concrete store that holds the state of the menu. It updates its state and notifies the views when it receives an action from the dispatcher. + +```java +public class MenuStore extends Store { + + @Getter + private MenuItem selected = MenuItem.HOME; + + @Override + public void onAction(Action action) { + if (action.getType().equals(ActionType.MENU_ITEM_SELECTED)) { + var menuAction = (MenuAction) action; + selected = menuAction.getMenuItem(); + notifyChange(); + } + } +} +``` + +In this example, when a menu item is clicked, the `MenuView` triggers a `MENU_ITEM_SELECTED` action. The `Dispatcher` forwards this action to all registered stores. The `MenuStore` handles this action by updating its state and notifying its views, causing them to rerender with the new state. + +This is a basic example of the Flux pattern, where actions are dispatched from the views, handled by the stores, and cause the views to update. + +## When to Use the Flux Pattern in Java + +Flux is applicable in developing client-side Java applications, where maintaining consistent data across various components and managing complex state interactions are critical. It is especially suited for applications with dynamic user interfaces that react to frequent data updates. + +## Real-World Applications of Flux Pattern in Java + +* Facebook extensively uses the Flux design pattern in conjunction with React to build robust, scalable user interfaces that can handle complex data updates efficiently. +* Many modern web applications adopt Flux or its variations (like Redux) to manage state in environments that demand high responsiveness and predictability. + +## Benefits and Trade-offs of Flux Pattern + +Benefits: + +* Ensures a unidirectional data flow that simplifies debugging and testing. +* Enhances consistency across the application by centralizing the application state. +* Improves the predictability of data flow and interaction in large applications. + +Trade-offs: + +* Can introduce boilerplate and complexity in smaller applications. +* May require a learning curve to understand the pattern's architecture and its implementation nuances. + +## Related Java Design Patterns + +* [Observer](https://java-design-patterns.com/patterns/observer/): Flux's dispatcher component acts similarly to an observer, managing notifications about data changes to various stores. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Typically, the dispatcher in Flux is implemented as a singleton. +* [Mediator](https://java-design-patterns.com/patterns/mediator/): Flux can be considered a variation of the mediator pattern where the dispatcher mediates the flow of data and ensures components do not update the state directly. + +## References and Credits + +* [Learning React: Modern Patterns for Developing React Apps](https://amzn.to/3Qdn9Pg) +* [Pro React](https://amzn.to/3xNRttK) diff --git a/flux/etc/flux-architecture-diagram.png b/flux/etc/flux-architecture-diagram.png new file mode 100644 index 000000000000..a2493da64608 Binary files /dev/null and b/flux/etc/flux-architecture-diagram.png differ diff --git a/flux/pom.xml b/flux/pom.xml index 2b8588884aa5..f0259126dc2b 100644 --- a/flux/pom.xml +++ b/flux/pom.xml @@ -34,6 +34,14 @@ flux + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/flux/src/main/java/com/iluwatar/flux/action/Action.java b/flux/src/main/java/com/iluwatar/flux/action/Action.java index e493d04dfa82..cf59d5ad9446 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/Action.java +++ b/flux/src/main/java/com/iluwatar/flux/action/Action.java @@ -27,13 +27,10 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Action is the data payload dispatched to the stores when something happens. - */ +/** Action is the data payload dispatched to the stores when something happens. */ @RequiredArgsConstructor @Getter public abstract class Action { private final ActionType type; - } diff --git a/flux/src/main/java/com/iluwatar/flux/action/ActionType.java b/flux/src/main/java/com/iluwatar/flux/action/ActionType.java index a8206972e14f..9665959d030e 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/ActionType.java +++ b/flux/src/main/java/com/iluwatar/flux/action/ActionType.java @@ -24,12 +24,8 @@ */ package com.iluwatar.flux.action; -/** - * Types of actions. - */ +/** Types of actions. */ public enum ActionType { - MENU_ITEM_SELECTED, CONTENT_CHANGED - } diff --git a/flux/src/main/java/com/iluwatar/flux/action/Content.java b/flux/src/main/java/com/iluwatar/flux/action/Content.java index d17aed00a9cb..124e8f9eda99 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/Content.java +++ b/flux/src/main/java/com/iluwatar/flux/action/Content.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Content items. - */ +/** Content items. */ @RequiredArgsConstructor public enum Content { - PRODUCTS("Products - This page lists the company's products."), COMPANY("Company - This page displays information about the company."); diff --git a/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java b/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java index a824bcbbdb72..fb3020dfa506 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java +++ b/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java @@ -24,19 +24,15 @@ */ package com.iluwatar.flux.action; -/** - * ContentAction is a concrete action. - */ +import lombok.Getter; + +/** ContentAction is a concrete action. */ public class ContentAction extends Action { - private final Content content; + @Getter private final Content content; public ContentAction(Content content) { super(ActionType.CONTENT_CHANGED); this.content = content; } - - public Content getContent() { - return content; - } } diff --git a/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java b/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java index 6b641c8c2e4f..81e52213dd7e 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java +++ b/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java @@ -24,20 +24,15 @@ */ package com.iluwatar.flux.action; +import lombok.Getter; -/** - * MenuAction is a concrete action. - */ +/** MenuAction is a concrete action. */ public class MenuAction extends Action { - private final MenuItem menuItem; + @Getter private final MenuItem menuItem; public MenuAction(MenuItem menuItem) { super(ActionType.MENU_ITEM_SELECTED); this.menuItem = menuItem; } - - public MenuItem getMenuItem() { - return menuItem; - } } diff --git a/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java b/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java index 70aabe5667a7..cc8087077e6c 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java +++ b/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java @@ -24,12 +24,11 @@ */ package com.iluwatar.flux.action; -/** - * Menu items. - */ +/** Menu items. */ public enum MenuItem { - - HOME("Home"), PRODUCTS("Products"), COMPANY("Company"); + HOME("Home"), + PRODUCTS("Products"), + COMPANY("Company"); private final String title; diff --git a/flux/src/main/java/com/iluwatar/flux/app/App.java b/flux/src/main/java/com/iluwatar/flux/app/App.java index 65c3aa8b499a..c47c6c888c2d 100644 --- a/flux/src/main/java/com/iluwatar/flux/app/App.java +++ b/flux/src/main/java/com/iluwatar/flux/app/App.java @@ -35,7 +35,7 @@ * Flux is the application architecture that Facebook uses for building client-side web * applications. Flux eschews MVC in favor of a unidirectional data flow. When a user interacts with * a React view, the view propagates an action through a central dispatcher, to the various stores - * that hold the application's data and business logic, which updates all of the views that are + * that hold the application's data and business logic, which updates all the views that are * affected. * *

This example has two views: menu and content. They represent typical main menu and content diff --git a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java index 4dd2a411d9d7..ca087dd420a2 100644 --- a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java +++ b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java @@ -32,30 +32,22 @@ import com.iluwatar.flux.store.Store; import java.util.LinkedList; import java.util.List; +import lombok.Getter; -/** - * Dispatcher sends Actions to registered Stores. - */ +/** Dispatcher sends Actions to registered Stores. */ public final class Dispatcher { - private static Dispatcher instance = new Dispatcher(); + @Getter private static Dispatcher instance = new Dispatcher(); private final List stores = new LinkedList<>(); - private Dispatcher() { - } - - public static Dispatcher getInstance() { - return instance; - } + private Dispatcher() {} public void registerStore(Store store) { stores.add(store); } - /** - * Menu item selected handler. - */ + /** Menu item selected handler. */ public void menuItemSelected(MenuItem menuItem) { dispatchAction(new MenuAction(menuItem)); if (menuItem == MenuItem.COMPANY) { diff --git a/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java b/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java index ad897cbf1ce9..b26146d52d95 100644 --- a/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java +++ b/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java @@ -28,13 +28,12 @@ import com.iluwatar.flux.action.ActionType; import com.iluwatar.flux.action.Content; import com.iluwatar.flux.action.ContentAction; +import lombok.Getter; -/** - * ContentStore is a concrete store. - */ +/** ContentStore is a concrete store. */ public class ContentStore extends Store { - private Content content = Content.PRODUCTS; + @Getter private Content content = Content.PRODUCTS; @Override public void onAction(Action action) { @@ -44,8 +43,4 @@ public void onAction(Action action) { notifyChange(); } } - - public Content getContent() { - return content; - } } diff --git a/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java b/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java index f1cf56ba5cd7..c0d8d8255a16 100644 --- a/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java +++ b/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java @@ -28,13 +28,12 @@ import com.iluwatar.flux.action.ActionType; import com.iluwatar.flux.action.MenuAction; import com.iluwatar.flux.action.MenuItem; +import lombok.Getter; -/** - * MenuStore is a concrete store. - */ +/** MenuStore is a concrete store. */ public class MenuStore extends Store { - private MenuItem selected = MenuItem.HOME; + @Getter private MenuItem selected = MenuItem.HOME; @Override public void onAction(Action action) { @@ -44,8 +43,4 @@ public void onAction(Action action) { notifyChange(); } } - - public MenuItem getSelected() { - return selected; - } } diff --git a/flux/src/main/java/com/iluwatar/flux/store/Store.java b/flux/src/main/java/com/iluwatar/flux/store/Store.java index 313635bd69ca..879ae65d26c0 100644 --- a/flux/src/main/java/com/iluwatar/flux/store/Store.java +++ b/flux/src/main/java/com/iluwatar/flux/store/Store.java @@ -29,9 +29,7 @@ import java.util.LinkedList; import java.util.List; -/** - * Store is a data model. - */ +/** Store is a data model. */ public abstract class Store { private final List views = new LinkedList<>(); diff --git a/flux/src/main/java/com/iluwatar/flux/view/ContentView.java b/flux/src/main/java/com/iluwatar/flux/view/ContentView.java index 7f01daadb80e..66befdf78062 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/ContentView.java +++ b/flux/src/main/java/com/iluwatar/flux/view/ContentView.java @@ -29,9 +29,7 @@ import com.iluwatar.flux.store.Store; import lombok.extern.slf4j.Slf4j; -/** - * ContentView is a concrete view. - */ +/** ContentView is a concrete view. */ @Slf4j public class ContentView implements View { diff --git a/flux/src/main/java/com/iluwatar/flux/view/MenuView.java b/flux/src/main/java/com/iluwatar/flux/view/MenuView.java index 378c9ed50c1f..74ae5192b4f7 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/MenuView.java +++ b/flux/src/main/java/com/iluwatar/flux/view/MenuView.java @@ -30,9 +30,7 @@ import com.iluwatar.flux.store.Store; import lombok.extern.slf4j.Slf4j; -/** - * MenuView is a concrete view. - */ +/** MenuView is a concrete view. */ @Slf4j public class MenuView implements View { diff --git a/flux/src/main/java/com/iluwatar/flux/view/View.java b/flux/src/main/java/com/iluwatar/flux/view/View.java index 6e6a7cd596be..b31b20c18ffd 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/View.java +++ b/flux/src/main/java/com/iluwatar/flux/view/View.java @@ -26,9 +26,7 @@ import com.iluwatar.flux.store.Store; -/** - * Views define the representation of data. - */ +/** Views define the representation of data. */ public interface View { void storeChanged(Store store); diff --git a/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java index 6fec8ce38bcb..8263468c9472 100644 --- a/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java +++ b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java @@ -29,11 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:11 PM - * - * @author Jeroen Meulemeester - */ +/** ContentTest */ class ContentTest { @Test @@ -44,5 +40,4 @@ void testToString() { assertFalse(toString.trim().isEmpty()); } } - } diff --git a/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java index 3b995a804133..bf012abee931 100644 --- a/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java +++ b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java @@ -29,11 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:15 PM - * - * @author Jeroen Meulemeester - */ +/** MenuItemTest */ class MenuItemTest { @Test @@ -44,5 +40,4 @@ void testToString() { assertFalse(toString.trim().isEmpty()); } } - } diff --git a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java index 0fc0049fc9c2..c594a1efb840 100644 --- a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java +++ b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.flux.app; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java index 8e933a4f76b4..b45de5992aef 100644 --- a/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java +++ b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java @@ -43,11 +43,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -/** - * Date: 12/12/15 - 8:22 PM - * - * @author Jeroen Meulemeester - */ +/** DispatcherTest */ class DispatcherTest { /** @@ -86,28 +82,37 @@ void testMenuItemSelected() { verifyNoMoreInteractions(store); final var actions = actionCaptor.getAllValues(); - final var menuActions = actions.stream() - .filter(a -> a.getType().equals(ActionType.MENU_ITEM_SELECTED)) - .map(a -> (MenuAction) a) - .toList(); + final var menuActions = + actions.stream() + .filter(a -> a.getType().equals(ActionType.MENU_ITEM_SELECTED)) + .map(a -> (MenuAction) a) + .toList(); - final var contentActions = actions.stream() - .filter(a -> a.getType().equals(ActionType.CONTENT_CHANGED)) - .map(a -> (ContentAction) a) - .toList(); + final var contentActions = + actions.stream() + .filter(a -> a.getType().equals(ActionType.CONTENT_CHANGED)) + .map(a -> (ContentAction) a) + .toList(); assertEquals(2, menuActions.size()); - assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.HOME::equals) - .count()); - assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem) - .filter(MenuItem.COMPANY::equals).count()); + assertEquals( + 1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.HOME::equals).count()); + assertEquals( + 1, + menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.COMPANY::equals).count()); assertEquals(2, contentActions.size()); - assertEquals(1, contentActions.stream().map(ContentAction::getContent) - .filter(Content.PRODUCTS::equals).count()); - assertEquals(1, contentActions.stream().map(ContentAction::getContent) - .filter(Content.COMPANY::equals).count()); - + assertEquals( + 1, + contentActions.stream() + .map(ContentAction::getContent) + .filter(Content.PRODUCTS::equals) + .count()); + assertEquals( + 1, + contentActions.stream() + .map(ContentAction::getContent) + .filter(Content.COMPANY::equals) + .count()); } - } diff --git a/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java index 20d381e9b75b..6d6270ddbd71 100644 --- a/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java +++ b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java @@ -38,11 +38,7 @@ import com.iluwatar.flux.view.View; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:18 PM - * - * @author Jeroen Meulemeester - */ +/** ContentStoreTest */ class ContentStoreTest { @Test @@ -63,7 +59,5 @@ void testOnAction() { verify(view, times(1)).storeChanged(eq(contentStore)); verifyNoMoreInteractions(view); assertEquals(Content.COMPANY, contentStore.getContent()); - } - } diff --git a/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java index 2266b1b2a7d1..368307602596 100644 --- a/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java +++ b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java @@ -38,11 +38,7 @@ import com.iluwatar.flux.view.View; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:18 PM - * - * @author Jeroen Meulemeester - */ +/** MenuStoreTest */ class MenuStoreTest { @Test @@ -63,7 +59,5 @@ void testOnAction() { verify(view, times(1)).storeChanged(eq(menuStore)); verifyNoMoreInteractions(view); assertEquals(MenuItem.PRODUCTS, menuStore.getSelected()); - } - } diff --git a/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java index 54f909a9baae..a6b91a95b244 100644 --- a/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java +++ b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java @@ -34,11 +34,7 @@ import com.iluwatar.flux.store.ContentStore; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:31 PM - * - * @author Jeroen Meulemeester - */ +/** ContentViewTest */ class ContentViewTest { @Test @@ -52,5 +48,4 @@ void testStoreChanged() { verify(store, times(1)).getContent(); verifyNoMoreInteractions(store); } - } diff --git a/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java index bd0713b3b4a7..8fda7e47a57d 100644 --- a/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java +++ b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java @@ -38,11 +38,7 @@ import com.iluwatar.flux.store.Store; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:31 PM - * - * @author Jeroen Meulemeester - */ +/** MenuViewTest */ class MenuViewTest { @Test @@ -67,7 +63,5 @@ void testItemClicked() { // We should receive a menu click action and a content changed action verify(store, times(2)).onAction(any(Action.class)); - } - } diff --git a/flyweight/README.md b/flyweight/README.md index 097bbb82e5b2..e8e95097aeea 100644 --- a/flyweight/README.md +++ b/flyweight/README.md @@ -1,45 +1,48 @@ --- -title: Flyweight +title: "Flyweight Pattern in Java: Maximizing Memory Efficiency with Shared Object Instances" +shortTitle: Flyweight +description: "Learn how the Flyweight design pattern optimizes memory usage in Java applications by sharing data among similar objects. Enhance performance and reduce memory footprint with practical examples and detailed explanations." category: Structural language: en tag: - - Gang of Four - - Performance + - Gang of Four + - Memory management + - Object composition + - Optimization + - Performance --- -## Intent +## Intent of Flyweight Design Pattern -Use sharing to support large numbers of fine-grained objects efficiently. +The Flyweight design pattern in Java is crucial for optimizing memory usage and enhancing application performance. By minimizing the number of objects created, it significantly reduces the memory footprint. The primary goal of the Flyweight pattern is to share as much data as possible among similar objects, thereby improving efficiency and performance. -## Explanation +## Detailed Explanation of Flyweight Pattern with Real-World Examples Real-world example -> Alchemist's shop has shelves full of magic potions. Many of the potions are the same so there is -> no need to create a new object for each of them. Instead, one object instance can represent -> multiple shelf items so the memory footprint remains small. +> A real-world application of the Flyweight pattern in Java can be seen in text editors like Microsoft Word or Google Docs. These applications use Flyweight to efficiently manage memory by sharing character objects, reducing the memory footprint significantly. In such applications, each character in a document could potentially be a separate object, which would be highly inefficient in terms of memory usage. Instead, the Flyweight pattern can be used to share character objects. For instance, all instances of the letter 'A' can share a single 'A' object with its intrinsic state (e.g., the shape of the character). The extrinsic state, such as the position, font, and color, can be stored separately and applied as needed. This way, the application efficiently manages memory by reusing existing objects for characters that appear multiple times. In plain words -> It is used to minimize memory usage or computational expenses by sharing as much as possible with -> similar objects. +> It is used to minimize memory usage or computational expenses by sharing as much as possible with similar objects. Wikipedia says -> In computer programming, flyweight is a software design pattern. A flyweight is an object that -> minimizes memory use by sharing as much data as possible with other similar objects; it is a way -> to use objects in large numbers when a simple repeated representation would use an unacceptable -> amount of memory. +> In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory. -**Programmatic example** +## Programmatic Example of Flyweight Pattern in Java -Translating our alchemist shop example from above. First of all, we have different potion types: +Alchemist's shop has shelves full of magic potions. Many of the potions are the same so there is no need to create a new object for each of them. Instead, one object instance can represent multiple shelf items so the memory footprint remains small. + +First of all, we have different `Potion` types: ```java public interface Potion { void drink(); } +``` +```java @Slf4j public class HealingPotion implements Potion { @Override @@ -47,7 +50,9 @@ public class HealingPotion implements Potion { LOGGER.info("You feel healed. (Potion={})", System.identityHashCode(this)); } } +``` +```java @Slf4j public class HolyWaterPotion implements Potion { @Override @@ -55,7 +60,9 @@ public class HolyWaterPotion implements Potion { LOGGER.info("You feel blessed. (Potion={})", System.identityHashCode(this)); } } +``` +```java @Slf4j public class InvisibilityPotion implements Potion { @Override @@ -80,29 +87,20 @@ public class PotionFactory { var potion = potions.get(type); if (potion == null) { switch (type) { - case HEALING -> { - potion = new HealingPotion(); - potions.put(type, potion); - } - case HOLY_WATER -> { - potion = new HolyWaterPotion(); - potions.put(type, potion); - } - case INVISIBILITY -> { - potion = new InvisibilityPotion(); - potions.put(type, potion); - } + case HEALING -> potion = new HealingPotion(); + case HOLY_WATER -> potion = new HolyWaterPotion(); + case INVISIBILITY -> potion = new InvisibilityPotion(); default -> { } } + potions.put(type, potion); } return potion; } } ``` -`AlchemistShop` contains two shelves of magic potions. The potions are created using the -aforementioned `PotionFactory`. +`AlchemistShop` contains two shelves of magic potions. The potions are created using the aforementioned `PotionFactory`. ```java @Slf4j @@ -152,54 +150,68 @@ public class AlchemistShop { In our scenario, a brave visitor enters the alchemist shop and drinks all the potions. ```java -// create the alchemist shop with the potions -var alchemistShop = new AlchemistShop(); -// a brave visitor enters the alchemist shop and drinks all the potions -alchemistShop.drinkPotions(); +public static void main(String[] args) { + // create the alchemist shop with the potions + var alchemistShop = new AlchemistShop(); + // a brave visitor enters the alchemist shop and drinks all the potions + alchemistShop.drinkPotions(); +} ``` Program output: -```java -Drinking top shelf potions -You become invisible. (Potion=1509514333) -You become invisible. (Potion=1509514333) -You feel strong. (Potion=739498517) -You feel healed. (Potion=125130493) -You become invisible. (Potion=1509514333) -You feel strong. (Potion=739498517) -You feel healed. (Potion=125130493) -You feel healed. (Potion=125130493) -Drinking bottom shelf potions -Urgh! This is poisonous. (Potion=166239592) -Urgh! This is poisonous. (Potion=166239592) -Urgh! This is poisonous. (Potion=166239592) -You feel blessed. (Potion=991505714) -You feel blessed. (Potion=991505714) +``` +09:02:52.731 [main] INFO com.iluwatar.flyweight.AlchemistShop -- Drinking top shelf potions +09:02:52.733 [main] INFO com.iluwatar.flyweight.InvisibilityPotion -- You become invisible. (Potion=1395089624) +09:02:52.733 [main] INFO com.iluwatar.flyweight.InvisibilityPotion -- You become invisible. (Potion=1395089624) +09:02:52.733 [main] INFO com.iluwatar.flyweight.StrengthPotion -- You feel strong. (Potion=1450821318) +09:02:52.733 [main] INFO com.iluwatar.flyweight.HealingPotion -- You feel healed. (Potion=668849042) +09:02:52.733 [main] INFO com.iluwatar.flyweight.InvisibilityPotion -- You become invisible. (Potion=1395089624) +09:02:52.733 [main] INFO com.iluwatar.flyweight.StrengthPotion -- You feel strong. (Potion=1450821318) +09:02:52.733 [main] INFO com.iluwatar.flyweight.HealingPotion -- You feel healed. (Potion=668849042) +09:02:52.733 [main] INFO com.iluwatar.flyweight.HealingPotion -- You feel healed. (Potion=668849042) +09:02:52.733 [main] INFO com.iluwatar.flyweight.AlchemistShop -- Drinking bottom shelf potions +09:02:52.734 [main] INFO com.iluwatar.flyweight.PoisonPotion -- Urgh! This is poisonous. (Potion=2096057945) +09:02:52.734 [main] INFO com.iluwatar.flyweight.PoisonPotion -- Urgh! This is poisonous. (Potion=2096057945) +09:02:52.734 [main] INFO com.iluwatar.flyweight.PoisonPotion -- Urgh! This is poisonous. (Potion=2096057945) +09:02:52.734 [main] INFO com.iluwatar.flyweight.HolyWaterPotion -- You feel blessed. (Potion=1689843956) +09:02:52.734 [main] INFO com.iluwatar.flyweight.HolyWaterPotion -- You feel blessed. (Potion=1689843956) ``` -## Class diagram - -![alt text](./etc/flyweight.urm.png "Flyweight pattern class diagram") - -## Applicability +## When to Use the Flyweight Pattern in Java -The Flyweight pattern's effectiveness depends heavily on how and where it's used. Apply the -Flyweight pattern when all of the following are true: +The Flyweight pattern's effectiveness depends heavily on how and where it's used. Apply the Flyweight pattern when all the following are true: -* An application uses a large number of objects. -* Storage costs are high because of the sheer quantity of objects. +* The Flyweight pattern is particularly effective in Java applications that use a large number of objects. +* When storage costs are high due to the quantity of objects, Flyweight helps by sharing intrinsic data and managing extrinsic state separately. * Most of the object state can be made extrinsic. -* Many groups of objects may be replaced by relatively few shared objects once the extrinsic state - is removed. -* The application doesn't depend on object identity. Since flyweight objects may be shared, identity -tests will return true for conceptually distinct objects. +* Many groups of objects may be replaced by relatively few shared objects once the extrinsic state is removed. +* The application doesn't depend on object identity. Since flyweight objects may be shared, identity tests will return true for conceptually distinct objects. -## Known uses +## Real-World Applications of Flyweight Pattern in Java * [java.lang.Integer#valueOf(int)](http://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#valueOf%28int%29) and similarly for Byte, Character and other wrapped types. +* Java’s String class utilizes the Flyweight pattern to manage string literals efficiently. +* GUI applications often use Flyweight for sharing objects like fonts or graphical components, thereby conserving memory and improving performance. + +## Benefits and Trade-offs of Flyweight Pattern + +Benefits: + +* Reduces the number of instances of an object, conserving memory. +* Centralizes state management, reducing the risk of inconsistent state. + +Trade-offs: + +* Increases complexity by adding the management layer for shared objects. +* Potential overhead in accessing shared objects if not well implemented. + +## Related Java Design Patterns + +* [Composite](https://java-design-patterns.com/patterns/composite/): Often combined with Flyweight when the composites are shareable. Both are used to manage hierarchies and structures of objects. +* [State](https://java-design-patterns.com/patterns/state/): Can be used to manage state in a shared Flyweight object, distinguishing internal state (invariant) from external state (context-specific). -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) diff --git a/flyweight/pom.xml b/flyweight/pom.xml index 9f3a9672c2b9..26bc01369fd9 100644 --- a/flyweight/pom.xml +++ b/flyweight/pom.xml @@ -34,6 +34,14 @@ flyweight + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java index 05e1a48b47f3..42ce63c8efe8 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java @@ -27,37 +27,33 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -/** - * AlchemistShop holds potions on its shelves. It uses PotionFactory to provide the potions. - */ +/** AlchemistShop holds potions on its shelves. It uses PotionFactory to provide the potions. */ @Slf4j public class AlchemistShop { private final List topShelf; private final List bottomShelf; - /** - * Constructor. - */ + /** Constructor. */ public AlchemistShop() { var factory = new PotionFactory(); - topShelf = List.of( - factory.createPotion(PotionType.INVISIBILITY), - factory.createPotion(PotionType.INVISIBILITY), - factory.createPotion(PotionType.STRENGTH), - factory.createPotion(PotionType.HEALING), - factory.createPotion(PotionType.INVISIBILITY), - factory.createPotion(PotionType.STRENGTH), - factory.createPotion(PotionType.HEALING), - factory.createPotion(PotionType.HEALING) - ); - bottomShelf = List.of( - factory.createPotion(PotionType.POISON), - factory.createPotion(PotionType.POISON), - factory.createPotion(PotionType.POISON), - factory.createPotion(PotionType.HOLY_WATER), - factory.createPotion(PotionType.HOLY_WATER) - ); + topShelf = + List.of( + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.STRENGTH), + factory.createPotion(PotionType.HEALING), + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.STRENGTH), + factory.createPotion(PotionType.HEALING), + factory.createPotion(PotionType.HEALING)); + bottomShelf = + List.of( + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.HOLY_WATER), + factory.createPotion(PotionType.HOLY_WATER)); } /** @@ -78,9 +74,7 @@ public final List getBottomShelf() { return List.copyOf(this.bottomShelf); } - /** - * Drink all the potions. - */ + /** Drink all the potions. */ public void drinkPotions() { LOGGER.info("Drinking top shelf potions"); topShelf.forEach(Potion::drink); diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java index 9c63e4a8d919..9fb66df96155 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * HealingPotion. - */ +/** HealingPotion. */ @Slf4j public class HealingPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java index 47d1cd4af8e7..73822c8bff2e 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * HolyWaterPotion. - */ +/** HolyWaterPotion. */ @Slf4j public class HolyWaterPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java index 09a3d83a237d..1e5ada3cc2f4 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * InvisibilityPotion. - */ +/** InvisibilityPotion. */ @Slf4j public class InvisibilityPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java index 8123053f8f7f..a25bfc6fcee9 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * PoisonPotion. - */ +/** PoisonPotion. */ @Slf4j public class PoisonPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java b/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java index edbf07789834..fa1af252b9af 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java @@ -1,33 +1,31 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.flyweight; - -/** - * Interface for Potions. - */ -public interface Potion { - - void drink(); -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.flyweight; + +/** Interface for Potions. */ +public interface Potion { + + void drink(); +} diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java b/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java index 582cf8b90b67..2ac4e58a1bee 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java @@ -1,61 +1,60 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.flyweight; - -import java.util.EnumMap; -import java.util.Map; - -/** - * PotionFactory is the Flyweight in this example. It minimizes memory use by sharing object - * instances. It holds a map of potion instances and new potions are created only when none of the - * type already exists. - */ -public class PotionFactory { - - private final Map potions; - - public PotionFactory() { - potions = new EnumMap<>(PotionType.class); - } - - Potion createPotion(PotionType type) { - var potion = potions.get(type); - if (potion == null) { - switch (type) { - case HEALING -> potion = new HealingPotion(); - case HOLY_WATER -> potion = new HolyWaterPotion(); - case INVISIBILITY -> potion = new InvisibilityPotion(); - case POISON -> potion = new PoisonPotion(); - case STRENGTH -> potion = new StrengthPotion(); - default -> { - } - } - if (potion != null) { - potions.put(type, potion); - } - } - return potion; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.flyweight; + +import java.util.EnumMap; +import java.util.Map; + +/** + * PotionFactory is the Flyweight in this example. It minimizes memory use by sharing object + * instances. It holds a map of potion instances and new potions are created only when none of the + * type already exists. + */ +public class PotionFactory { + + private final Map potions; + + public PotionFactory() { + potions = new EnumMap<>(PotionType.class); + } + + Potion createPotion(PotionType type) { + var potion = potions.get(type); + if (potion == null) { + switch (type) { + case HEALING -> potion = new HealingPotion(); + case HOLY_WATER -> potion = new HolyWaterPotion(); + case INVISIBILITY -> potion = new InvisibilityPotion(); + case POISON -> potion = new PoisonPotion(); + case STRENGTH -> potion = new StrengthPotion(); + default -> {} + } + if (potion != null) { + potions.put(type, potion); + } + } + return potion; + } +} diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java b/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java index b45fd2268746..da73c1f1caa1 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java @@ -1,33 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.flyweight; - -/** - * Enumeration for potion types. - */ -public enum PotionType { - - HEALING, INVISIBILITY, STRENGTH, HOLY_WATER, POISON -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.flyweight; + +/** Enumeration for potion types. */ +public enum PotionType { + HEALING, + INVISIBILITY, + STRENGTH, + HOLY_WATER, + POISON +} diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java index 22dfa24ede15..57a880b9f0e9 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * StrengthPotion. - */ +/** StrengthPotion. */ @Slf4j public class StrengthPotion implements Potion { diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java index 79a70611ea95..b5aff254afb3 100644 --- a/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java @@ -31,11 +31,7 @@ import java.util.HashSet; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 10:54 PM - * - * @author Jeroen Meulemeester - */ +/** AlchemistShopTest */ class AlchemistShopTest { @Test diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java index 59ef84b3d365..d2960fb15a79 100644 --- a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.flyweight; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/front-controller/README.md b/front-controller/README.md index 6be08d55f00c..c04e9b5198ad 100644 --- a/front-controller/README.md +++ b/front-controller/README.md @@ -1,33 +1,144 @@ --- -title: Front Controller -category: Structural +title: "Front Controller Pattern in Java: Centralizing Web Request Handling" +shortTitle: Front Controller +description: "Explore the Front Controller design pattern in Java for centralized request handling. Learn how to improve web application efficiency and consistency with this architectural pattern." +category: Architectural language: en tag: - - Decoupling + - Architecture + - Decoupling + - Enterprise patterns + - Layered architecture + - Web development --- -## Intent -Introduce a common handler for all requests for a web site. This -way we can encapsulate common functionality such as security, -internationalization, routing and logging in a single place. +## Also known as -## Class diagram -![alt text](./etc/front-controller.png "Front Controller") +* Centralized Request Handling -## Applicability -Use the Front Controller pattern when +## Intent of Front Controller Design Pattern -* you want to encapsulate common request handling functionality in single place -* you want to implements dynamic request handling i.e. change routing without modifying code -* make web server configuration portable, you only need to register the handler web server specific way +The Front Controller design pattern aims to provide a centralized entry point for handling all incoming web requests. This pattern ensures consistent and efficient request routing and management across a Java web application. -## Real world examples +## Detailed Explanation of Front Controller Pattern with Real-World Examples + +Real-world example + +> In a real-world scenario, a front desk in a hotel serves as the centralized request handling point, similar to how the Front Controller design pattern functions in web application architecture. This desk acts as the "front controller" of the hotel, responsible for receiving all inquiries, from room service orders to maintenance requests. The receptionist assesses each request and routes it to the appropriate department—housekeeping, the kitchen, or maintenance. This system centralizes request handling, ensuring that guest needs are addressed efficiently and consistently, similar to how a Front Controller in a software application manages all incoming requests and delegates them to specific handlers. + +In plain words + +> The Front Controller design pattern centralizes incoming web requests into a single handling point, allowing consistent processing and delegation across an application. + +Wikipedia says + +> The front controller software design pattern is listed in several pattern catalogs and is related to the design of web applications. It is "a controller that handles all requests for a website", which is a useful structure for web application developers to achieve flexibility and reuse without code redundancy. + +Architecture diagram + +![Front Controller Architecture Diagram](./etc/front-controller-architecture-diagram.png) + + +## Programmatic Example of Front Controller Pattern in Java + +The Front Controller design pattern is a pattern that provides a centralized entry point for handling all requests in a web application. It ensures that request handling is managed consistently and efficiently across an application. + +In the provided code, we can see an example of the Front Controller pattern in the `App`, `FrontController` and `Dispatcher` classes. + +The `App` class is the entry point of the application. It creates an instance of `FrontController` and uses it to handle various requests. + +```java +public class App { + + public static void main(String[] args) { + var controller = new FrontController(); + controller.handleRequest("Archer"); + controller.handleRequest("Catapult"); + controller.handleRequest("foobar"); + } +} +``` + +The `FrontController` class is the front controller in this example. It handles all requests and delegates them to the `Dispatcher`. + +```java +public class FrontController { + + private final Dispatcher dispatcher; + + public FrontController() { + this.dispatcher = new Dispatcher(); + } + + public void handleRequest(String request) { + dispatcher.dispatch(request); + } +} +``` + +The `Dispatcher` class is responsible for handling the dispatching of requests to the appropriate command. It retrieves the corresponding command based on the request and invokes the command's process method to handle the business logic. + +```java +public class Dispatcher { + + public void dispatch(String request) { + var command = getCommand(request); + command.process(); + } + + Command getCommand(String request) { + var commandClass = getCommandClass(request); + try { + return (Command) commandClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new ApplicationException(e); + } + } + + static Class getCommandClass(String request) { + try { + return Class.forName("com.iluwatar.front.controller." + request + "Command"); + } catch (ClassNotFoundException e) { + return UnknownCommand.class; + } + } +} +``` + +In this example, when a request is received, the `FrontController` delegates the request to the `Dispatcher`, which creates a command object based on the request and calls its `process` method. The command object is responsible for handling the request and rendering the appropriate view. + +This is a basic example of the Front Controller pattern, where all requests are handled by a single controller and dispatcher, ensuring consistent and efficient request handling. + +## When to Use the Front Controller Pattern in Java + +* The Front Controller design pattern is particularly useful for Java web applications that require a centralized mechanism for request handling. +* Systems that need a common processing point for all requests to perform tasks such as authentication, logging, and routing. + +## Real-World Applications of Front Controller Pattern in Java * [Apache Struts](https://struts.apache.org/) +* Java web frameworks like Spring MVC and JavaServer Faces (JSF) implement the Front Controller pattern through their central dispatcher servlet, which manages web requests and delegates responsibilities. + +## Benefits and Trade-offs of Front Controller Pattern + +Benefits: + +* The main benefit of the Front Controller design pattern is the centralization of request handling, which simplifies maintenance and ensures consistent behavior across the application. +* Eases the integration of services like security and user session management. +* Facilitates common behavior like routing, logging, and authentication across requests. + +Trade-offs: + +* Can become a bottleneck if not properly managed. +* Increases complexity in the dispatcher controller, requiring careful design to avoid tight coupling. + +## Related Java Design Patterns + +* [Page Controller](https://java-design-patterns.com/patterns/page-controller/): Front Controller can delegate requests to Page Controllers, which handle specific page requests. This division supports the Single Responsibility Principle. +* [Model-View-Controller (MVC)](https://java-design-patterns.com/patterns/model-view-controller/): Front Controller acts as the controller, managing the flow between model and view. +* [Command](https://java-design-patterns.com/patterns/command/): Can be used to encapsulate a request as an object, which the Front Controller can manipulate and delegate. -## Credits +## References and Credits -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) -* [Presentation Tier Patterns](http://www.javagyan.com/tutorials/corej2eepatterns/presentation-tier-patterns) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/front-controller/etc/front-controller-architecture-diagram.png b/front-controller/etc/front-controller-architecture-diagram.png new file mode 100644 index 000000000000..1caa0dc3b45d Binary files /dev/null and b/front-controller/etc/front-controller-architecture-diagram.png differ diff --git a/front-controller/pom.xml b/front-controller/pom.xml index c9eac51babb2..ba29e314bcc6 100644 --- a/front-controller/pom.xml +++ b/front-controller/pom.xml @@ -34,6 +34,14 @@ front-controller + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/App.java b/front-controller/src/main/java/com/iluwatar/front/controller/App.java index 5bc2b0422814..88f24360930a 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/App.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/App.java @@ -25,14 +25,14 @@ package com.iluwatar.front.controller; /** - * The Front Controller is a presentation tier pattern. Essentially it defines a controller that - * handles all requests for a web site. + * The Front Controller is a presentation tier pattern. Essentially, it defines a controller that + * handles all requests for a website. * *

The Front Controller pattern consolidates request handling through a single handler object ( - * {@link FrontController}). This object can carry out the common the behavior such as - * authorization, request logging and routing requests to corresponding views. + * {@link FrontController}). This object can carry out common behavior such as authorization, + * request logging and routing requests to corresponding views. * - *

Typically the requests are mapped to command objects ({@link Command}) which then display the + *

Typically, the requests are mapped to command objects ({@link Command}) which then display the * correct view ({@link View}). * *

In this example we have implemented two views: {@link ArcherView} and {@link CatapultView}. diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java index 04a9744061ff..2016c9d4d593 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java @@ -24,12 +24,12 @@ */ package com.iluwatar.front.controller; -/** - * Custom exception type. - */ +import java.io.Serial; + +/** Custom exception type. */ public class ApplicationException extends RuntimeException { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; public ApplicationException(Throwable cause) { super(cause); diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java index ec3a120c0e10..a062e340577a 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Command for archers. - */ +/** Command for archers. */ public class ArcherCommand implements Command { @Override diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java index 9b7d96d9d9b1..794ecccb3991 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View for archers. - */ +/** View for archers. */ @Slf4j public class ArcherView implements View { diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java index 6d9c63dc367a..6d032f99929f 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Command for catapults. - */ +/** Command for catapults. */ public class CatapultCommand implements Command { @Override diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java index 59c56c5ddb0d..68a02460a6c1 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View for catapults. - */ +/** View for catapults. */ @Slf4j public class CatapultView implements View { diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java index 65e3359fe266..a55b978485ef 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Commands are the intermediary between requests and views. - */ +/** Commands are the intermediary between requests and views. */ public interface Command { void process(); diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/Dispatcher.java b/front-controller/src/main/java/com/iluwatar/front/controller/Dispatcher.java new file mode 100644 index 000000000000..5b9da51b0abe --- /dev/null +++ b/front-controller/src/main/java/com/iluwatar/front/controller/Dispatcher.java @@ -0,0 +1,72 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.front.controller; + +/** + * The Dispatcher class is responsible for handling the dispatching of requests to the appropriate + * command. It retrieves the corresponding command based on the request and invokes the command's + * process method to handle the business logic. + */ +public class Dispatcher { + + /** + * Dispatches the request to the appropriate command. + * + * @param request the request to be handled + */ + public void dispatch(String request) { + var command = getCommand(request); + command.process(); + } + + /** + * Retrieves the appropriate command instance for the given request. + * + * @param request the request to be handled + * @return the command instance corresponding to the request + */ + Command getCommand(String request) { + var commandClass = getCommandClass(request); + try { + return (Command) commandClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new ApplicationException(e); + } + } + + /** + * Retrieves the Class object for the command corresponding to the given request. + * + * @param request the request to be handled + * @return the Class object of the command corresponding to the request + */ + static Class getCommandClass(String request) { + try { + return Class.forName("com.iluwatar.front.controller." + request + "Command"); + } catch (ClassNotFoundException e) { + return UnknownCommand.class; + } + } +} diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java index 54e7119058a7..3e1a1d1b88b1 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View for errors. - */ +/** View for errors. */ @Slf4j public class ErrorView implements View { diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java index 995f917b3fd5..293cedba4e39 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java @@ -25,30 +25,19 @@ package com.iluwatar.front.controller; /** - * FrontController is the handler class that takes in all the requests and renders the correct - * response. + * The FrontController is responsible for handling all incoming requests. It delegates the + * processing of requests to the Dispatcher, which then determines the appropriate command and view + * to render the correct response. */ public class FrontController { - public void handleRequest(String request) { - var command = getCommand(request); - command.process(); - } + private final Dispatcher dispatcher; - private Command getCommand(String request) { - var commandClass = getCommandClass(request); - try { - return (Command) commandClass.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new ApplicationException(e); - } + public FrontController() { + this.dispatcher = new Dispatcher(); } - private static Class getCommandClass(String request) { - try { - return Class.forName("com.iluwatar.front.controller." + request + "Command"); - } catch (ClassNotFoundException e) { - return UnknownCommand.class; - } + public void handleRequest(String request) { + dispatcher.dispatch(request); } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java index 1cb7060b2831..56d2b60a16f9 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Default command in case the mapping is not successful. - */ +/** Default command in case the mapping is not successful. */ public class UnknownCommand implements Command { @Override diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/View.java b/front-controller/src/main/java/com/iluwatar/front/controller/View.java index 187ceeb7c609..cc741b9e6db0 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/View.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/View.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Views are the representations rendered for the user. - */ +/** Views are the representations rendered for the user. */ public interface View { void display(); diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java index ad2abda468ef..287398cc5763 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.front.controller; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java index 9214b2ce1e3b..91e071f8be4e 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java @@ -28,11 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/13/15 - 1:35 PM - * - * @author Jeroen Meulemeester - */ +/** ApplicationExceptionTest */ class ApplicationExceptionTest { @Test @@ -40,5 +36,4 @@ void testCause() { final var cause = new Exception(); assertSame(cause, new ApplicationException(cause).getCause()); } - -} \ No newline at end of file +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java index 1906fd16efa7..7f7a3d39062c 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java @@ -33,11 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * Date: 12/13/15 - 1:39 PM - * - * @author Jeroen Meulemeester - */ +/** CommandTest */ class CommandTest { private InMemoryAppender appender; @@ -54,14 +50,13 @@ void tearDown() { static List dataProvider() { return List.of( - new Object[]{"Archer", "Displaying archers"}, - new Object[]{"Catapult", "Displaying catapults"}, - new Object[]{"NonExistentCommand", "Error 500"} - ); + new Object[] {"Archer", "Displaying archers"}, + new Object[] {"Catapult", "Displaying catapults"}, + new Object[] {"NonExistentCommand", "Error 500"}); } /** - * @param request The request that's been tested + * @param request The request that's been tested * @param displayMessage The expected display message */ @ParameterizedTest @@ -73,5 +68,4 @@ void testDisplay(String request, String displayMessage) { assertEquals(displayMessage, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/DispatcherTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/DispatcherTest.java new file mode 100644 index 000000000000..64c611c49442 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/DispatcherTest.java @@ -0,0 +1,91 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.front.controller; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DispatcherTest { + + private Dispatcher dispatcher; + + @BeforeEach + public void setUp() { + dispatcher = new Dispatcher(); + } + + @Test + void testDispatchKnownCommand() { + Command mockCommand = mock(ArcherCommand.class); + dispatcher = spy(dispatcher); + doReturn(mockCommand).when(dispatcher).getCommand("Archer"); + + dispatcher.dispatch("Archer"); + + verify(mockCommand, times(1)).process(); + } + + @Test + void testDispatchUnknownCommand() { + Command mockCommand = mock(UnknownCommand.class); + dispatcher = spy(dispatcher); + doReturn(mockCommand).when(dispatcher).getCommand("Unknown"); + + dispatcher.dispatch("Unknown"); + + verify(mockCommand, times(1)).process(); + } + + @Test + void testGetCommandKnown() { + Command command = dispatcher.getCommand("Archer"); + assertNotNull(command); + assertTrue(command instanceof ArcherCommand); + } + + @Test + void testGetCommandUnknown() { + Command command = dispatcher.getCommand("Unknown"); + assertNotNull(command); + assertTrue(command instanceof UnknownCommand); + } + + @Test + void testGetCommandClassKnown() { + Class commandClass = Dispatcher.getCommandClass("Archer"); + assertNotNull(commandClass); + assertEquals(ArcherCommand.class, commandClass); + } + + @Test + void testGetCommandClassUnknown() { + Class commandClass = Dispatcher.getCommandClass("Unknown"); + assertNotNull(commandClass); + assertEquals(UnknownCommand.class, commandClass); + } +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java index 8f510cf50606..b4163aef4c5a 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java @@ -33,11 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * Date: 12/13/15 - 1:39 PM - * - * @author Jeroen Meulemeester - */ +/** FrontControllerTest */ class FrontControllerTest { private InMemoryAppender appender; @@ -54,14 +50,13 @@ void tearDown() { static List dataProvider() { return List.of( - new Object[]{new ArcherCommand(), "Displaying archers"}, - new Object[]{new CatapultCommand(), "Displaying catapults"}, - new Object[]{new UnknownCommand(), "Error 500"} - ); + new Object[] {new ArcherCommand(), "Displaying archers"}, + new Object[] {new CatapultCommand(), "Displaying catapults"}, + new Object[] {new UnknownCommand(), "Error 500"}); } /** - * @param command The command that's been tested + * @param command The command that's been tested * @param displayMessage The expected display message */ @ParameterizedTest @@ -72,5 +67,4 @@ void testDisplay(Command command, String displayMessage) { assertEquals(displayMessage, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java index e5bb7f1d363b..09cd1e21e617 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java @@ -33,11 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * Date: 12/13/15 - 1:39 PM - * - * @author Jeroen Meulemeester - */ +/** ViewTest */ class ViewTest { private InMemoryAppender appender; @@ -54,14 +50,13 @@ void tearDown() { static List dataProvider() { return List.of( - new Object[]{new ArcherView(), "Displaying archers"}, - new Object[]{new CatapultView(), "Displaying catapults"}, - new Object[]{new ErrorView(), "Error 500"} - ); + new Object[] {new ArcherView(), "Displaying archers"}, + new Object[] {new CatapultView(), "Displaying catapults"}, + new Object[] {new ErrorView(), "Error 500"}); } /** - * @param view The view that's been tested + * @param view The view that's been tested * @param displayMessage The expected display message */ @ParameterizedTest @@ -72,5 +67,4 @@ void testDisplay(View view, String displayMessage) { assertEquals(displayMessage, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java b/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java index ca0a5ccbfa29..86268d4c38c8 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java @@ -27,13 +27,11 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; -import org.slf4j.LoggerFactory; import java.util.LinkedList; import java.util.List; +import org.slf4j.LoggerFactory; -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/function-composition/.gitignore b/function-composition/.gitignore new file mode 100644 index 000000000000..431eba612aae --- /dev/null +++ b/function-composition/.gitignore @@ -0,0 +1,59 @@ +################## Eclipse ###################### +target +.metadata +.settings +.classpath +.project +*.class +tmp/ +*.tmp +*.bak +*~.nib +local.properties +.loadpath +.recommenders +.DS_Store + +####### Java annotation processor (APT) ######## +.factorypath + +################ Package Files ################## +*.jar +*.war +*.ear +*.swp +datanucleus.log +/bin/ +*.log +event-sourcing/Journal.json + +################## Checkstyle ################### +.checkstyle + +##################### STS ####################### +.apt_generated +.springBeans +.sts4-cache + +################# IntelliJ IDEA ################# +.idea +*.iws +*.iml +*.ipr + +################### NetBeans #################### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +#################### VS Code #################### +.vscode/ + +#################### Java Design Patterns ####### +etc/Java Design Patterns.urm.puml +serialized-entity/output.txt diff --git a/function-composition/README.md b/function-composition/README.md new file mode 100644 index 000000000000..3398f4e9f2ce --- /dev/null +++ b/function-composition/README.md @@ -0,0 +1,134 @@ +--- +title: "Function Composition Pattern in Java: Crafting Elegant Functional Pipelines" +shortTitle: Function Composition +description: "Learn about the Function Composition design pattern in Java. Discover how to create complex functions by combining simpler ones, enhancing code modularity and reusability. Explore real-world examples, benefits, and applications." +category: Functional +language: en +tag: + - Code simplification + - Composition + - Decoupling + - Functional decomposition + - Lambda + - Reusability +--- + +## Also known as + +* Function Chaining +* Function Pipelining +* Functional Composition + +## Intent of Function Composition Design Pattern + +The Function Composition design pattern in Java enables the creation of complex functions by combining simpler ones. This enhances modular code and reusability, crucial for maintainable software development. + +## Detailed Explanation of Function Composition Pattern with Real-World Examples + +Real-world example + +> Imagine a fast-food restaurant where the process of making a burger is broken down into several steps: grilling the patty, toasting the bun, adding condiments, and assembling the burger. Each of these steps can be seen as a function. +> +> In the Functional Composition design pattern, these individual steps (functions) can be composed into a complete burger-making process. Each step remains simple and reusable. For instance, the grilling function could be reused for making sandwiches or other dishes that require a grilled patty. This modular approach allows the restaurant to efficiently create various menu items by reusing and combining simple, predefined steps. + +In plain words + +> The Function Composition pattern allows building complex functions by combining simpler ones, making it easier to manage, test, and reuse individual pieces of functionality. + +Wikipedia says + +> Function composition is an act or mechanism to combine simple functions to build more complicated ones. Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole. + +## Programmatic Example of Function Composition Pattern in Java + +In the functional programming paradigm, function composition is a powerful technique. For instance, in Java, you can use higher-order functions to compose operations like multiplying and squaring numbers. + +Using Java's functional interfaces, we can define simple functions and compose them. Here's how function composition works in Java. + +Let's start with defining two simple functions. In this case, we have a function `timesTwo` that multiplies its input by 2, and a function `square` that squares its input. + +```java +Function timesTwo = x -> x * 2; +Function square = x -> x * x; +``` + +Next, we use the `FunctionComposer` class to compose these two functions into a new function. The `composeFunctions` method takes two functions as arguments and returns a new function that is the composition of the input functions. + +```java +Function composedFunction = FunctionComposer.composeFunctions(timesTwo, square); +``` + +Finally, we apply the composed function to an input value. In this case, we apply it to the number 3. The result is the square of the number 3 multiplied by 2, which is 36. + +```java +public static void main(String[] args) { + final var logger = LoggerFactory.getLogger(App.class); + Function timesTwo = x -> x * 2; + Function square = x -> x * x; + + Function composedFunction = FunctionComposer.composeFunctions(timesTwo, square); + + int result = composedFunction.apply(3); + logger.info("Result of composing 'timesTwo' and 'square' functions applied to 3 is: " + result); +} +``` + +This will output: + +``` +Result of composing 'timesTwo' and 'square' functions applied to 3 is: 36 +``` + +This example demonstrates how the Function Composition pattern can be used to create complex functions by composing simpler ones, enhancing modularity and reusability of function-based logic. + +## Function Composition Pattern Sequence diagram + +![Functional Composition Diagram](./etc/function.composition.urm.png "Functional Composition") + +## When to Use the Function Composition Pattern in Java + +Use the Function Composition pattern when: + +* You want to create a pipeline of operations in Java. This enhances code clarity and quality by structuring complex logic into simpler, reusable components. +* You are working in a functional programming environment or a language that supports higher-order functions. +* When you want to avoid deep nesting of function calls and instead build a pipeline of operations. +* When aiming to promote immutability and side-effect-free functions in your design. + +## Function Composition Pattern Java Tutorials + +* [Function Composition in Java (Medium)](https://functionalprogramming.medium.com/function-composition-in-java-beaf39426f52) +* [Functional Programming in Java (Baeldung)](https://www.baeldung.com/java-functional-programming) + +## Real-World Applications of Function Composition Pattern in Java + +* Stream processing in Java 8 and above +* Query builders in ORM libraries +* Middleware composition in web frameworks + +## Benefits and Trade-offs of Function Composition Pattern + +Benefits: + +* High reusability of composed functions. +* Increased modularity, making complex functions easier to understand and maintain. +* Flexible and dynamic creation of function pipelines at runtime. +* Enhances readability by structuring code in a linear, declarative manner. +* Facilitates easier testing of individual functions. + +Trade-offs: + +* Potentially higher complexity when debugging composed functions. +* Overhead from creating and managing multiple function objects in memory-intensive scenarios. +* May require a paradigm shift for developers unfamiliar with functional programming concepts. + +## Related Java Design Patterns + +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/) - Both patterns allow processing to be broken down into a series of steps, but Functional Composition focuses on function composition rather than responsibility delegation. +* [Decorator](https://java-design-patterns.com/patterns/decorator/) - Similar in combining behaviors, but Decorator applies additional behavior to objects, while Functional Composition builds new functions. +* [Strategy](https://java-design-patterns.com/patterns/strategy/) - Provides interchangeable functions (strategies), which can be composed in Functional Composition. + +## References and Credits + +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Functional Programming in Java](https://amzn.to/3JUIc5Q) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3QCmGXs) diff --git a/function-composition/etc/function-composition.urm.puml b/function-composition/etc/function-composition.urm.puml new file mode 100644 index 000000000000..79b2a898fd12 --- /dev/null +++ b/function-composition/etc/function-composition.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.function.composition { + class App { + + App() + + main(args : String[]) {static} + } + class FunctionComposer { + + FunctionComposer() + + composeFunctions(f1 : Function, f2 : Function) : Function {static} + } +} +@enduml \ No newline at end of file diff --git a/function-composition/etc/function.composition.urm.png b/function-composition/etc/function.composition.urm.png new file mode 100644 index 000000000000..53e64608631e Binary files /dev/null and b/function-composition/etc/function.composition.urm.png differ diff --git a/function-composition/etc/function.composition.urm.puml b/function-composition/etc/function.composition.urm.puml new file mode 100644 index 000000000000..5fc3b5c6da37 --- /dev/null +++ b/function-composition/etc/function.composition.urm.puml @@ -0,0 +1,21 @@ +@startuml +skinparam monochrome true + +participant "App" as App +participant "FunctionComposer" as Composer +participant "Function" as F1 + +create F1 +App -> F1 : func1 = x -> x * 2 +create F1 +App -> F1 : func2 = x -> x * x + +App -> Composer : func1, func2 +activate Composer +Composer -> F1 : func1.andThen(func2) +deactivate Composer +activate F1 +F1 -> App : composedFunction +deactivate F1 + +@enduml \ No newline at end of file diff --git a/embedded-value/pom.xml b/function-composition/pom.xml similarity index 52% rename from embedded-value/pom.xml rename to function-composition/pom.xml index ccef450e4bfc..abf504b2979d 100644 --- a/embedded-value/pom.xml +++ b/function-composition/pom.xml @@ -26,41 +26,45 @@ --> - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - embedded-value - - - com.h2database - h2 - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.embedded.value.App - - - - - - - - + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + function-composition + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + App + + + + + + + + \ No newline at end of file diff --git a/function-composition/src/main/java/com/iluwatar/function/composition/App.java b/function-composition/src/main/java/com/iluwatar/function/composition/App.java new file mode 100644 index 000000000000..206d8fe1cd70 --- /dev/null +++ b/function-composition/src/main/java/com/iluwatar/function/composition/App.java @@ -0,0 +1,49 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.function.composition; + +import java.util.function.Function; +import org.slf4j.LoggerFactory; + +/** Main application class to demonstrate the use of function composition. */ +public class App { + + /** + * Main method to demonstrate function composition using FunctionComposer. + * + * @param args command line arguments (not used) + */ + public static void main(String[] args) { + final var logger = LoggerFactory.getLogger(App.class); + Function timesTwo = x -> x * 2; + Function square = x -> x * x; + + Function composedFunction = + FunctionComposer.composeFunctions(timesTwo, square); + + int result = composedFunction.apply(3); + logger.info("Result of composing 'timesTwo' and 'square' functions applied to 3 is: " + result); + } +} diff --git a/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java b/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java new file mode 100644 index 000000000000..9438b85507a2 --- /dev/null +++ b/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java @@ -0,0 +1,47 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.function.composition; + +import java.util.function.Function; + +/** + * Class for composing functions using the Function Composition pattern. Provides a static method to + * compose two functions using the 'andThen' method. + */ +public class FunctionComposer { + + /** + * Composes two functions where the output of the first function becomes the input of the second + * function. + * + * @param f1 the first function to apply + * @param f2 the second function to apply after the first + * @return a composed function that applies f1 and then f2 + */ + public static Function composeFunctions( + Function f1, Function f2) { + return f1.andThen(f2); + } +} diff --git a/function-composition/src/test/java/com/iluwatar/function/composition/AppTest.java b/function-composition/src/test/java/com/iluwatar/function/composition/AppTest.java new file mode 100644 index 000000000000..f1640675c761 --- /dev/null +++ b/function-composition/src/test/java/com/iluwatar/function/composition/AppTest.java @@ -0,0 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.function.composition; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +/** Application test */ +class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java b/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java new file mode 100644 index 000000000000..451fb787b54c --- /dev/null +++ b/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java @@ -0,0 +1,96 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.function.composition; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +/** Test class for FunctionComposer. */ +public class FunctionComposerTest { + + /** Tests the composition of two functions. */ + @Test + void testComposeFunctions() { + Function timesTwo = x -> x * 2; + Function square = x -> x * x; + + Function composed = FunctionComposer.composeFunctions(timesTwo, square); + + assertEquals(36, composed.apply(3), "Expected output of composed functions is 36"); + } + + /** Tests function composition with identity function. */ + @Test + void testComposeWithIdentity() { + Function identity = Function.identity(); + Function timesThree = x -> x * 3; + + Function composedLeft = + FunctionComposer.composeFunctions(identity, timesThree); + Function composedRight = + FunctionComposer.composeFunctions(timesThree, identity); + + assertEquals( + 9, composedLeft.apply(3), "Composition with identity on the left should be the same"); + assertEquals( + 9, composedRight.apply(3), "Composition with identity on the right should be the same"); + } + + /** Tests function composition resulting in zero. */ + @Test + void testComposeToZero() { + Function multiply = x -> x * 10; + Function toZero = x -> 0; + + Function composed = FunctionComposer.composeFunctions(multiply, toZero); + + assertEquals( + 0, composed.apply(5), "Expected output of function composition leading to zero is 0"); + } + + /** Tests the composition with a negative function. */ + @Test + void testComposeNegative() { + Function negate = x -> -x; + Function square = x -> x * x; + + Function composed = FunctionComposer.composeFunctions(negate, square); + + assertEquals(9, composed.apply(3), "Expected square of negative number to be positive"); + } + + /** Tests the composition of functions that cancel each other out. */ + @Test + void testComposeInverseFunctions() { + Function timesTwo = x -> x * 2; + Function half = x -> x / 2; + + Function composed = FunctionComposer.composeFunctions(timesTwo, half); + + assertEquals(5, composed.apply(5), "Expect the functions to cancel each other out"); + } +} diff --git a/game-loop/README.md b/game-loop/README.md index a139f6fd8237..69d2d641193f 100644 --- a/game-loop/README.md +++ b/game-loop/README.md @@ -1,44 +1,42 @@ ---- -title: Game Loop +--- +title: "Game Loop Pattern in Java: Mastering Smooth Game Mechanics" +shortTitle: Game Loop +description: "Learn about the Game Loop design pattern, its implementation in Java, and how it ensures smooth gameplay by continuously updating game state, processing inputs, and rendering. Ideal for real-time simulations and gaming." category: Behavioral language: en -tag: - - Game programming ---- - -## Intent - -A game loop runs continuously during gameplay. Each turn of the loop, it processes user input -without blocking, updates the game state, and renders the game. It tracks the passage of time to -control the rate of gameplay. +tag: + - Concurrency + - Event-driven + - Game programming + - Performance +--- -This pattern decouples progression of game time from user input and processor speed. +## Also known as -## Applicability - -This pattern is used in every game engine. +* Game Cycle +* Main Game Loop -## Explanation +## Intent of Game Loop Design Pattern -Real world example +The Game Loop design pattern is essential for creating smooth and interactive gaming experiences by facilitating continuous game execution. Each loop cycle processes input, updates the game state, and renders the game state to the screen, ensuring consistent performance across all hardware setups. -> Game loop is the main process of all the game rendering threads. It's present in all modern games. -> It drives input process, internal status update, rendering, AI and all the other processes. +## Detailed Explanation of Game Loop Pattern with Real-World Examples + +Real-world example + +> A practical analogy of the Game Loop can be seen in an amusement park ride, like a roller coaster. Similar to how the ride operates in a loop, updating its state and ensuring smooth operation, the Game Loop continuously processes inputs and updates the game state for a seamless gaming experience. The roller coaster operates in a continuous loop, where the state of the ride (the position and speed of the coaster) is continuously updated while the ride is running. The control system of the roller coaster ensures that the cars move smoothly along the track, adjusting speeds, and handling the ride's safety systems in real-time. Just like the game loop, this control system repeatedly processes inputs (such as the current speed and position), updates the state, and triggers outputs (like adjusting the brakes or accelerating the cars) to maintain the desired operation throughout the duration of the ride. In plain words -> Game Loop pattern ensures that game time progresses in equal speed in all different hardware -> setups. +> Game Loop pattern ensures that game time progresses in equal speed in all different hardware setups. Wikipedia says -> The central component of any game, from a programming standpoint, is the game loop. The game loop -> allows the game to run smoothly regardless of a user's input, or lack thereof. +> The central component of any game, from a programming standpoint, is the game loop. The game loop allows the game to run smoothly regardless of a user's input, or lack thereof. -**Programmatic Example** +## Programmatic Example of Game Loop Pattern in Java -Let's start with something simple. Here's `Bullet` class. Bullets will move in our game. For -demonstration purposes it's enough that it has 1-dimensional position. +In our Java example, we illustrate a simple game loop controlling a bullet's movement, updating its position, ensuring smooth rendering, and responding to user inputs. The Game Loop is the main process driving all game rendering threads, present in all modern games. It handles input processing, internal status updates, rendering, AI, and other processes. Starting with a simple `Bullet` class, we demonstrate the movement of bullets in our game, focusing on their 1-dimensional position for demonstration purposes. ```java public class Bullet { @@ -81,8 +79,7 @@ public class GameController { } ``` -Now we introduce the game loop. Or actually in this demo we have 3 different game loops. Let's see -the base class `GameLoop` first. +Now we introduce the game loop. Actually, in this demo we have 3 different game loops. Let's see the base class `GameLoop` first. ```java public enum GameStatus { @@ -157,34 +154,99 @@ public class FrameBasedGameLoop extends GameLoop { } ``` +Here's the second game loop implementation, `FixedStepGameLoop`: + +```java +public class FixedStepGameLoop extends GameLoop { + + /** + * 20 ms per frame = 50 FPS. + */ + private static final long MS_PER_FRAME = 20; + + @Override + protected void processGameLoop() { + var previousTime = System.currentTimeMillis(); + var lag = 0L; + while (isGameRunning()) { + var currentTime = System.currentTimeMillis(); + var elapsedTime = currentTime - previousTime; + previousTime = currentTime; + lag += elapsedTime; + + processInput(); + + while (lag >= MS_PER_FRAME) { + update(); + lag -= MS_PER_FRAME; + } + + render(); + } + } + + protected void update() { + controller.moveBullet(0.5f * MS_PER_FRAME / 1000); + } +} +``` + +And the third game loop implementation, `VariableStepGameLoop`: + +```java +public class VariableStepGameLoop extends GameLoop { + + @Override + protected void processGameLoop() { + var lastFrameTime = System.currentTimeMillis(); + while (isGameRunning()) { + processInput(); + var currentFrameTime = System.currentTimeMillis(); + var elapsedTime = currentFrameTime - lastFrameTime; + update(elapsedTime); + lastFrameTime = currentFrameTime; + render(); + } + } + + protected void update(Long elapsedTime) { + controller.moveBullet(0.5f * elapsedTime / 1000); + } + +} +``` + Finally, we show all the game loops in action. ```java +public static void main(String[] args) { + try { - LOGGER.info("Start frame-based game loop:"); - var frameBasedGameLoop = new FrameBasedGameLoop(); - frameBasedGameLoop.run(); - Thread.sleep(GAME_LOOP_DURATION_TIME); - frameBasedGameLoop.stop(); - LOGGER.info("Stop frame-based game loop."); - - LOGGER.info("Start variable-step game loop:"); - var variableStepGameLoop = new VariableStepGameLoop(); - variableStepGameLoop.run(); - Thread.sleep(GAME_LOOP_DURATION_TIME); - variableStepGameLoop.stop(); - LOGGER.info("Stop variable-step game loop."); - - LOGGER.info("Start fixed-step game loop:"); - var fixedStepGameLoop = new FixedStepGameLoop(); - fixedStepGameLoop.run(); - Thread.sleep(GAME_LOOP_DURATION_TIME); - fixedStepGameLoop.stop(); - LOGGER.info("Stop variable-step game loop."); - + LOGGER.info("Start frame-based game loop:"); + var frameBasedGameLoop = new FrameBasedGameLoop(); + frameBasedGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + frameBasedGameLoop.stop(); + LOGGER.info("Stop frame-based game loop."); + + LOGGER.info("Start variable-step game loop:"); + var variableStepGameLoop = new VariableStepGameLoop(); + variableStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + variableStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + + LOGGER.info("Start fixed-step game loop:"); + var fixedStepGameLoop = new FixedStepGameLoop(); + fixedStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + fixedStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + } catch (InterruptedException e) { - LOGGER.error(e.getMessage()); + LOGGER.error(e.getMessage()); } +} ``` Program output: @@ -241,12 +303,36 @@ Current bullet position: 0.98999935 Stop variable-step game loop. ``` -## Class diagram +## When to Use the Game Loop Pattern in Java + +The Game Loop pattern is perfect for real-time simulations and gaming where continuous state updates and smooth frame rates are critical. + +## Real-World Applications of Game Loop Pattern in Java + +* Video games, both 2D and 3D, across various platforms. +* Real-time simulations that require a steady frame rate for updating logic and rendering. + +## Benefits and Trade-offs of Game Loop Pattern + +Benefits: + +* Ensures the game progresses smoothly and deterministically. +* Facilitates synchronization between the game state, user input, and screen rendering. +* Provides a clear structure for the game developers to manage game dynamics and timing. + +Trade-offs: + +* Can lead to performance issues if the loop is not well-managed, especially in resource-intensive updates or rendering. +* Difficulty in managing varying frame rates across different hardware. + +## Related Java Design Patterns + +* [State](https://java-design-patterns.com/patterns/state/): Often used within a game loop to manage different states of the game (e.g., menu, playing, paused). The relationship lies in managing the state-specific behavior and transitions smoothly within the game loop. +* [Observer](https://java-design-patterns.com/patterns/observer/): Useful in a game loop for event handling, where game entities can subscribe to and react to events (e.g., collision, scoring). -![alt text](./etc/game-loop.urm.png "Game Loop pattern class diagram") +## References and Credits -## Credits - +* [Game Programming Patterns](https://amzn.to/3K96fOn) +* [Game Engine Architecture, Third Edition](https://amzn.to/3VgB4av) +* [Real-Time Collision Detection](https://amzn.to/3W9Jj8T) * [Game Programming Patterns - Game Loop](http://gameprogrammingpatterns.com/game-loop.html) -* [Game Programming Patterns](https://www.amazon.com/gp/product/0990582906/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0990582906&linkId=1289749a703b3fe0e24cd8d604d7c40b) -* [Game Engine Architecture, Third Edition](https://www.amazon.com/gp/product/1138035459/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1138035459&linkId=94502746617211bc40e0ef49d29333ac) diff --git a/game-loop/pom.xml b/game-loop/pom.xml index af782d4cec63..1ef70a6509b9 100644 --- a/game-loop/pom.xml +++ b/game-loop/pom.xml @@ -34,6 +34,14 @@ 4.0.0 game-loop + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/App.java b/game-loop/src/main/java/com/iluwatar/gameloop/App.java index f0193739642c..76ed426e6a3c 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/App.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/App.java @@ -27,20 +27,19 @@ import lombok.extern.slf4j.Slf4j; /** - * A game loop runs continuously during gameplay. Each turn of the loop, it processes - * user input without blocking, updates the game state, and renders the game. It tracks - * the passage of time to control the rate of gameplay. + * A game loop runs continuously during gameplay. Each turn of the loop, it processes user input + * without blocking, updates the game state, and renders the game. It tracks the passage of time to + * control the rate of gameplay. */ @Slf4j public class App { - /** - * Each type of game loop will run for 2 seconds. - */ + /** Each type of game loop will run for 2 seconds. */ private static final int GAME_LOOP_DURATION_TIME = 2000; /** * Program entry point. + * * @param args runtime arguments */ public static void main(String[] args) { @@ -71,5 +70,4 @@ public static void main(String[] args) { LOGGER.error(e.getMessage()); } } - } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java b/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java index a0999399681e..00b1a8b773b7 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java @@ -24,22 +24,15 @@ */ package com.iluwatar.gameloop; -/** - * Bullet object class. - */ +import lombok.Getter; +import lombok.Setter; + +/** Bullet object class. */ public class Bullet { - private float position; + @Getter @Setter private float position; public Bullet() { position = 0.0f; } - - public float getPosition() { - return position; - } - - public void setPosition(float position) { - this.position = position; - } } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java index 3f775a9d2a77..58c4e0dfdca2 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java @@ -25,15 +25,13 @@ package com.iluwatar.gameloop; /** - * For fixed-step game loop, a certain amount of real time has elapsed since the - * last turn of the game loop. This is how much game time need to be simulated for - * the game’s “now” to catch up with the player’s. + * For fixed-step game loop, a certain amount of real time has elapsed since the last turn of the + * game loop. This is how much game time need to be simulated for the game’s “now” to catch up with + * the player’s. */ public class FixedStepGameLoop extends GameLoop { - /** - * 20 ms per frame = 50 FPS. - */ + /** 20 ms per frame = 50 FPS. */ private static final long MS_PER_FRAME = 20; @Override diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java index 12117e3b17c5..a60eaf547066 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java @@ -25,12 +25,11 @@ package com.iluwatar.gameloop; /** - * Frame-based game loop is the easiest implementation. The loop always keeps spinning - * for the following three processes: processInput, update and render. The problem with - * it is you have no control over how fast the game runs. On a fast machine, that loop - * will spin so fast users won’t be able to see what’s going on. On a slow machine, the - * game will crawl. If you have a part of the game that’s content-heavy or does more AI - * or physics, the game will actually play slower there. + * Frame-based game loop is the easiest implementation. The loop always keeps spinning for the + * following three processes: processInput, update and render. The problem with it is you have no + * control over how fast the game runs. On a fast machine, that loop will spin so fast users won’t + * be able to see what’s going on. On a slow machine, the game will crawl. If you have a part of the + * game that’s content-heavy or does more AI or physics, the game will actually play slower there. */ public class FrameBasedGameLoop extends GameLoop { @@ -44,11 +43,10 @@ protected void processGameLoop() { } /** - * Each time when update() is invoked, a new frame is created, and the bullet will be - * moved 0.5f away from the current position. + * Each time when update() is invoked, a new frame is created, and the bullet will be moved 0.5f + * away from the current position. */ protected void update() { controller.moveBullet(0.5f); } - } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java index 1ed2304ecb80..872aa8e8ce50 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java @@ -25,16 +25,14 @@ package com.iluwatar.gameloop; /** - * Update and render objects in the game. Here we add a Bullet object to the - * game system to show how the game loop works. + * Update and render objects in the game. Here we add a Bullet object to the game system to show how + * the game loop works. */ public class GameController { protected final Bullet bullet; - /** - * Initialize Bullet instance. - */ + /** Initialize Bullet instance. */ public GameController() { bullet = new Bullet(); } @@ -57,6 +55,4 @@ public void moveBullet(float offset) { public float getBulletPosition() { return bullet.getPosition(); } - } - diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java index 6ad8c2c52c8b..0dcdb21f28ea 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java @@ -28,9 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Abstract class for GameLoop implementation class. - */ +/** Abstract class for GameLoop implementation class. */ public abstract class GameLoop { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -39,26 +37,20 @@ public abstract class GameLoop { protected final GameController controller; - /** - * Initialize game status to be stopped. - */ + /** Initialize game status to be stopped. */ protected GameLoop() { controller = new GameController(); status = GameStatus.STOPPED; } - /** - * Run game loop. - */ + /** Run game loop. */ public void run() { status = GameStatus.RUNNING; Thread gameThread = new Thread(this::processGameLoop); gameThread.start(); } - /** - * Stop game loop. - */ + /** Stop game loop. */ public void stop() { status = GameStatus.STOPPED; } @@ -73,9 +65,8 @@ public boolean isGameRunning() { } /** - * Handle any user input that has happened since the last call. In order to - * simulate the situation in real-life game, here we add a random time lag. - * The time lag ranges from 50 ms to 250 ms. + * Handle any user input that has happened since the last call. In order to simulate the situation + * in real-life game, here we add a random time lag. The time lag ranges from 50 ms to 250 ms. */ protected void processInput() { try { @@ -88,18 +79,12 @@ protected void processInput() { } } - /** - * Render game frames to screen. Here we print bullet position to simulate - * this process. - */ + /** Render game frames to screen. Here we print bullet position to simulate this process. */ protected void render() { var position = controller.getBulletPosition(); logger.info("Current bullet position: {}", position); } - /** - * execute game loop logic. - */ + /** execute game loop logic. */ protected abstract void processGameLoop(); - } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java index 8fb198c51b1f..316bb83d95d3 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java @@ -24,11 +24,8 @@ */ package com.iluwatar.gameloop; -/** - * Enum class for game status. - */ +/** Enum class for game status. */ public enum GameStatus { - - RUNNING, STOPPED - + RUNNING, + STOPPED } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java index 98644224a34d..9c7c0f348c9c 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java @@ -25,10 +25,9 @@ package com.iluwatar.gameloop; /** - * The variable-step game loop chooses a time step to advance based on how much - * real time passed since the last frame. The longer the frame takes, the bigger - * steps the game takes. It always keeps up with real time because it will take - * bigger and bigger steps to get there. + * The variable-step game loop chooses a time step to advance based on how much real time passed + * since the last frame. The longer the frame takes, the bigger steps the game takes. It always + * keeps up with real time because it will take bigger and bigger steps to get there. */ public class VariableStepGameLoop extends GameLoop { @@ -48,5 +47,4 @@ protected void processGameLoop() { protected void update(Long elapsedTime) { controller.moveBullet(0.5f * elapsedTime / 1000); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java index 72997b744dee..9565030373bc 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * App unit test class. - */ +/** App unit test class. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java index 8cd00f286929..fdcd8a1e3cd8 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java @@ -26,13 +26,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -/** - * FixedStepGameLoop unit test class. - */ +/** FixedStepGameLoop unit test class. */ class FixedStepGameLoopTest { private FixedStepGameLoop gameLoop; @@ -52,5 +50,4 @@ void testUpdate() { gameLoop.update(); assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java index fb93a3f0f6b2..ea2f80646dfe 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * FrameBasedGameLoop unit test class. - */ +/** FrameBasedGameLoop unit test class. */ class FrameBasedGameLoopTest { private FrameBasedGameLoop gameLoop; diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java index a8eef34318f4..c5855a88ea12 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java @@ -54,5 +54,4 @@ void testMoveBullet() { void testGetBulletPosition() { assertEquals(controller.bullet.getPosition(), controller.getBulletPosition(), 0); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java index 83c631db0f22..c7dddd372bd5 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java @@ -31,23 +31,21 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * GameLoop unit test class. - */ +/** GameLoop unit test class. */ class GameLoopTest { private GameLoop gameLoop; - /** - * Create mock implementation of GameLoop. - */ + /** Create mock implementation of GameLoop. */ @BeforeEach void setup() { - gameLoop = new GameLoop() { - @Override - protected void processGameLoop() { - } - }; + gameLoop = + new GameLoop() { + @Override + protected void processGameLoop() { + throw new UnsupportedOperationException("Not supported yet."); + } + }; } @AfterEach diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java index 4c04eec0a6cf..b528c145c9b0 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * VariableStepGameLoop unit test class. - */ +/** VariableStepGameLoop unit test class. */ class VariableStepGameLoopTest { private VariableStepGameLoop gameLoop; diff --git a/gateway/README.md b/gateway/README.md index 9307b5d1d0f9..0df5d1b74e85 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -1,24 +1,31 @@ --- -title: Gateway -category: Structural +title: "Gateway Pattern in Java: Simplifying External System Integration" +shortTitle: Gateway +description: "Discover the Gateway design pattern in Java, a powerful technique for integrating remote services and APIs. Learn how to encapsulate interactions and simplify your application architecture with practical examples and real-world use cases." +category: Integration language: en tag: -- Decoupling - + - API design + - Data access + - Decoupling + - Enterprise patterns --- -## Intent +## Also known as + +* Service Gateway -Provide a interface to access a set of external systems or functionalities. Gateway provides a simple uniform view of -external resources to the internals of an application. +## Intent of Gateway Design Pattern -## Explanation +The Gateway design pattern is a crucial concept in Java design patterns for simplifying API integration and interactions with remote services. It provides a unified and simplified interface to external systems, enhancing the maintainability and architecture of applications. By encapsulating these interactions, the Gateway pattern ensures loose coupling and promotes a more modular and scalable software design, making it essential for robust and efficient application development. + +## Detailed Explanation of Gateway Pattern with Real-World Examples Real-world example -> Gateway acts like a real front gate of a certain city. The people inside the city are called -> internal system, and different outside cities are called external services. The gateway is here -> to provide access for internal system to different external services. +> In real-world applications, companies often need to interact with multiple external systems. The Gateway design pattern provides a unified interface for such interactions, handling protocol translation and data transformation, thereby ensuring loose coupling between the internal and external components. ' +> +> Consider a logistics company that uses multiple third-party services for various operations, such as shipping, inventory management, and customer notifications. Each of these services has its own API with different protocols and data formats. To simplify the interaction, the company implements a Gateway design pattern. This gateway acts as a unified interface for all third-party service interactions, allowing the company's internal systems to communicate with these services seamlessly. The gateway handles the translation of protocols, data transformation, and routing of requests, ensuring that the internal systems remain decoupled from the specifics of each external service. This setup improves maintainability and scalability while providing a single point of control for external communications. In plain words @@ -26,124 +33,134 @@ In plain words Wikipedia says -> A server that acts as an API front-end, receives API requests, enforces throttling and security -> policies, passes requests to the back-end service and then passes the response back to the requester. +> A server that acts as an API front-end, receives API requests, enforces throttling and security policies, passes requests to the back-end service and then passes the response back to the requester. -**Programmatic Example** +## Programmatic Example of Gateway Pattern in Java -The main class in our example is the `ExternalService` that contains items. +First, we define a `Gateway` interface. This interface represents the contract for our external services. Each service that we want to interact with will implement this interface. ```java -class ExternalServiceA implements Gateway { - @Override - public void execute() throws Exception { - LOGGER.info("Executing Service A"); - // Simulate a time-consuming task - Thread.sleep(1000); - } +public interface Gateway { + void execute(); } +``` -/** - * ExternalServiceB is one of external services. - */ -class ExternalServiceB implements Gateway { - @Override - public void execute() throws Exception { - LOGGER.info("Executing Service B"); - // Simulate a time-consuming task - Thread.sleep(1000); - } +Next, we create our external services. These are the services that our application needs to interact with. Each service implements the `Gateway` interface and provides its own implementation of the `execute` method. + +```java +public class ExternalServiceA implements Gateway { + @Override + public void execute() { + // Implementation for ExternalServiceA + } } +``` -/** - * ExternalServiceC is one of external services. - */ -class ExternalServiceC implements Gateway { - @Override - public void execute() throws Exception { - LOGGER.info("Executing Service C"); - // Simulate a time-consuming task - Thread.sleep(1000); - } +```java +public class ExternalServiceB implements Gateway { + @Override + public void execute() { + // Implementation for ExternalServiceB + } +} +``` - public void error() throws Exception { - // Simulate an exception - throw new RuntimeException("Service C encountered an error"); - } +```java +public class ExternalServiceC implements Gateway { + @Override + public void execute() { + // Implementation for ExternalServiceC + } } ``` -To operate these external services, Here's the `App` class: +We then create a `GatewayFactory` class. This class maintains a registry of all available gateways. It provides methods to register a new gateway and to retrieve a gateway by its key. ```java -public class App { - /** - * Simulate an application calling external services. - */ - public static void main(String[] args) throws Exception { - GatewayFactory gatewayFactory = new GatewayFactory(); - - // Register different gateways - gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); - gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); - gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); - - // Use an executor service for asynchronous execution - Gateway serviceA = gatewayFactory.getGateway("ServiceA"); - Gateway serviceB = gatewayFactory.getGateway("ServiceB"); - Gateway serviceC = gatewayFactory.getGateway("ServiceC"); - - // Execute external services - try { - serviceA.execute(); - serviceB.execute(); - serviceC.execute(); - } catch (ThreadDeath e) { - LOGGER.info("Interrupted!" + e); - throw e; - } - } +public class GatewayFactory { + private Map gateways = new HashMap<>(); + + public void registerGateway(String key, Gateway gateway) { + gateways.put(key, gateway); + } + + public Gateway getGateway(String key) { + return gateways.get(key); + } } ``` -The `Gateway` interface is extremely simple. +Finally, we have our main application. The application uses the `GatewayFactory` to register and retrieve gateways. It then uses these gateways to interact with the external services. ```java -interface Gateway { - void execute() throws Exception; +public class App { + public static void main(String[] args) throws Exception { + GatewayFactory gatewayFactory = new GatewayFactory(); + + // Register different gateways + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + + // Use an executor service for execution + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + + // Execute external services + try { + serviceA.execute(); + serviceB.execute(); + serviceC.execute(); + } catch (ThreadDeath e) { + LOGGER.info("Interrupted!" + e); + throw e; + } + } } ``` -Program output: +Running the example produces the following output. -```java - Executing Service A - Executing Service B - Executing Service C ``` +09:24:44.030 [main] INFO com.iluwatar.gateway.ExternalServiceA -- Executing Service A +09:24:45.038 [main] INFO com.iluwatar.gateway.ExternalServiceB -- Executing Service B +09:24:46.043 [main] INFO com.iluwatar.gateway.ExternalServiceC -- Executing Service C +``` + +This example demonstrates how the Gateway design pattern can be used to simplify the interaction with multiple external services. Each service is encapsulated behind a common interface, and the application interacts with this interface rather than directly with the services. This reduces coupling and makes the application easier to maintain and extend. + +## When to Use the Gateway Pattern in Java + +Use the Gateway pattern when integrating with remote services or APIs. It is particularly beneficial in microservices architecture to manage communication through well-defined interfaces. -## Class diagram +## Real-World Applications of Gateway Pattern in Java -![alt text](./etc/gateway.urm.png "gateway") +* API Gateways in Microservices: Acts as an intermediary that processes incoming requests from clients, directing them to appropriate services within a microservices architecture. +* Database Gateways: Provides a unified interface to access data from various database systems, hiding the specifics of database querying and data retrieval. -## Applicability +## Benefits and Trade-offs of Gateway Pattern -Use the Gateway pattern +Benefits: -* To access an aggregate object's contents without exposing its internal representation. -* To integration with multiple external services or APIs. -* To provide a uniform interface for traversing different aggregate structures. +* The Gateway design pattern reduces complexity by abstracting the details of external APIs and services behind a simpler interface. +* Promotes loose coupling between the application and its dependencies on external systems. +* Makes the system easier to test and maintain. -## Tutorials +Trade-offs: -* [Pattern: API Gateway / Backends for Frontends](https://microservices.io/patterns/apigateway.html) +* Introduces an additional layer that could potentially impact performance. +* Requires careful design to avoid creating a monolithic gateway that becomes a bottleneck. -## Known uses +## Related Java Design Patterns -* [API Gateway](https://java-design-patterns.com/patterns/api-gateway/) -* [10 most common use cases of an API Gateway](https://apisix.apache.org/blog/2022/10/27/ten-use-cases-api-gateway/) +* [Facade](https://java-design-patterns.com/patterns/facade/): Similar to Gateway in abstracting complex subsystems, but Gateway specifically targets external or remote interfaces. +* [Adapter](https://java-design-patterns.com/patterns/adapter/): While both patterns provide a different interface to a subsystem, Gateway focuses more on networked data sources and services. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Often used together, as both can control and manage access to another object, but Gateway specifically deals with external services. +* [API Gateway](https://java-design-patterns.com/patterns/microservices-api-gateway/): Often considered a specialization of the Gateway pattern, it specifically manages API requests and routes them to the appropriate services within a backend system. -## Credits +## References and Credits -* [Gateway](https://martinfowler.com/articles/gateway-pattern.html) -* [What is the difference between Facade and Gateway design patterns?](https://stackoverflow.com/questions/4422211/what-is-the-difference-between-facade-and-gateway-design-patterns) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Gateway (Martin Fowler)](https://martinfowler.com/articles/gateway-pattern.html) diff --git a/gateway/pom.xml b/gateway/pom.xml index 88c15cd586ea..4fbde9cce9aa 100644 --- a/gateway/pom.xml +++ b/gateway/pom.xml @@ -36,13 +36,16 @@ gateway - org.junit.jupiter - junit-jupiter-engine - test + org.slf4j + slf4j-api - junit - junit + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/gateway/src/main/java/com/iluwatar/gateway/App.java b/gateway/src/main/java/com/iluwatar/gateway/App.java index 811cd4f7cc3b..5034748b20ec 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/App.java +++ b/gateway/src/main/java/com/iluwatar/gateway/App.java @@ -27,21 +27,21 @@ import lombok.extern.slf4j.Slf4j; /** - * the Gateway design pattern is a structural design pattern that provides a unified interface to a set of - * interfaces in a subsystem. It involves creating a Gateway interface that serves as a common entry point for - * interacting with various services, and concrete implementations of this interface for different external services. + * the Gateway design pattern is a structural design pattern that provides a unified interface to a + * set of interfaces in a subsystem. It involves creating a Gateway interface that serves as a + * common entry point for interacting with various services, and concrete implementations of this + * interface for different external services. * - *

In this example, GateFactory is the factory class, and it provides a method to create different kinds of external - * services. ExternalServiceA, B, and C are virtual implementations of the external services. Each service provides its - * own implementation of the execute() method. The Gateway interface is the common interface for all external services. - * The App class serves as the main entry point for the application implementing the Gateway design pattern. Through - * the Gateway interface, the App class could call each service with much less complexity. + *

In this example, GateFactory is the factory class, and it provides a method to create + * different kinds of external services. ExternalServiceA, B, and C are virtual implementations of + * the external services. Each service provides its own implementation of the execute() method. The + * Gateway interface is the common interface for all external services. The App class serves as the + * main entry point for the application implementing the Gateway design pattern. Through the Gateway + * interface, the App class could call each service with much less complexity. */ @Slf4j public class App { - /** - * Simulate an application calling external services. - */ + /** Simulate an application calling external services. */ public static void main(String[] args) throws Exception { GatewayFactory gatewayFactory = new GatewayFactory(); diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java index d6437e61197f..892dc5d7b755 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java @@ -24,13 +24,9 @@ */ package com.iluwatar.gateway; - import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -/** -* ExternalServiceA is one of external services. -*/ +/** ExternalServiceA is one of external services. */ @Slf4j class ExternalServiceA implements Gateway { @Override @@ -40,4 +36,3 @@ public void execute() throws Exception { Thread.sleep(1000); } } - diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java index 153cfc5b2d4c..dcd931ffb1f2 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java @@ -24,12 +24,9 @@ */ package com.iluwatar.gateway; - import lombok.extern.slf4j.Slf4j; -/** -* ExternalServiceB is one of external services. -*/ +/** ExternalServiceB is one of external services. */ @Slf4j class ExternalServiceB implements Gateway { @Override @@ -39,4 +36,3 @@ public void execute() throws Exception { Thread.sleep(1000); } } - diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java index 7b6d2c6fd324..b01d79e37968 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java @@ -24,12 +24,9 @@ */ package com.iluwatar.gateway; - import lombok.extern.slf4j.Slf4j; -/** -* ExternalServiceC is one of external services. -*/ +/** ExternalServiceC is one of external services. */ @Slf4j class ExternalServiceC implements Gateway { @Override @@ -39,7 +36,7 @@ public void execute() throws Exception { Thread.sleep(1000); } - public void error() throws Exception { + public void error() { // Simulate an exception throw new RuntimeException("Service C encountered an error"); } diff --git a/gateway/src/main/java/com/iluwatar/gateway/Gateway.java b/gateway/src/main/java/com/iluwatar/gateway/Gateway.java index 912f78b4737b..36627681c859 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/Gateway.java +++ b/gateway/src/main/java/com/iluwatar/gateway/Gateway.java @@ -1,8 +1,30 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.gateway; -/** - * Service interface. - */ +/** Service interface. */ interface Gateway { void execute() throws Exception; -} \ No newline at end of file +} diff --git a/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java b/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java index 0a76368a349c..2501e5f72183 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java +++ b/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java @@ -1,11 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.gateway; import java.util.HashMap; import java.util.Map; /** - * The "GatewayFactory" class is responsible for providing different external services in this Gateway design pattern - * example. It allows clients to register and retrieve specific gateways based on unique keys. + * The "GatewayFactory" class is responsible for providing different external services in this + * Gateway design pattern example. It allows clients to register and retrieve specific gateways + * based on unique keys. */ public class GatewayFactory { private Map gateways = new HashMap<>(); diff --git a/gateway/src/test/java/com/iluwatar/gateway/AppTest.java b/gateway/src/test/java/com/iluwatar/gateway/AppTest.java index ad75d517576e..0776926edd38 100644 --- a/gateway/src/test/java/com/iluwatar/gateway/AppTest.java +++ b/gateway/src/test/java/com/iluwatar/gateway/AppTest.java @@ -1,85 +1,111 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.gateway; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import java.util.concurrent.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class AppTest { - private GatewayFactory gatewayFactory; - private ExecutorService executorService; - @Before - public void setUp() { - gatewayFactory = new GatewayFactory(); - executorService = Executors.newFixedThreadPool(2); - gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); - gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); - gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); - } + private GatewayFactory gatewayFactory; + private ExecutorService executorService; + + @BeforeEach + void setUp() { + gatewayFactory = new GatewayFactory(); + executorService = Executors.newFixedThreadPool(2); + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + } - @Test - public void testServiceAExecution() throws InterruptedException, ExecutionException { - // Test Service A execution - Future serviceAFuture = executorService.submit(() -> { - try { + @Test + void testServiceAExecution() throws InterruptedException, ExecutionException { + // Test Service A execution + Future serviceAFuture = + executorService.submit( + () -> { + try { Gateway serviceA = gatewayFactory.getGateway("ServiceA"); serviceA.execute(); - } catch (Exception e) { + } catch (Exception e) { fail("Service A should not throw an exception."); - } - }); + } + }); - // Wait for Service A to complete - serviceAFuture.get(); - } + // Wait for Service A to complete + serviceAFuture.get(); + } - @Test - public void testServiceCExecutionWithException() throws InterruptedException, ExecutionException { - // Test Service B execution with an exception - Future serviceBFuture = executorService.submit(() -> { - try { + @Test + void testServiceCExecutionWithException() throws InterruptedException, ExecutionException { + // Test Service B execution with an exception + Future serviceBFuture = + executorService.submit( + () -> { + try { Gateway serviceB = gatewayFactory.getGateway("ServiceB"); serviceB.execute(); - } catch (Exception e) { + } catch (Exception e) { fail("Service B should not throw an exception."); - } - }); + } + }); - // Wait for Service B to complete - serviceBFuture.get(); - } + // Wait for Service B to complete + serviceBFuture.get(); + } - @Test - public void testServiceCExecution() throws InterruptedException, ExecutionException { - // Test Service C execution - Future serviceCFuture = executorService.submit(() -> { - try { + @Test + void testServiceCExecution() throws InterruptedException, ExecutionException { + // Test Service C execution + Future serviceCFuture = + executorService.submit( + () -> { + try { Gateway serviceC = gatewayFactory.getGateway("ServiceC"); serviceC.execute(); - } catch (Exception e) { + } catch (Exception e) { fail("Service C should not throw an exception."); - } - }); + } + }); - // Wait for Service C to complete - serviceCFuture.get(); - } + // Wait for Service C to complete + serviceCFuture.get(); + } - @Test - public void testServiceCError() { - try { - ExternalServiceC serviceC = (ExternalServiceC) gatewayFactory.getGateway("ServiceC"); - serviceC.error(); - fail("Service C should throw an exception."); - } catch (Exception e) { - assertEquals("Service C encountered an error", e.getMessage()); - } + @Test + void testServiceCError() { + try { + ExternalServiceC serviceC = (ExternalServiceC) gatewayFactory.getGateway("ServiceC"); + serviceC.error(); + fail("Service C should throw an exception."); + } catch (Exception e) { + assertEquals("Service C encountered an error", e.getMessage()); } + } } diff --git a/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java b/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java index f6dd640f31b9..f5e216cb2781 100644 --- a/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java +++ b/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java @@ -1,69 +1,92 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.gateway; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ServiceFactoryTest { - private GatewayFactory gatewayFactory; - private ExecutorService executorService; - @Before - public void setUp() { - gatewayFactory = new GatewayFactory(); - executorService = Executors.newFixedThreadPool(2); - gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); - gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); - gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); - } + private GatewayFactory gatewayFactory; + private ExecutorService executorService; - @Test - public void testGatewayFactoryRegistrationAndRetrieval() { - Gateway serviceA = gatewayFactory.getGateway("ServiceA"); - Gateway serviceB = gatewayFactory.getGateway("ServiceB"); - Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + @BeforeEach + void setUp() { + gatewayFactory = new GatewayFactory(); + executorService = Executors.newFixedThreadPool(2); + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + } - // Check if the retrieved instances match their expected types - assertTrue("ServiceA should be an instance of ExternalServiceA", serviceA instanceof ExternalServiceA); - assertTrue("ServiceB should be an instance of ExternalServiceB", serviceB instanceof ExternalServiceB); - assertTrue("ServiceC should be an instance of ExternalServiceC", serviceC instanceof ExternalServiceC); - } + @Test + void testGatewayFactoryRegistrationAndRetrieval() { + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); - @Test - public void testGatewayFactoryRegistrationWithNonExistingKey() { - Gateway nonExistingService = gatewayFactory.getGateway("NonExistingService"); - assertEquals(null, nonExistingService); - } + // Check if the retrieved instances match their expected types + assertTrue( + serviceA instanceof ExternalServiceA, "ServiceA should be an instance of ExternalServiceA"); + assertTrue( + serviceB instanceof ExternalServiceB, "ServiceB should be an instance of ExternalServiceB"); + assertTrue( + serviceC instanceof ExternalServiceC, "ServiceC should be an instance of ExternalServiceC"); + } - @Test - public void testGatewayFactoryConcurrency() throws InterruptedException { - int numThreads = 10; - CountDownLatch latch = new CountDownLatch(numThreads); - AtomicBoolean failed = new AtomicBoolean(false); + @Test + void testGatewayFactoryRegistrationWithNonExistingKey() { + Gateway nonExistingService = gatewayFactory.getGateway("NonExistingService"); + assertNull(nonExistingService); + } - for (int i = 0; i < numThreads; i++) { - executorService.submit(() -> { - try { - Gateway serviceA = gatewayFactory.getGateway("ServiceA"); - serviceA.execute(); - } catch (Exception e) { - failed.set(true); - } finally { - latch.countDown(); - } - }); - } + @Test + void testGatewayFactoryConcurrency() throws InterruptedException { + int numThreads = 10; + CountDownLatch latch = new CountDownLatch(numThreads); + AtomicBoolean failed = new AtomicBoolean(false); - latch.await(); - assertTrue("This should not fail", !failed.get()); + for (int i = 0; i < numThreads; i++) { + executorService.submit( + () -> { + try { + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + serviceA.execute(); + } catch (Exception e) { + failed.set(true); + } finally { + latch.countDown(); + } + }); } + + latch.await(); + assertFalse(failed.get(), "This should not fail"); + } } diff --git a/guarded-suspension/README.md b/guarded-suspension/README.md index 9dc39f2c23f5..f13e40684d06 100644 --- a/guarded-suspension/README.md +++ b/guarded-suspension/README.md @@ -1,26 +1,31 @@ --- -title: Guarded Suspension +title: "Guarded Suspension Pattern in Java: Ensuring Safe Concurrency in Critical Sections" +shortTitle: Guarded Suspension +description: "Learn about the Guarded Suspension design pattern in Java. Understand its implementation for efficient concurrency control, with real-world examples and code snippets." category: Concurrency language: en tag: - - Decoupling + - Asynchronous + - Decoupling + - Resource management + - Synchronization + - Thread management --- -## Intent -Use Guarded suspension pattern to handle a situation when you want to execute a method on object which is not in a proper state. +## Also known as -## Class diagram -![Guarded Suspension diagram](./etc/guarded-suspension.png) +* Conditional Block +* Suspended Execution -## Applicability -Use Guarded Suspension pattern when the developer knows that the method execution will be blocked for a finite period of time +## Intent of Guarded Suspension Design Pattern -## Explanation +The Guarded Suspension pattern is crucial in Java design patterns for managing operations that require both a lock and a condition to proceed. It optimizes concurrency control by allowing a thread to wait for the right condition efficiently. -Real world example +## Detailed Explanation of Guarded Suspension Pattern with Real-World Examples -> When we reserve a dining room online and arrive to find it unready, the manager has it cleaned while we wait. -> Once ready, we're escorted to the room. This process exemplifies the Guarded Suspension pattern. +Real-world example + +> A practical example of the Guarded Suspension pattern can be seen in a ride-sharing service. In this system, passengers wait for a car to become available, ensuring efficient resource use without continuous checking. When a passenger requests a ride, the request is suspended until a driver becomes available. The system monitors the availability of drivers, and once a driver is ready to take a new passenger, the system notifies the waiting passenger and resumes the ride request process. This ensures that passengers are not continuously checking for available drivers and that drivers are efficiently matched with passengers based on their availability. In plain words @@ -28,65 +33,126 @@ In plain words Wikipedia says -> In concurrent programming, Guarded Suspension manages operations requiring a lock -> and a precondition, delaying execution until the precondition is met. +> In concurrent programming, Guarded Suspension manages operations requiring a lock and a precondition, delaying execution until the precondition is met. + +## Programmatic Example of Guarded Suspension Pattern in Java -**Programmatic Example** +The `GuardedQueue` class in Java showcases concurrent programming using the Guarded Suspension pattern. It includes synchronized methods that manage thread management and synchronization, demonstrating how threads wait for the right conditions to execute. -The `GuardedQueue` class encapsulates a queue, and provides two synchronized methods, `get` and `put`. -The `get` method waits if the queue is empty, and the `put` method adds an item to the queue and notifies waiting threads: +The `GuardedQueue` class demonstrates the Guarded Suspension pattern by encapsulating a queue and providing two synchronized methods, `get` and `put`. The `get` method waits if the queue is empty, while the `put` method adds an item to the queue and notifies any waiting threads. ```java +@Slf4j public class GuardedQueue { -private final Queue sourceList = new LinkedList<>(); + private final Queue sourceList = new LinkedList<>(); + // Synchronized get method waits until the queue is not empty public synchronized Integer get() { while (sourceList.isEmpty()) { try { wait(); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } - return sourceList.peek(); + return sourceList.poll(); } + // Synchronized put method adds an item to the queue and notifies waiting threads public synchronized void put(Integer e) { sourceList.add(e); notify(); } } +``` +* `get`: This method waits while the `sourceList` is empty. When an item is added and `notify` is called, the waiting thread is awakened to continue execution and retrieve the item. +* `put`: This method adds an item to the queue and calls `notify` to wake up any waiting thread that is suspended in the `get` method. + +Here is the `App` class driving the example: + +```java public class App { -public static void main(String[] args) { -GuardedQueue guardedQueue = new GuardedQueue(); -ExecutorService executorService = Executors.newFixedThreadPool(3); + public static void main(String[] args) { + GuardedQueue guardedQueue = new GuardedQueue(); + ExecutorService executorService = Executors.newFixedThreadPool(3); - // Here we create the first thread which is supposed to get from guardedQueue - executorService.execute(guardedQueue::get); + // Thread to get from the guardedQueue + executorService.execute(() -> { + Integer item = guardedQueue.get(); + LOGGER.info("Retrieved: " + item); + }); + // Simulating some delay before putting an item try { Thread.sleep(2000); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } - // Here we create the second thread which is supposed to put to guardedQueue + // Thread to put an item into the guardedQueue executorService.execute(() -> { guardedQueue.put(20); + LOGGER.info("Item added to queue"); }); executorService.shutdown(); try { executorService.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } } ``` +* `ExecutorService` is used to manage a pool of threads. +* The first thread attempts to retrieve an item from the `GuardedQueue` and waits since the queue is initially empty. +* After a 2-second delay, the second thread adds an item to the queue, waking up the first thread. +* The first thread then retrieves the item and logs it. + +Execution yields: + +``` +19:22:58.984 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- waiting +19:23:00.993 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- putting +19:23:00.994 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- notifying +19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- getting +19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- Retrieved: 20 +``` + +* The log output shows the sequence of events: the first thread waits, the second thread puts an item, and the first thread then retrieves the item. This demonstrates the Guarded Suspension pattern in action. + +## When to Use the Guarded Suspension Pattern in Java + +This pattern is ideal for scenarios requiring a thread to wait for specific conditions, promoting efficient concurrency control and reducing busy waiting overhead. + +## Real-World Applications of Guarded Suspension Pattern in Java + +* Network servers waiting for client requests. +* Producer-consumer scenarios where the consumer must wait for the producer to provide data. +* Event-driven applications where actions are triggered only after specific events have occurred. + +## Benefits and Trade-offs of Guarded Suspension Pattern + +Benefits: + +* Reduces CPU consumption by preventing busy waiting. +* Increases system responsiveness by synchronizing actions to the availability of necessary conditions or resources. + +Trade-offs: + +* Complexity in implementation, especially when multiple conditions need to be managed. +* Potential for deadlocks if not carefully managed. + +## Related Java Design Patterns + +* [Monitor](https://java-design-patterns.com/patterns/monitor/): Both patterns manage the synchronization of threads based on conditions. Guarded Suspension specifically deals with suspending threads until conditions are met, while Monitor Object encapsulates condition and mutual exclusion handling. +* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Often implemented using Guarded Suspension to handle waiting consumers and producers efficiently. +* [Balking](https://java-design-patterns.com/patterns/balking/): Similar to Guarded Suspension, Balking is used when a thread checks a condition and only proceeds if the condition is favorable; if not, it immediately returns or bails out. This pattern complements Guarded Suspension by managing actions based on immediate condition checks without waiting. -## Related patterns +## References and Credits -* Balking +* [Concurrent Programming in Java : Design Principles and Patterns](https://amzn.to/4dIBqxL) +* [Java Concurrency in Practice](https://amzn.to/3JxnXek) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/49Ke1c9) diff --git a/guarded-suspension/pom.xml b/guarded-suspension/pom.xml index 2e4fdec921e0..099bfe1e4c70 100644 --- a/guarded-suspension/pom.xml +++ b/guarded-suspension/pom.xml @@ -35,6 +35,14 @@ jar guarded-suspension + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java index 289d28c15081..5c6b7e0547dc 100644 --- a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java @@ -26,18 +26,19 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; /** - * Created by robertt240 on 1/26/17. - * - *

Guarded-suspension is a concurrent design pattern for handling situation when to execute some - * action we need condition to be satisfied. - * - *

Implementation is based on GuardedQueue, which has two methods: get and put, the condition is - * that we cannot get from empty queue so when thread attempt to break the condition we invoke - * Object's wait method on him and when other thread put an element to the queue he notify the - * waiting one that now he can get from queue. + * Guarded-suspension is a concurrent design pattern for handling situation when to execute some + * action we need condition to be satisfied. The implementation utilizes a GuardedQueue, which + * features two primary methods: `get` and `put`. The key condition governing these operations is + * that elements cannot be retrieved (`get`) from an empty queue. When a thread attempts to retrieve + * an element under this condition, it triggers the invocation of the `wait` method from the Object + * class, causing the thread to pause. Conversely, when an element is added (`put`) to the queue by + * another thread, it invokes the `notify` method. This notifies the waiting thread that it can now + * successfully retrieve an element from the queue. */ +@Slf4j public class App { /** * Example pattern execution. @@ -48,7 +49,7 @@ public static void main(String[] args) { var guardedQueue = new GuardedQueue(); var executorService = Executors.newFixedThreadPool(3); - //here we create first thread which is supposed to get from guardedQueue + // here we create first thread which is supposed to get from guardedQueue executorService.execute(guardedQueue::get); // here we wait two seconds to show that the thread which is trying @@ -56,7 +57,7 @@ public static void main(String[] args) { try { Thread.sleep(2000); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } // now we execute second thread which will put number to guardedQueue // and notify first thread that it could get @@ -65,8 +66,7 @@ public static void main(String[] args) { try { executorService.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } - } diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java index 1238762bbeae..346e7193a6a5 100644 --- a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java @@ -33,7 +33,8 @@ * used to handle a situation when you want to execute a method on an object which is not in a * proper state. * - * @see http://java-design-patterns.com/patterns/guarded-suspension/ + * @see http://java-design-patterns.com/patterns/guarded-suspension/ */ @Slf4j public class GuardedQueue { @@ -44,7 +45,7 @@ public GuardedQueue() { } /** - * Get the last element of the queue is exists. + * Get the last element of the queue if exists. * * @return last element of a queue if queue is not empty */ @@ -54,7 +55,7 @@ public synchronized Integer get() { LOGGER.info("waiting"); wait(); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } LOGGER.info("getting"); diff --git a/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java index 33e9fc6b7391..edbcf514586d 100644 --- a/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java +++ b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java @@ -28,11 +28,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; -/** - * Test for Guarded Queue - */ +/** Test for Guarded Queue. */ +@Slf4j class GuardedQueueTest { private volatile Integer value; @@ -46,7 +46,7 @@ void testGet() { try { executorService.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } assertEquals(Integer.valueOf(10), value); } @@ -57,5 +57,4 @@ void testPut() { g.put(12); assertEquals(Integer.valueOf(12), g.get()); } - } diff --git a/half-sync-half-async/README.md b/half-sync-half-async/README.md index 3e0943599c33..784197023c59 100644 --- a/half-sync-half-async/README.md +++ b/half-sync-half-async/README.md @@ -1,35 +1,167 @@ --- -title: Half-Sync/Half-Async +title: "Half-Sync/Half-Async Pattern in Java: Enhancing System Performance with Dual Processing" +shortTitle: Half-Sync/Half-Async +description: "Learn how the Half-Sync/Half-Async design pattern in Java improves concurrency and system efficiency by decoupling asynchronous and synchronous processing. Explore real-world examples, programmatic implementations, and key use cases." category: Concurrency language: en tag: - - Performance + - Asynchronous + - Decoupling + - Synchronization + - Thread management --- -## Intent -The Half-Sync/Half-Async pattern decouples synchronous I/O from -asynchronous I/O in a system to simplify concurrent programming effort without -degrading execution efficiency. +## Also known as -## Class diagram -![Half-Sync/Half-Async class diagram](./etc/half-sync-half-async.png) +* Async-Sync Bridge +* Half-Synchronous/Half-Asynchronous -## Applicability -Use Half-Sync/Half-Async pattern when +## Intent of Half-Sync/Half-Async Design Pattern -* a system possesses following characteristics: - * the system must perform tasks in response to external events that occur asynchronously, like hardware interrupts in OS - * it is inefficient to dedicate separate thread of control to perform synchronous I/O for each external source of event - * the higher level tasks in the system can be simplified significantly if I/O is performed synchronously. -* one or more tasks in a system must run in a single thread of control, while other tasks may benefit from multi-threading. +The Half-Sync/Half-Async pattern in Java aims to decouple asynchronous and synchronous processing in concurrent systems, enhancing efficiency and performance. This pattern is particularly useful for managing complex concurrent operations in software systems. -## Real world examples +## Detailed Explanation of Half-Sync/Half-Async Pattern with Real-World Examples -* [BSD Unix networking subsystem](https://www.dre.vanderbilt.edu/~schmidt/PDF/PLoP-95.pdf) -* [Real Time CORBA](http://www.omg.org/news/meetings/workshops/presentations/realtime2001/4-3_Pyarali_thread-pool.pdf) -* [Android AsyncTask framework](https://developer.android.com/reference/android/os/AsyncTask) +Real-world example -## Credits +> Imagine a busy restaurant kitchen where order taking is asynchronous, allowing waiters to keep working while chefs cook each dish synchronously. Similarly, the Half-Sync/Half-Async pattern handles multiple asynchronous tasks and synchronous processing in Java applications efficiently. Meanwhile, the cooking (synchronous part) follows a specific sequence and requires waiting for each dish to be prepared before starting the next. This setup enables the restaurant to handle multiple customer orders efficiently, while ensuring each dish is cooked with the required attention and timing, much like the Half-Sync/Half-Async pattern manages asynchronous tasks and synchronous processing in software systems. -* [Douglas C. Schmidt and Charles D. Cranor - Half Sync/Half Async](https://www.dre.vanderbilt.edu/~schmidt/PDF/PLoP-95.pdf) -* [Pattern Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://www.amazon.com/gp/product/0471606952/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0471606952&linkCode=as2&tag=javadesignpat-20&linkId=889e4af72dca8261129bf14935e0f8dc) +In plain words + +> The Half-Sync/Half-Async pattern separates operations into asynchronous tasks that handle events without waiting, and synchronous tasks that process these events in an orderly and blocking manner. + +Wikipedia says + +> The Half-Sync/Half-Async design pattern is used to solve situations where one part of the application runs synchronously while another runs asynchronously, and the two modules need to communicate with each other. + +## Programmatic Example of Half-Sync/Half-Async Pattern in Java + +The Half-Sync/Half-Async design pattern is a concurrency pattern that separates synchronous and asynchronous processing in a system, simplifying the programming model without affecting performance. It's particularly useful in scenarios where you have a mix of short, mid, and long duration tasks. + +In the provided Java implementation, we can see an example of the Half-Sync/Half-Async pattern in the `App`, `AsynchronousService`, and `ArithmeticSumTask` classes. + +The `App` class is the entry point of the application. It creates an instance of `AsynchronousService` and uses it to handle various tasks asynchronously. + +```java +public class App { + + public static void main(String[] args) { + var service = new AsynchronousService(new LinkedBlockingQueue<>()); + service.execute(new ArithmeticSumTask(1000)); + service.execute(new ArithmeticSumTask(500)); + service.execute(new ArithmeticSumTask(2000)); + service.execute(new ArithmeticSumTask(1)); + service.close(); + } +} +``` + +The `AsynchronousService` class is the asynchronous part of the system. It manages a queue of tasks and processes them in a separate thread. + +```java +public class AsynchronousService { + // Implementation details... +} +``` + +The `ArithmeticSumTask` class represents a task that can be processed asynchronously. It implements the `AsyncTask` interface, which defines methods for pre-processing, post-processing, and error handling. + +```java +static class ArithmeticSumTask implements AsyncTask { + private final long numberOfElements; + + public ArithmeticSumTask(long numberOfElements) { + this.numberOfElements = numberOfElements; + } + + @Override + public Long call() throws Exception { + return ap(numberOfElements); + } + + @Override + public void onPreCall() { + if (numberOfElements < 0) { + throw new IllegalArgumentException("n is less than 0"); + } + } + + @Override + public void onPostCall(Long result) { + LOGGER.info(result.toString()); + } + + @Override + public void onError(Throwable throwable) { + throw new IllegalStateException("Should not occur"); + } +} +``` + +This is the `main` function in the `App` class. + +```java +public static void main(String[] args) { + + var service = new AsynchronousService(new LinkedBlockingQueue<>()); + + service.execute(new ArithmeticSumTask(1000)); + + service.execute(new ArithmeticSumTask(500)); + service.execute(new ArithmeticSumTask(2000)); + service.execute(new ArithmeticSumTask(1)); + + service.close(); +} +``` + +In this example, the `App` class enqueues tasks to the `AsynchronousService`, which processes them asynchronously. The `ArithmeticSumTask` class defines the task to be processed, including pre-processing, the actual processing, and post-processing steps. + +Running the code produces: + +``` +10:56:33.922 [pool-1-thread-4] INFO com.iluwatar.halfsynchalfasync.App -- 1 +10:56:34.425 [pool-1-thread-2] INFO com.iluwatar.halfsynchalfasync.App -- 125250 +10:56:34.925 [pool-1-thread-1] INFO com.iluwatar.halfsynchalfasync.App -- 500500 +10:56:35.925 [pool-1-thread-3] INFO com.iluwatar.halfsynchalfasync.App -- 2001000 +``` + +This is a basic example of the Half-Sync/Half-Async pattern, where tasks are enqueued and processed asynchronously, while the main thread continues to handle other tasks. + +## When to Use the Half-Sync/Half-Async Pattern in Java + +Use the Half-Sync/Half-Async pattern in scenarios where: + +* High performance and efficient concurrency are crucial, such as in Java's standard libraries and network servers managing concurrent connections. +* The system needs to effectively utilize multicore architectures to balance tasks between asynchronous and synchronous processing. +* Decoupling of asynchronous tasks from synchronous processing is necessary to simplify the design and implementation. + +## Real-World Applications of Half-Sync/Half-Async Pattern in Java + +* The Half-Sync/Half-Async pattern is utilized in various frameworks and systems, including BSD Unix networking, Real-Time CORBA, and Android's AsyncTask framework. +* Java's standard libraries utilize this pattern with thread pools and execution queues in the concurrency utilities (e.g., java.util.concurrent). +* Network servers handling concurrent connections where IO operations are handled asynchronously and processing of requests is done synchronously. + +## Benefits and Trade-offs of Half-Sync/Half-Async Pattern + +Benefits: + +* This pattern improves system responsiveness and throughput by isolating blocking operations from non-blocking ones, making it a valuable design pattern in Java concurrency. +* Simplifies programming model by isolating asynchronous and synchronous processing layers. + +Trade-offs: + +* Adds complexity in managing two different processing modes. +* Requires careful design to avoid bottlenecks between the synchronous and asynchronous parts. + +## Related Java Design Patterns + +* [Leader/Followers](https://java-design-patterns.com/patterns/leader-followers/): Both patterns manage thread assignments and concurrency, but Leader/Followers uses a single thread to handle all I/O events, dispatching work to others. +* [Producer/Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Can be integrated with Half-Sync/Half-Async to manage work queues between the async and sync parts. +* [Reactor](https://java-design-patterns.com/patterns/reactor/): Often used with Half-Sync/Half-Async to handle multiple service requests delivered to a service handler without blocking the handler. + +## References and Credits + +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) +* [Half-Sync/Half-Async (Douglas C. Schmidt and Charles D. Cranor)](https://www.dre.vanderbilt.edu/~schmidt/PDF/PLoP-95.pdf) diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml index 28cbf20239e0..33d6803db3da 100644 --- a/half-sync-half-async/pom.xml +++ b/half-sync-half-async/pom.xml @@ -34,6 +34,14 @@ half-sync-half-async + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java index e9074b7286e7..1a8baafbc1ab 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java @@ -32,7 +32,7 @@ * AsyncTask} and {@link AsynchronousService}. * *

PROBLEM
- * A concurrent system have a mixture of short duration, mid duration and long duration tasks. Mid + * A concurrent system have a mixture of short duration, mid-duration and long duration tasks. Mid * or long duration tasks should be performed asynchronously to meet quality of service * requirements. * @@ -43,13 +43,14 @@ * *

APPLICABILITY
* UNIX network subsystems - In operating systems network operations are carried out asynchronously - * with help of hardware level interrupts.
CORBA - At the asynchronous layer one thread is - * associated with each socket that is connected to the client. Thread blocks waiting for CORBA - * requests from the client. On receiving request it is inserted in the queuing layer which is then - * picked up by synchronous layer which processes the request and sends response back to the - * client.
Android AsyncTask framework - Framework provides a way to execute long running - * blocking calls, such as downloading a file, in background threads so that the UI thread remains - * free to respond to user inputs.
+ * with help of hardware level interrupts.
+ * CORBA - At the asynchronous layer one thread is associated with each socket that is connected to + * the client. Thread blocks waiting for CORBA requests from the client. On receiving request it is + * inserted in the queuing layer which is then picked up by synchronous layer which processes the + * request and sends response back to the client.
+ * Android AsyncTask framework - Framework provides a way to execute long-running blocking calls, + * such as downloading a file, in background threads so that the UI thread remains free to respond + * to user inputs.
* *

IMPLEMENTATION
* The main method creates an asynchronous service which does not block the main thread while the @@ -90,9 +91,7 @@ public static void main(String[] args) { service.close(); } - /** - * ArithmeticSumTask. - */ + /** ArithmeticSumTask. */ static class ArithmeticSumTask implements AsyncTask { private final long numberOfElements; @@ -101,18 +100,18 @@ public ArithmeticSumTask(long numberOfElements) { } /* - * This is the long running task that is performed in background. In our example the long - * running task is calculating arithmetic sum with artificial delay. + * This is the long-running task that is performed in background. In our example the long-running + * task is calculating arithmetic sum with artificial delay. */ @Override - public Long call() throws Exception { + public Long call() { return ap(numberOfElements); } /* * This will be called in context of the main thread where some validations can be done * regarding the inputs. Such as it must be greater than 0. It's a small computation which can - * be performed in main thread. If we did validated the input in background thread then we pay + * be performed in main thread. If we did validate the input in background thread then we pay * the cost of context switching which is much more than validating it in main thread. */ @Override diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java index 75513464b7b5..ca31b0d8b0a1 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java @@ -43,7 +43,7 @@ public class AsynchronousService { /* * This represents the queuing layer as well as synchronous layer of the pattern. The thread pool - * contains worker threads which execute the tasks in blocking/synchronous manner. Long running + * contains worker threads which execute the tasks in blocking/synchronous manner. Long-running * tasks should be performed in the background which does not affect the performance of main * thread. */ @@ -58,7 +58,6 @@ public AsynchronousService(BlockingQueue workQueue) { service = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, workQueue); } - /** * A non-blocking method which performs the task provided in background and returns immediately. * @@ -79,30 +78,29 @@ public void execute(final AsyncTask task) { return; } - service.submit(new FutureTask(task) { - @Override - protected void done() { - super.done(); - try { - /* - * called in context of background thread. There is other variant possible where result is - * posted back and sits in the queue of caller thread which then picks it up for - * processing. An example of such a system is Android OS, where the UI elements can only - * be updated using UI thread. So result must be posted back in UI thread. - */ - task.onPostCall(get()); - } catch (InterruptedException e) { - // should not occur - } catch (ExecutionException e) { - task.onError(e.getCause()); - } - } - }); + service.submit( + new FutureTask<>(task) { + @Override + protected void done() { + super.done(); + try { + /* + * called in context of background thread. There is other variant possible where result is + * posted back and sits in the queue of caller thread which then picks it up for + * processing. An example of such a system is Android OS, where the UI elements can only + * be updated using UI thread. So result must be posted back in UI thread. + */ + task.onPostCall(get()); + } catch (InterruptedException e) { + // should not occur + } catch (ExecutionException e) { + task.onError(e.getCause()); + } + } + }); } - /** - * Stops the pool of workers. This is a blocking call to wait for all tasks to be completed. - */ + /** Stops the pool of workers. This is a blocking call to wait for all tasks to be completed. */ public void close() { service.shutdown(); try { diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java index d445a155c65b..a871c81cb26b 100644 --- a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.halfsynchalfasync; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java index 4574c3b6ac2d..1360f7daa557 100644 --- a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java @@ -39,11 +39,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/12/15 - 11:15 PM - * - * @author Jeroen Meulemeester - */ +/** AsynchronousServiceTest */ class AsynchronousServiceTest { private AsynchronousService service; private AsyncTask task; @@ -100,5 +96,4 @@ void testPreCallException() { verifyNoMoreInteractions(task); } - -} \ No newline at end of file +} diff --git a/health-check/README.md b/health-check/README.md index 6543c092ea3c..23c7f74f7675 100644 --- a/health-check/README.md +++ b/health-check/README.md @@ -1,447 +1,115 @@ --- -title: Health Check Pattern +title: "Health Check Pattern in Java: Monitoring System Health for Optimal Operation" +shortTitle: Health Check +description: "Learn about the Health Check pattern in Java, a vital design for monitoring system health and ensuring reliability in microservices and distributed systems. Discover examples, applications, and benefits." category: Behavioral language: en tag: - - Performance + - Fault tolerance - Microservices - - Resilience - - Observability + - Monitoring + - System health --- -# Health Check Pattern - ## Also known as -Health Monitoring, Service Health Check -## Intent -To ensure the stability and resilience of services in a microservices architecture by providing a way to monitor and diagnose their health. +* Health Monitoring +* Service Health Check -## Explanation -In microservices architecture, it's critical to continuously check the health of individual services. The Health Check Pattern is a mechanism for microservices to expose their health status. This pattern is implemented by including a health check endpoint in microservices that returns the service's current state. This is vital for maintaining system resilience and operational readiness. +## Intent of Health Check Design Pattern -For more information, see the Health Check API pattern on [Microservices.io](https://microservices.io/patterns/observability/health-check-api.html). +The Health Check pattern in Java is designed to proactively monitor the health of individual software components or services, allowing for quick identification and remediation of issues that may affect overall system functionality in microservices architectures. +## Detailed Explanation of Health Check Pattern with Real-World Examples -## Real World Example -In a cloud-native environment, such as Kubernetes or AWS ECS, health checks are used to ensure that containers are running correctly. If a service fails its health check, it can be automatically restarted or replaced, ensuring high availability and resilience. +Real-world example -## In Plain Words -The Health Check Pattern is like a regular doctor's visit for services in a microservices architecture. It helps in early detection of issues and ensures that services are healthy and available. +> Consider a hospital where patient monitoring systems are used to ensure the health of patients. Each monitoring device periodically checks the vital signs of a patient and reports back to a central system. Similarly, in Java-based software systems, a Health Check pattern allows each service to periodically report its status to a central monitoring system. If any device detects abnormal vital signs, it triggers an alert for immediate medical attention. Similarly, in software, a Health Check pattern allows each service to periodically report its status to a central monitoring system. If a service is found to be unhealthy, the system can take corrective actions such as alerting administrators, restarting the service, or redirecting traffic to healthy instances, thereby ensuring continuous and reliable operation. +In plain words -## Programmatic Example -Here, provided detailed examples of health check implementations in a microservices environment. +> The Health Check Pattern is like a regular doctor's visit for services in a microservices architecture. It helps in early detection of issues and ensures that services are healthy and available. -### AsynchronousHealthChecker -An asynchronous health checker component that executes health checks in a separate thread. -```java - /** - * Performs a health check asynchronously using the provided health check logic with a specified - * timeout. - * - * @param healthCheck the health check logic supplied as a {@code Supplier} - * @param timeoutInSeconds the maximum time to wait for the health check to complete, in seconds - * @return a {@code CompletableFuture} object that represents the result of the health - * check - */ - public CompletableFuture performCheck( - Supplier healthCheck, long timeoutInSeconds) { - CompletableFuture future = - CompletableFuture.supplyAsync(healthCheck, healthCheckExecutor); - - // Schedule a task to enforce the timeout - healthCheckExecutor.schedule( - () -> { - if (!future.isDone()) { - LOGGER.error(HEALTH_CHECK_TIMEOUT_MESSAGE); - future.completeExceptionally(new TimeoutException(HEALTH_CHECK_TIMEOUT_MESSAGE)); - } - }, - timeoutInSeconds, - TimeUnit.SECONDS); - - return future.handle( - (result, throwable) -> { - if (throwable != null) { - LOGGER.error(HEALTH_CHECK_FAILED_MESSAGE, throwable); - // Check if the throwable is a TimeoutException or caused by a TimeoutException - Throwable rootCause = - throwable instanceof CompletionException ? throwable.getCause() : throwable; - if (!(rootCause instanceof TimeoutException)) { - LOGGER.error(HEALTH_CHECK_FAILED_MESSAGE, rootCause); - return Health.down().withException(rootCause).build(); - } else { - LOGGER.error(HEALTH_CHECK_TIMEOUT_MESSAGE, rootCause); - // If it is a TimeoutException, rethrow it wrapped in a CompletionException - throw new CompletionException(rootCause); - } - } else { - return result; - } - }); - } -``` +## Programmatic Example of Health Check Pattern in Java -### CpuHealthIndicator -A health indicator that checks the health of the system's CPU. -```java - /** - * Checks the health of the system's CPU and returns a health indicator object. - * - * @return a health indicator object - */ - @Override - public Health health() { - if (!(osBean instanceof com.sun.management.OperatingSystemMXBean sunOsBean)) { - LOGGER.error("Unsupported operating system MXBean: {}", osBean.getClass().getName()); - return Health.unknown() - .withDetail(ERROR_MESSAGE, "Unsupported operating system MXBean") - .build(); - } - - double systemCpuLoad = sunOsBean.getCpuLoad() * 100; - double processCpuLoad = sunOsBean.getProcessCpuLoad() * 100; - int availableProcessors = sunOsBean.getAvailableProcessors(); - double loadAverage = sunOsBean.getSystemLoadAverage(); - - Map details = new HashMap<>(); - details.put("timestamp", Instant.now()); - details.put("systemCpuLoad", String.format("%.2f%%", systemCpuLoad)); - details.put("processCpuLoad", String.format("%.2f%%", processCpuLoad)); - details.put("availableProcessors", availableProcessors); - details.put("loadAverage", loadAverage); - - if (systemCpuLoad > systemCpuLoadThreshold) { - LOGGER.error(HIGH_SYSTEM_CPU_LOAD_MESSAGE, systemCpuLoad); - return Health.down() - .withDetails(details) - .withDetail(ERROR_MESSAGE, HIGH_SYSTEM_CPU_LOAD_MESSAGE_WITHOUT_PARAM) - .build(); - } else if (processCpuLoad > processCpuLoadThreshold) { - LOGGER.error(HIGH_PROCESS_CPU_LOAD_MESSAGE, processCpuLoad); - return Health.down() - .withDetails(details) - .withDetail(ERROR_MESSAGE, HIGH_PROCESS_CPU_LOAD_MESSAGE_WITHOUT_PARAM) - .build(); - } else if (loadAverage > (availableProcessors * loadAverageThreshold)) { - LOGGER.error(HIGH_LOAD_AVERAGE_MESSAGE, loadAverage); - return Health.up() - .withDetails(details) - .withDetail(ERROR_MESSAGE, HIGH_LOAD_AVERAGE_MESSAGE_WITHOUT_PARAM) - .build(); - } else { - return Health.up().withDetails(details).build(); - } - } - -``` +The Health Check design pattern is particularly useful in distributed systems where the health of individual components can affect the overall health of the system. Using Spring Boot Actuator, developers can easily implement health checks in Java applications. +In the provided code, we can see an example of the Health Check pattern in the `App` class and the use of Spring Boot's Actuator. - -### CustomHealthIndicator -A custom health indicator that periodically checks the health of a database and caches the result. It leverages an asynchronous health checker to perform the health checks. - -- `AsynchronousHealthChecker`: A component for performing health checks asynchronously. -- `CacheManager`: Manages caching of health check results. -- `HealthCheckRepository`: A repository for querying health-related data from the database. +The `App` class is the entry point of the application. It starts a Spring Boot application which has health check capabilities built-in through the use of Spring Boot Actuator. ```java -/** - * Perform a health check and cache the result. - * - * @return the health status of the application - * @throws HealthCheckInterruptedException if the health check is interrupted - */ -@Override -@Cacheable(value = "health-check", unless = "#result.status == 'DOWN'") -public Health health() { - LOGGER.info("Performing health check"); - CompletableFuture healthFuture = - healthChecker.performCheck(this::check, timeoutInSeconds); - try { - return healthFuture.get(timeoutInSeconds, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOGGER.error("Health check interrupted", e); - throw new HealthCheckInterruptedException(e); - } catch (Exception e) { - LOGGER.error("Health check failed", e); - return Health.down(e).build(); - } -} - -/** - * Checks the health of the database by querying for a simple constant value expected from the - * database. - * - * @return Health indicating UP if the database returns the constant correctly, otherwise DOWN. - */ -private Health check() { - Integer result = healthCheckRepository.checkHealth(); - boolean databaseIsUp = result != null && result == 1; - LOGGER.info("Health check result: {}", databaseIsUp); - return databaseIsUp - ? Health.up().withDetail("database", "reachable").build() - : Health.down().withDetail("database", "unreachable").build(); -} - -/** - * Evicts all entries from the health check cache. This is scheduled to run at a fixed rate - * defined in the application properties. - */ -@Scheduled(fixedRateString = "${health.check.cache.evict.interval:60000}") -public void evictHealthCache() { - LOGGER.info("Evicting health check cache"); - try { - Cache healthCheckCache = cacheManager.getCache("health-check"); - LOGGER.info("Health check cache: {}", healthCheckCache); - if (healthCheckCache != null) { - healthCheckCache.clear(); - } - } catch (Exception e) { - LOGGER.error("Failed to evict health check cache", e); - } +@EnableCaching +@EnableScheduling +@SpringBootApplication +public class App { + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } } - ``` -### DatabaseTransactionHealthIndicator -A health indicator that checks the health of database transactions by attempting to perform a test transaction using a retry mechanism. +Spring Boot Actuator provides several built-in health checks through its `/actuator/health` endpoint. For example, it can check the status of the database connection, disk space, and other important system parameters. You can also add custom health checks as needed. -- **HealthCheckRepository**: A repository for performing health checks on the database. -- **AsynchronousHealthChecker**: An asynchronous health checker used to execute health checks in a separate thread. -- **RetryTemplate**: A retry template used to retry the test transaction if it fails due to a transient error. +To add a custom health check, you can create a class that implements the `HealthIndicator` interface and override its `health` method. Here is an example: ```java -/** - * Performs a health check by attempting to perform a test transaction with retry support. - * - * @return the health status of the database transactions - */ -@Override -public Health health() { - LOGGER.info("Calling performCheck with timeout {}", timeoutInSeconds); - Supplier dbTransactionCheck = - () -> { - try { - healthCheckRepository.performTestTransaction(); - return Health.up().build(); - } catch (Exception e) { - LOGGER.error("Database transaction health check failed", e); - return Health.down(e).build(); - } - }; - try { - return asynchronousHealthChecker.performCheck(dbTransactionCheck, timeoutInSeconds).get(); - } catch (InterruptedException | ExecutionException e) { - LOGGER.error("Database transaction health check timed out or was interrupted", e); - Thread.currentThread().interrupt(); - return Health.down(e).build(); +@Component +public class CustomHealthCheck implements HealthIndicator { + @Override + public Health health() { + int errorCode = check(); // perform some specific health check + if (errorCode != 0) { + return Health.down() + .withDetail("Error Code", errorCode).build(); + } + return Health.up().build(); + } + + public int check() { + // Our logic to check health + return 0; } } ``` +In this example, the `check` method contains the logic for the health check. If the health check fails, it returns a non-zero error code, and the `health` method builds a `DOWN` health status with the error code. If the health check passes, it returns a `UP` health status. -### GarbageCollectionHealthIndicator -A custom health indicator that checks the garbage collection status of the application and reports the health status accordingly. -```java - /** - * Performs a health check by gathering garbage collection metrics and evaluating the overall - * health of the garbage collection system. - * - * @return a {@link Health} object representing the health status of the garbage collection system - */ - @Override - public Health health() { - List gcBeans = getGarbageCollectorMxBeans(); - List memoryPoolMxBeans = getMemoryPoolMxBeans(); - Map> gcDetails = new HashMap<>(); - - for (GarbageCollectorMXBean gcBean : gcBeans) { - Map collectorDetails = createCollectorDetails(gcBean, memoryPoolMxBeans); - gcDetails.put(gcBean.getName(), collectorDetails); - } - return Health.up().withDetails(gcDetails).build(); - } +This is a basic example of the Health Check pattern, where health checks are built into the system and can be easily accessed and monitored. -``` +## When to Use the Health Check Pattern in Java -### MemoryHealthIndicator -A custom health indicator that checks the memory usage of the application and reports the health status accordingly. -```java - /** - * Performs a health check by checking the memory usage of the application. - * - * @return the health status of the application - */ - public Health checkMemory() { - Supplier memoryCheck = - () -> { - MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean(); - MemoryUsage heapMemoryUsage = memoryMxBean.getHeapMemoryUsage(); - long maxMemory = heapMemoryUsage.getMax(); - long usedMemory = heapMemoryUsage.getUsed(); - - double memoryUsage = (double) usedMemory / maxMemory; - String format = String.format("%.2f%% of %d max", memoryUsage * 100, maxMemory); - - if (memoryUsage < memoryThreshold) { - LOGGER.info("Memory usage is below threshold: {}", format); - return Health.up().withDetail("memory usage", format).build(); - } else { - return Health.down().withDetail("memory usage", format).build(); - } - }; - - try { - CompletableFuture future = - asynchronousHealthChecker.performCheck(memoryCheck, timeoutInSeconds); - return future.get(); - } catch (InterruptedException e) { - LOGGER.error("Health check interrupted", e); - Thread.currentThread().interrupt(); - return Health.down().withDetail("error", "Health check interrupted").build(); - } catch (ExecutionException e) { - LOGGER.error("Health check failed", e); - Throwable cause = e.getCause() == null ? e : e.getCause(); - return Health.down().withDetail("error", cause.toString()).build(); - } - } +* Use when building Java microservices or distributed systems where it is crucial to monitor the health of each service. +* Suitable for scenarios where automated systems need to determine the operational status of services to perform load balancing, failover, or recovery operations. - /** - * Retrieves the health status of the application by checking the memory usage. - * - * @return the health status of the application - */ - @Override - public Health health() { - return checkMemory(); - } -} -``` - - - -## Using Spring Boot Actuator for Health Checks -Spring Boot Actuator provides built-in health checking functionality that can be easily integrated into your application. By adding the Spring Boot Actuator dependency, you can expose health check information through a predefined endpoint, typically `/actuator/health`. +## Real-World Applications of Health Check Pattern in Java -## Output -This shows the output of the health check pattern using a GET request to the Actuator health endpoint. +Known uses of the Health Check pattern in Java include -### HTTP GET Request -``` -curl -X GET "http://localhost:6161/actuator/health" -``` +* Kubernetes liveness and readiness probes +* AWS elastic load balancing health checks +* Spring Boot Actuator integrations -### Output -```json -{ - "status": "UP", - "components": { - "cpu": { - "status": "UP", - "details": { - "processCpuLoad": "0.03%", - "availableProcessors": 10, - "systemCpuLoad": "21.40%", - "loadAverage": 3.3916015625, - "timestamp": "2023-12-03T08:44:19.488422Z" - } - }, - "custom": { - "status": "UP", - "details": { - "database": "reachable" - } - }, - "databaseTransaction": { - "status": "UP" - }, - "db": { - "status": "UP", - "details": { - "database": "H2", - "validationQuery": "isValid()" - } - }, - "diskSpace": { - "status": "UP", - "details": { - "total": 994662584320, - "free": 377635827712, - "threshold": 10485760, - "exists": true - } - }, - "garbageCollection": { - "status": "UP", - "details": { - "G1 Young Generation": { - "count": "11", - "time": "30ms", - "memoryPools": "G1 Old Gen: 0.005056262016296387%" - }, - "G1 Old Generation": { - "count": "0", - "time": "0ms", - "memoryPools": "G1 Old Gen: 0.005056262016296387%" - } - } - }, - "livenessState": { - "status": "UP" - }, - "memory": { - "status": "UP", - "details": { - "memory usage": "1.36% of 4294967296 max" - } - }, - "ping": { - "status": "UP" - }, - "readinessState": { - "status": "UP" - } - }, - "groups": [ - "liveness", - "readiness" - ] -} -``` +## Benefits and Trade-offs of Health Check Pattern -## Class Diagram -![Health Check Pattern](./etc/health-check.png) +Benefits: -## Applicability -Use the Health Check Pattern when: -- You have an application composed of multiple services and need to monitor the health of each service individually. -- You want to implement automatic service recovery or replacement based on health status. -- You are employing orchestration or automation tools that rely on health checks to manage service instances. +* Improved system reliability through early detection of failures. +* Enhanced system availability by allowing for automatic or manual recovery processes. +* Simplifies maintenance and operations by providing clear visibility into system health. -## Tutorials -- Implementing Health Checks in Java using Spring Boot Actuator. +Trade-offs: -## Known Uses -- Kubernetes Liveness and Readiness Probes -- AWS Elastic Load Balancing Health Checks -- Spring Boot Actuator +* Additional overhead for implementing and maintaining health check mechanisms. +* May introduce complexity in handling false positives and negatives in health status reporting. -## Consequences -**Pros:** -- Enhances the fault tolerance of the system by detecting failures and enabling quick recovery. -- Improves the visibility of system health for operational monitoring and alerting. +## Related Java Design Patterns -**Cons:** -- Adds complexity to service implementation. -- Requires a strategy to handle cascading failures when dependent services are unhealthy. +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Both patterns enhance system resilience; while Health Check monitors health status, Circuit Breaker protects a system from repeated failures. +* [Observer](https://java-design-patterns.com/patterns/observer/): Health Check can be seen as a specific use case of the Observer pattern, where the subject being observed is the system’s health. -## Related Patterns -- Circuit Breaker -- Retry Pattern -- Timeout Pattern +## References and Credits -## Credits -Inspired by the Health Check API pattern from [microservices.io](https://microservices.io/patterns/observability/health-check-api.html), and the issue [#2695](https://github.com/iluwatar/java-design-patterns/issues/2695) on iluwatar's Java design patterns repository. +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) +* [Release It! Design and Deploy Production-Ready Software](https://amzn.to/3Uul4kF) +* [Pattern: Health Check API (Microservices.io)](https://microservices.io/patterns/observability/health-check-api.html) diff --git a/health-check/etc/health-check.urm.puml b/health-check/etc/health-check.urm.puml new file mode 100644 index 000000000000..ebd0fa9678e7 --- /dev/null +++ b/health-check/etc/health-check.urm.puml @@ -0,0 +1,124 @@ +@startuml +package com.iluwatar.health.check { + class App { + + App() + + main(args : String[]) {static} + } + class AsynchronousHealthChecker { + - HEALTH_CHECK_FAILED_MESSAGE : String {static} + - HEALTH_CHECK_TIMEOUT_MESSAGE : String {static} + - LOGGER : Logger {static} + - healthCheckExecutor : ScheduledExecutorService + + AsynchronousHealthChecker() + - awaitTerminationWithTimeout() : boolean + + performCheck(healthCheck : Supplier, timeoutInSeconds : long) : CompletableFuture + + shutdown() + } + class CpuHealthIndicator { + - ERROR_MESSAGE : String {static} + - HIGH_LOAD_AVERAGE_MESSAGE : String {static} + - HIGH_LOAD_AVERAGE_MESSAGE_WITHOUT_PARAM : String {static} + - HIGH_PROCESS_CPU_LOAD_MESSAGE : String {static} + - HIGH_PROCESS_CPU_LOAD_MESSAGE_WITHOUT_PARAM : String {static} + - HIGH_SYSTEM_CPU_LOAD_MESSAGE : String {static} + - HIGH_SYSTEM_CPU_LOAD_MESSAGE_WITHOUT_PARAM : String {static} + - LOGGER : Logger {static} + - defaultWarningMessage : String + - loadAverageThreshold : double + - osBean : OperatingSystemMXBean + - processCpuLoadThreshold : double + - systemCpuLoadThreshold : double + + CpuHealthIndicator() + + getDefaultWarningMessage() : String + + getLoadAverageThreshold() : double + + getOsBean() : OperatingSystemMXBean + + getProcessCpuLoadThreshold() : double + + getSystemCpuLoadThreshold() : double + + health() : Health + + init() + + setDefaultWarningMessage(defaultWarningMessage : String) + + setLoadAverageThreshold(loadAverageThreshold : double) + + setOsBean(osBean : OperatingSystemMXBean) + + setProcessCpuLoadThreshold(processCpuLoadThreshold : double) + + setSystemCpuLoadThreshold(systemCpuLoadThreshold : double) + } + class CustomHealthIndicator { + - LOGGER : Logger {static} + - cacheManager : CacheManager + - healthCheckRepository : HealthCheckRepository + - healthChecker : AsynchronousHealthChecker + - timeoutInSeconds : long + + CustomHealthIndicator(healthChecker : AsynchronousHealthChecker, cacheManager : CacheManager, healthCheckRepository : HealthCheckRepository) + - check() : Health + + evictHealthCache() + + health() : Health + } + class DatabaseTransactionHealthIndicator { + - LOGGER : Logger {static} + - asynchronousHealthChecker : AsynchronousHealthChecker + - healthCheckRepository : HealthCheckRepository + - retryTemplate : RetryTemplate + - timeoutInSeconds : long + + DatabaseTransactionHealthIndicator(healthCheckRepository : HealthCheckRepository, asynchronousHealthChecker : AsynchronousHealthChecker, retryTemplate : RetryTemplate) + + getAsynchronousHealthChecker() : AsynchronousHealthChecker + + getHealthCheckRepository() : HealthCheckRepository + + getRetryTemplate() : RetryTemplate + + getTimeoutInSeconds() : long + + health() : Health + + setTimeoutInSeconds(timeoutInSeconds : long) + } + class GarbageCollectionHealthIndicator { + - LOGGER : Logger {static} + - memoryUsageThreshold : double + + GarbageCollectionHealthIndicator() + - addMemoryPoolDetails(collectorDetails : Map, memoryPoolMxBeans : List, memoryPoolNamesList : List) + - createCollectorDetails(gcBean : GarbageCollectorMXBean, memoryPoolMxBeans : List) : Map + # getGarbageCollectorMxBeans() : List + # getMemoryPoolMxBeans() : List + + getMemoryUsageThreshold() : double + + health() : Health + + setMemoryUsageThreshold(memoryUsageThreshold : double) + } + class HealthCheck { + - id : Integer + - status : String + + HealthCheck() + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : Integer + + getStatus() : String + + hashCode() : int + + setId(id : Integer) + + setStatus(status : String) + + toString() : String + } + class HealthCheckRepository { + - HEALTH_CHECK_OK : String {static} + - LOGGER : Logger {static} + - entityManager : EntityManager + + HealthCheckRepository() + + checkHealth() : Integer + + performTestTransaction() + } + class MemoryHealthIndicator { + - LOGGER : Logger {static} + - asynchronousHealthChecker : AsynchronousHealthChecker + - memoryThreshold : double + - timeoutInSeconds : long + + MemoryHealthIndicator(asynchronousHealthChecker : AsynchronousHealthChecker) + + checkMemory() : Health + + health() : Health + } + class RetryConfig { + - backOffPeriod : long + - maxAttempts : int + + RetryConfig() + + retryTemplate() : RetryTemplate + } +} +DatabaseTransactionHealthIndicator --> "-asynchronousHealthChecker" AsynchronousHealthChecker +DatabaseTransactionHealthIndicator --> "-healthCheckRepository" HealthCheckRepository +CustomHealthIndicator --> "-healthCheckRepository" HealthCheckRepository +CustomHealthIndicator --> "-healthChecker" AsynchronousHealthChecker +MemoryHealthIndicator --> "-asynchronousHealthChecker" AsynchronousHealthChecker +@enduml \ No newline at end of file diff --git a/health-check/pom.xml b/health-check/pom.xml index b6f6ba2644f1..203503ad03b6 100644 --- a/health-check/pom.xml +++ b/health-check/pom.xml @@ -36,25 +36,38 @@ health-check - - + + org.springframework.boot + spring-boot-starter + org.springframework.boot spring-boot-starter-web - - + + org.springframework.boot + spring-boot-starter-test + test + org.springframework.boot spring-boot-starter-actuator - - org.springframework.boot spring-boot-starter-data-jpa + + org.hibernate + hibernate-core + 6.4.4.Final + + + jakarta.xml.bind + jakarta.xml.bind-api + 4.0.2 + @@ -67,21 +80,7 @@ org.springframework.retry spring-retry - - - - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - org.mockito - mockito-core - test + 2.0.11 @@ -95,17 +94,17 @@ org.assertj assertj-core - 3.24.2 + 3.27.3 test io.rest-assured rest-assured + 5.5.1 test - diff --git a/health-check/src/main/java/com/iluwatar/health/check/App.java b/health-check/src/main/java/com/iluwatar/health/check/App.java index 283f028ff6a5..8f8faed9f821 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/App.java +++ b/health-check/src/main/java/com/iluwatar/health/check/App.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; import org.springframework.boot.SpringApplication; @@ -12,8 +36,6 @@ * their availability and responsiveness. For more information about health checks and their role in * microservice architectures, please refer to: [Microservices Health Checks * API]('https://microservices.io/patterns/observability/health-check-api.html'). - * - * @author ydoksanbir */ @EnableCaching @EnableScheduling diff --git a/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java b/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java index 106a924cbb11..50433d48adf6 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java +++ b/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java @@ -1,5 +1,30 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; +import jakarta.annotation.PreDestroy; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executors; @@ -7,17 +32,12 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; -import javax.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.actuate.health.Health; import org.springframework.stereotype.Component; -/** - * An asynchronous health checker component that executes health checks in a separate thread. - * - * @author ydoksanbir - */ +/** An asynchronous health checker component that executes health checks in a separate thread. */ @Slf4j @Component @RequiredArgsConstructor diff --git a/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java index 0914c3807d66..451b1263bd0c 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java @@ -1,11 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; +import jakarta.annotation.PostConstruct; import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; import java.time.Instant; import java.util.HashMap; import java.util.Map; -import javax.annotation.PostConstruct; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -14,11 +38,7 @@ import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; -/** - * A health indicator that checks the health of the system's CPU. - * - * @author ydoksanbir - */ +/** A health indicator that checks the health of the system's CPU. */ @Getter @Setter @Slf4j @@ -55,13 +75,6 @@ public void init() { @Value("${cpu.load.average.threshold:0.75}") private double loadAverageThreshold; - /** - * The warning message to include in the health indicator's response when the load average is high - * but not exceeding the threshold. - */ - @Value("${cpu.warning.message:High load average}") - private String defaultWarningMessage; - private static final String ERROR_MESSAGE = "error"; private static final String HIGH_SYSTEM_CPU_LOAD_MESSAGE = "High system CPU load: {}"; diff --git a/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java index 94ba1cabc3d6..54f2efe04db6 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; import java.util.concurrent.CompletableFuture; @@ -16,8 +40,6 @@ /** * A custom health indicator that periodically checks the health of a database and caches the * result. It leverages an asynchronous health checker to perform the health checks. - * - * @author ydoksanbir */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java index cf26b3b76e26..81658a27c727 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; import java.util.concurrent.ExecutionException; @@ -17,8 +41,6 @@ * test transaction using a retry mechanism. If the transaction succeeds after multiple attempts, * the health indicator returns {@link Health#up()} and logs a success message. If all retry * attempts fail, the health indicator returns {@link Health#down()} and logs an error message. - * - * @author ydoksanbir */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java index f81df2b36084..0790f0407ad4 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; import java.lang.management.GarbageCollectorMXBean; @@ -20,8 +44,6 @@ * reports the health status accordingly. It gathers information about the collection count, * collection time, memory pool name, and garbage collector algorithm for each garbage collector and * presents the details in a structured manner. - * - * @author ydoksanbir */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java b/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java index 15c14488ae0b..6223acde083a 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java +++ b/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java @@ -1,17 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import lombok.Data; /** * An entity class that represents a health check record in the database. This class is used to * persist the results of health checks performed by the `DatabaseTransactionHealthIndicator`. - * - * @author ydoksanbir */ @Entity @Data diff --git a/health-check/src/main/java/com/iluwatar/health/check/HealthCheckInterruptedException.java b/health-check/src/main/java/com/iluwatar/health/check/HealthCheckInterruptedException.java index d20e80b8734f..486a381f2756 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/HealthCheckInterruptedException.java +++ b/health-check/src/main/java/com/iluwatar/health/check/HealthCheckInterruptedException.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; /** diff --git a/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java b/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java index 64a0a4e93159..d5016323ac6f 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java +++ b/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java @@ -1,16 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.transaction.Transactional; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; /** * A repository class for managing health check records in the database. This class provides methods * for checking the health of the database connection and performing test transactions. - * - * @author ydoksanbir */ @Slf4j @Repository @@ -42,7 +64,7 @@ public Integer checkHealth() { * @throws Exception if the test transaction fails */ @Transactional - public void performTestTransaction() { + public void performTestTransaction() throws Exception { try { HealthCheck healthCheck = new HealthCheck(); healthCheck.setStatus(HEALTH_CHECK_OK); diff --git a/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java index 5483dfadbb18..08d82dd4ad5b 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; import java.lang.management.ManagementFactory; @@ -17,8 +41,6 @@ * A custom health indicator that checks the memory usage of the application and reports the health * status accordingly. It uses an asynchronous health checker to perform the health check and a * configurable memory usage threshold to determine the health status. - * - * @author ydoksanbir */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java b/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java index 0a0ba5d27f06..026a4dd11efe 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java +++ b/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.health.check; import org.springframework.beans.factory.annotation.Value; @@ -8,11 +32,7 @@ import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Component; -/** - * Configuration class for retry policies used in health check operations. - * - * @author ydoksanbir - */ +/** Configuration class for retry policies used in health check operations. */ @Configuration @Component public class RetryConfig { diff --git a/health-check/src/test/java/AppTest.java b/health-check/src/test/java/AppTest.java index 038e1be1a6c7..c9cd78366461 100644 --- a/health-check/src/test/java/AppTest.java +++ b/health-check/src/test/java/AppTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.iluwatar.health.check.App; diff --git a/health-check/src/test/java/AsynchronousHealthCheckerTest.java b/health-check/src/test/java/AsynchronousHealthCheckerTest.java index b7aa8943cef2..2241f1989a4f 100644 --- a/health-check/src/test/java/AsynchronousHealthCheckerTest.java +++ b/health-check/src/test/java/AsynchronousHealthCheckerTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -21,11 +45,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Tests for {@link AsynchronousHealthChecker}. - * - * @author ydoksanbir - */ +/** Tests for {@link AsynchronousHealthChecker}. */ @Slf4j class AsynchronousHealthCheckerTest { diff --git a/health-check/src/test/java/CpuHealthIndicatorTest.java b/health-check/src/test/java/CpuHealthIndicatorTest.java index 25513e861517..c59f3fea7c3f 100644 --- a/health-check/src/test/java/CpuHealthIndicatorTest.java +++ b/health-check/src/test/java/CpuHealthIndicatorTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; @@ -10,11 +34,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Test class for the {@link CpuHealthIndicator} class. - * - * @author ydoksanbir - */ +/** Test class for the {@link CpuHealthIndicator} class. */ class CpuHealthIndicatorTest { /** The CPU health indicator to be tested. */ diff --git a/health-check/src/test/java/CustomHealthIndicatorTest.java b/health-check/src/test/java/CustomHealthIndicatorTest.java index a2b3ffc9e756..1c48861aac85 100644 --- a/health-check/src/test/java/CustomHealthIndicatorTest.java +++ b/health-check/src/test/java/CustomHealthIndicatorTest.java @@ -1,6 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.iluwatar.health.check.AsynchronousHealthChecker; import com.iluwatar.health.check.CustomHealthIndicator; @@ -18,11 +47,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -/** - * Tests class< for {@link CustomHealthIndicator}. * - * - * @author ydoksanbir - */ +/** Tests class< for {@link CustomHealthIndicator}. * */ class CustomHealthIndicatorTest { /** Mocked AsynchronousHealthChecker instance. */ diff --git a/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java b/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java index 7bb87ecab234..c396770d19d8 100644 --- a/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java +++ b/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java @@ -1,6 +1,34 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; import com.iluwatar.health.check.AsynchronousHealthChecker; import com.iluwatar.health.check.DatabaseTransactionHealthIndicator; @@ -15,11 +43,7 @@ import org.springframework.boot.actuate.health.Status; import org.springframework.retry.support.RetryTemplate; -/** - * Unit tests for the {@link DatabaseTransactionHealthIndicator} class. - * - * @author ydoksanbir - */ +/** Unit tests for the {@link DatabaseTransactionHealthIndicator} class. */ class DatabaseTransactionHealthIndicatorTest { /** Timeout value in seconds for the health check. */ @@ -54,7 +78,7 @@ void setUp() { * returns a Health object with Status.UP. */ @Test - void whenDatabaseTransactionSucceeds_thenHealthIsUp() { + void whenDatabaseTransactionSucceeds_thenHealthIsUp() throws Exception { CompletableFuture future = CompletableFuture.completedFuture(Health.up().build()); when(asynchronousHealthChecker.performCheck(any(Supplier.class), eq(timeoutInSeconds))) .thenReturn(future); @@ -76,7 +100,7 @@ void whenDatabaseTransactionSucceeds_thenHealthIsUp() { * returns a Health object with Status.DOWN. */ @Test - void whenDatabaseTransactionFails_thenHealthIsDown() { + void whenDatabaseTransactionFails_thenHealthIsDown() throws Exception { CompletableFuture future = new CompletableFuture<>(); when(asynchronousHealthChecker.performCheck(any(Supplier.class), eq(timeoutInSeconds))) .thenReturn(future); diff --git a/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java b/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java index 04ad5ae7da8f..6f3068b031d4 100644 --- a/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java +++ b/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -7,6 +31,7 @@ import java.lang.management.MemoryUsage; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,11 +40,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Test class for {@link GarbageCollectionHealthIndicator}. - * - * @author ydoksanbir - */ +/** Test class for {@link GarbageCollectionHealthIndicator}. */ class GarbageCollectionHealthIndicatorTest { /** Mocked garbage collector MXBean. */ @@ -49,6 +70,7 @@ protected List getMemoryPoolMxBeans() { } }); healthIndicator.setMemoryUsageThreshold(0.8); + Locale.setDefault(Locale.US); } /** Test case to verify that the health status is up when memory usage is low. */ diff --git a/health-check/src/test/java/HealthCheckRepositoryTest.java b/health-check/src/test/java/HealthCheckRepositoryTest.java index 2508c126d9e4..8a630299dc85 100644 --- a/health-check/src/test/java/HealthCheckRepositoryTest.java +++ b/health-check/src/test/java/HealthCheckRepositoryTest.java @@ -1,21 +1,41 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import com.iluwatar.health.check.HealthCheck; import com.iluwatar.health.check.HealthCheckRepository; -import javax.persistence.EntityManager; -import javax.persistence.Query; +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -/** - * Tests class for {@link HealthCheckRepository}. - * - * @author ydoksanbir - */ +/** Tests class for {@link HealthCheckRepository}. */ @ExtendWith(MockitoExtension.class) class HealthCheckRepositoryTest { diff --git a/health-check/src/test/java/HealthEndpointIntegrationTest.java b/health-check/src/test/java/HealthEndpointIntegrationTest.java index 58fecb546ea6..0d0a3d8f8052 100644 --- a/health-check/src/test/java/HealthEndpointIntegrationTest.java +++ b/health-check/src/test/java/HealthEndpointIntegrationTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; @@ -24,8 +48,6 @@ * {"status":"DOWN","components":{"cpu":{"status":"DOWN","details":{"processCpuLoad":"100.00%", * * "availableProcessors":2,"systemCpuLoad":"100.00%","loadAverage":1.97,"timestamp":"2023-11-09T08:34:15.974557865Z", * * "error":"High system CPU load"}}} * - * - * @author ydoksanbir */ @Slf4j @SpringBootTest( diff --git a/health-check/src/test/java/MemoryHealthIndicatorTest.java b/health-check/src/test/java/MemoryHealthIndicatorTest.java index a8424ea04943..8b8da1613408 100644 --- a/health-check/src/test/java/MemoryHealthIndicatorTest.java +++ b/health-check/src/test/java/MemoryHealthIndicatorTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -17,11 +41,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Unit tests for {@link MemoryHealthIndicator}. - * - * @author ydoksanbir - */ +/** Unit tests for {@link MemoryHealthIndicator}. */ @ExtendWith(MockitoExtension.class) class MemoryHealthIndicatorTest { diff --git a/health-check/src/test/java/RetryConfigTest.java b/health-check/src/test/java/RetryConfigTest.java index 483bd00ab3a6..947616e45f18 100644 --- a/health-check/src/test/java/RetryConfigTest.java +++ b/health-check/src/test/java/RetryConfigTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -8,11 +32,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.retry.support.RetryTemplate; -/** - * Unit tests for the {@link RetryConfig} class. - * - * @author ydoksanbir - */ +/** Unit tests for the {@link RetryConfig} class. */ @SpringBootTest(classes = RetryConfig.class) class RetryConfigTest { diff --git a/hexagonal-architecture/README.md b/hexagonal-architecture/README.md new file mode 100644 index 000000000000..636487363a63 --- /dev/null +++ b/hexagonal-architecture/README.md @@ -0,0 +1,215 @@ +--- +title: "Hexagonal Architecture Pattern in Java: Decoupling Core Logic for Enhanced Flexibility" +shortTitle: Hexagonal Architecture +description: "Explore the Hexagonal Architecture pattern in Java. Learn how it decouples core logic from external interfaces, enhances maintainability, and improves testability with practical examples." +category: Architectural +language: en +tag: + - Decoupling + - Layered architecture +--- + +## Also known as + +* Ports and Adapters + +## Intent of Hexagonal Architecture Design Pattern + +Hexagonal Architecture, also known as Ports and Adapters, is a design pattern in Java that promotes decoupling of core business logic from external interfaces like databases and user interfaces. This architectural approach enhances maintainability and testability of software systems. + +## Detailed Explanation of Hexagonal Architecture Pattern with Real-World Examples + +Real-world example + +> In online banking systems, Hexagonal Architecture allows core banking logic to remain unaffected by changes in user interfaces or third-party services. This decoupling ensures the system's maintainability and flexibility. In such systems, the core banking logic (like processing transactions, managing accounts, and calculating interest) represents the application's core. This core is then surrounded by various adapters that allow the system to interact with different external interfaces without affecting the business logic. For instance, customers might access their accounts through a web interface, a mobile app, or even through ATM services. Meanwhile, the banking system also needs to interface with external services for credit checks, fraud detection, and interbank transactions. Each of these interfaces interacts with the core banking logic through specific adapters designed to translate the external calls to and from the application's internal APIs. This setup allows the bank to modify or extend its external interfaces without having to alter the core business logic, enhancing flexibility and maintainability. + +In plain words + +> Hexagonal Architecture organizes an application into a central core of business logic surrounded by ports and adapters that manage interactions with external systems like user interfaces and databases, allowing the core to remain independent of external concerns. + +Wikipedia says + +> The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation. + +Architecture diagram + +![Hexagonal Architecture Diagram](./etc/hexagonal-architecture-diagram.png) + + +## Programmatic Example of Hexagonal Architecture Pattern in Java + +The Hexagonal Architecture, also known as Ports and Adapters, is a design pattern that aims to create a loosely coupled application where the core business logic is isolated from external interfaces like databases, user interfaces, or third-party services. This allows the core application to be independent and easily testable. + +The Java code example below illustrates how Hexagonal Architecture isolates core business logic using dependency injection, making the application highly testable and independent from external components. + +In the provided code, we can see an example of the Hexagonal Architecture pattern in the `App` class and the use of Google's Guice for dependency injection. + +The `App` class is the entry point of the application. It creates an instance of `LotteryAdministration` and `LotteryService` through dependency injection and uses them to handle various tasks. + +```java +public class App { + + public static void main(String[] args) { + + var injector = Guice.createInjector(new LotteryTestingModule()); + + // start new lottery round + var administration = injector.getInstance(LotteryAdministration.class); + administration.resetLottery(); + + // submit some lottery tickets + var service = injector.getInstance(LotteryService.class); + SampleData.submitTickets(service, 20); + + // perform lottery + administration.performLottery(); + } +} +``` + +The `LotteryAdministration` class is responsible for managing the lottery rounds. It has methods to start a new round, perform the lottery, and reset the lottery. + +```java +public class LotteryAdministration { + + private final LotteryTicketRepository repository; + private final LotteryEventLog notifications; + private final WireTransfers wireTransfers; + + @Inject + public LotteryAdministration(LotteryTicketRepository repository, LotteryEventLog notifications, + WireTransfers wireTransfers) { + this.repository = repository; + this.notifications = notifications; + this.wireTransfers = wireTransfers; + } + + public Map getAllSubmittedTickets() { + return repository.findAll(); + } + + public LotteryNumbers performLottery() { + // Implementation details... + } + + public void resetLottery() { + repository.deleteAll(); + } +} +``` + +The `LotteryService` class is responsible for managing the lottery tickets. It has methods to submit a ticket, check a ticket's status, and get the winning ticket. + +```java +public class LotteryService { + + private final LotteryTicketRepository repository; + private final LotteryEventLog notifications; + private final WireTransfers wireTransfers; + + @Inject + public LotteryService(LotteryTicketRepository repository, LotteryEventLog notifications, + WireTransfers wireTransfers) { + this.repository = repository; + this.notifications = notifications; + this.wireTransfers = wireTransfers; + } + + public Optional submitTicket(LotteryTicket ticket) { + // Implementation details... + } + + public LotteryTicketCheckResult checkTicketForPrize( + LotteryTicketId id, + LotteryNumbers winningNumbers + ) { + // Implementation details... + } +} +``` + +Running the main function of App class produces the following output: + +``` +11:06:58.357 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was submitted. Bank account 334-746 was charged for 3 credits. +11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for arnold@google.com was submitted. Bank account 114-988 was charged for 3 credits. +11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ollie@google.com was submitted. Bank account 190-045 was charged for 3 credits. +11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for peter@google.com was submitted. Bank account 335-886 was charged for 3 credits. +11:06:58.359 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ray@google.com was submitted. Bank account 843-073 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was submitted. Bank account 334-746 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lisa@google.com was submitted. Bank account 024-653 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for harriet@google.com was submitted. Bank account 842-404 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com was submitted. Bank account 734-853 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lars@google.com was submitted. Bank account 746-936 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for tyron@google.com was submitted. Bank account 310-992 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for xavier@google.com was submitted. Bank account 143-947 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for kevin@google.com was submitted. Bank account 453-936 was charged for 3 credits. +11:06:58.360 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for yngwie@google.com was submitted. Bank account 241-465 was charged for 3 credits. +11:06:58.361 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was submitted. Bank account 334-746 was charged for 3 credits. +11:06:58.361 [main] ERROR com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com could not be submitted because the credit transfer of 3 credits failed. +11:06:58.362 [main] ERROR com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com could not be submitted because the credit transfer of 3 credits failed. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for mary@google.com was submitted. Bank account 234-987 was charged for 3 credits. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for edwin@google.com was submitted. Bank account 895-345 was charged for 3 credits. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for jacob@google.com was submitted. Bank account 444-766 was charged for 3 credits. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ollie@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for jacob@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for peter@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for ray@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lisa@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for harriet@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for larry@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for lars@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for tyron@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for xavier@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for kevin@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for yngwie@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for calvin@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for mary@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for arnold@google.com was checked and unfortunately did not win this time. +11:06:58.362 [main] INFO com.iluwatar.hexagonal.eventlog.StdOutEventLog -- Lottery ticket for edwin@google.com was checked and unfortunately did not win this time. +``` + +In this example, the `LotteryAdministration` and `LotteryService` classes are the core of the application. They interact with external interfaces like `LotteryTicketRepository`, `LotteryEventLog`, and `WireTransfers` through dependency injection, keeping the core business logic decoupled from external concerns. This is a basic example of the Hexagonal Architecture pattern, where the core application is at the center of input/output systems. + +## Detailed Explanation of Hexagonal Architecture Pattern with Real-World Examples + +![Hexagonal Architecture class diagram](./etc/hexagonal.png) + +## When to Use the Hexagonal Architecture Pattern in Java + +Hexagonal Architecture is particularly beneficial in scenarios: + +* The application needs to interact with multiple external systems. +* There is a requirement for high testability and maintainability. +* The application should remain unaffected by changes in external interfaces. + +## Real-World Applications of Hexagonal Architecture Pattern in Java + +* Implemented extensively within enterprise applications that leverage frameworks like Spring. +* Used in microservices architectures to maintain clear boundaries and protocols between services. +* Adopted in systems that require integration with various databases or external APIs without impacting the business logic. + +## Benefits and Trade-offs of Hexagonal Architecture Pattern + +Benefits: + +* Improved Testability: Allows the core functionality to be tested independently of external components. +* Flexibility: Facilitates the addition or replacement of components that interact with the application without modifying the core business logic. +* Maintainability: Reduces dependencies on external interfaces, simplifying upgrades and maintenance. + +Trade-offs: + +* Complexity: Introduces more abstractions and layers, which can complicate the system design and understanding. +* Overhead: Might be an over-engineering for simple applications, where simpler architectural patterns could suffice. + +## Related Java Design Patterns + +* [Layered Architecture](https://java-design-patterns.com/patterns/layers/): Shares the concept of organizing code into responsibilities; however, Hexagonal emphasizes port-based interaction with external elements. +* Microservices: Often used in conjunction with Hexagonal Architecture to define clear boundaries and protocols between services. + +## References and Credits + +* [Implementing Domain-Driven Design](https://amzn.to/4dmBjrB) +* [Building Microservices](https://amzn.to/3UACtrU) diff --git a/hexagonal-architecture/etc/hexagonal-architecture-diagram.png b/hexagonal-architecture/etc/hexagonal-architecture-diagram.png new file mode 100644 index 000000000000..39ca7d99b2d4 Binary files /dev/null and b/hexagonal-architecture/etc/hexagonal-architecture-diagram.png differ diff --git a/hexagonal-architecture/etc/hexagonal-architecture.urm.puml b/hexagonal-architecture/etc/hexagonal-architecture.urm.puml new file mode 100644 index 000000000000..f86e734747f3 --- /dev/null +++ b/hexagonal-architecture/etc/hexagonal-architecture.urm.puml @@ -0,0 +1,282 @@ +@startuml +package com.iluwatar.hexagonal.sampledata { + class SampleData { + - PLAYERS : List {static} + - RANDOM : SecureRandom {static} + + SampleData() + - getRandomPlayerDetails() : PlayerDetails {static} + + submitTickets(lotteryService : LotteryService, numTickets : int) {static} + } +} +package com.iluwatar.hexagonal.service { + class ConsoleLottery { + - LOGGER : Logger {static} + + ConsoleLottery() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } + interface LotteryConsoleService { + + addFundsToLotteryAccount(WireTransfers, Scanner) {abstract} + + checkTicket(LotteryService, Scanner) {abstract} + + queryLotteryAccountFunds(WireTransfers, Scanner) {abstract} + + submitTicket(LotteryService, Scanner) {abstract} + } + class LotteryConsoleServiceImpl { + - logger : Logger + + LotteryConsoleServiceImpl(logger : Logger) + + addFundsToLotteryAccount(bank : WireTransfers, scanner : Scanner) + + checkTicket(service : LotteryService, scanner : Scanner) + + queryLotteryAccountFunds(bank : WireTransfers, scanner : Scanner) + - readString(scanner : Scanner) : String + + submitTicket(service : LotteryService, scanner : Scanner) + } +} +package com.iluwatar.hexagonal.mongo { + class MongoConnectionPropertiesLoader { + - DEFAULT_HOST : String {static} + - DEFAULT_PORT : int {static} + - LOGGER : Logger {static} + + MongoConnectionPropertiesLoader() + + load() {static} + } +} +package com.iluwatar.hexagonal.domain { + class LotteryAdministration { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryAdministration(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + getAllSubmittedTickets() : Map + + performLottery() : LotteryNumbers + + resetLottery() + } + class LotteryConstants { + + PLAYER_MAX_BALANCE : int {static} + + PRIZE_AMOUNT : int {static} + + SERVICE_BANK_ACCOUNT : String {static} + + SERVICE_BANK_ACCOUNT_BALANCE : int {static} + + TICKET_PRIZE : int {static} + - LotteryConstants() + } + class LotteryNumbers { + + MAX_NUMBER : int {static} + + MIN_NUMBER : int {static} + + NUM_NUMBERS : int {static} + - numbers : Set + - LotteryNumbers() + - LotteryNumbers(givenNumbers : Set) + # canEqual(other : Object) : boolean + + create(givenNumbers : Set) : LotteryNumbers {static} + + createRandom() : LotteryNumbers {static} + + equals(o : Object) : boolean + - generateRandomNumbers() + + getNumbers() : Set + + getNumbersAsString() : String + + hashCode() : int + + toString() : String + } + -class RandomNumberGenerator { + - randomIterator : OfInt + + RandomNumberGenerator(min : int, max : int) + + nextInt() : int + } + class LotteryService { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryService(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + checkTicketForPrize(id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult + + submitTicket(ticket : LotteryTicket) : Optional + } + class LotteryTicketCheckResult { + - prizeAmount : int + - result : CheckResult + + LotteryTicketCheckResult(result : CheckResult) + + LotteryTicketCheckResult(result : CheckResult, prizeAmount : int) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getPrizeAmount() : int + + getResult() : CheckResult + + hashCode() : int + } + enum CheckResult { + + NO_PRIZE {static} + + TICKET_NOT_SUBMITTED {static} + + WIN_PRIZE {static} + + valueOf(name : String) : CheckResult {static} + + values() : CheckResult[] {static} + } + class LotteryTicketId { + - id : int + - numAllocated : AtomicInteger {static} + + LotteryTicketId() + + LotteryTicketId(id : int) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : int + + hashCode() : int + + toString() : String + } + class LotteryUtils { + - LotteryUtils() + + checkTicketForPrize(repository : LotteryTicketRepository, id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult {static} + } +} +package com.iluwatar.hexagonal.banking { + class InMemoryBank { + - accounts : Map {static} + + InMemoryBank() + + getFunds(bankAccount : String) : int + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean + } + class MongoBank { + - DEFAULT_ACCOUNTS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - accountsCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + + MongoBank() + + MongoBank(dbName : String, accountsCollectionName : String) + + connect() + + connect(dbName : String, accountsCollectionName : String) + + getAccountsCollection() : MongoCollection + + getDatabase() : MongoDatabase + + getFunds(bankAccount : String) : int + + getMongoClient() : MongoClient + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean + } + interface WireTransfers { + + getFunds(String) : int {abstract} + + setFunds(String, int) {abstract} + + transferFunds(int, String, String) : boolean {abstract} + } +} +package com.iluwatar.hexagonal.database { + class InMemoryTicketRepository { + - tickets : Map {static} + + InMemoryTicketRepository() + + deleteAll() + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + save(ticket : LotteryTicket) : Optional + } + interface LotteryTicketRepository { + + deleteAll() {abstract} + + findAll() : Map {abstract} + + findById(LotteryTicketId) : Optional {abstract} + + save(LotteryTicket) : Optional {abstract} + } + class MongoTicketRepository { + - DEFAULT_COUNTERS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - DEFAULT_TICKETS_COLLECTION : String {static} + - TICKET_ID : String {static} + - countersCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + - ticketsCollection : MongoCollection + + MongoTicketRepository() + + MongoTicketRepository(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + connect() + + connect(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + deleteAll() + - docToTicket(doc : Document) : LotteryTicket + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + getCountersCollection() : MongoCollection + + getNextId() : int + + getTicketsCollection() : MongoCollection + - initCounters() + + save(ticket : LotteryTicket) : Optional + } +} +package com.iluwatar.hexagonal { + class App { + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.hexagonal.administration { + class ConsoleAdministration { + - LOGGER : Logger {static} + + ConsoleAdministration() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } + interface ConsoleAdministrationSrv { + + getAllSubmittedTickets() {abstract} + + performLottery() {abstract} + + resetLottery() {abstract} + } + class ConsoleAdministrationSrvImpl { + - administration : LotteryAdministration + - logger : Logger + + ConsoleAdministrationSrvImpl(administration : LotteryAdministration, logger : Logger) + + getAllSubmittedTickets() + + performLottery() + + resetLottery() + } +} +package com.iluwatar.hexagonal.eventlog { + interface LotteryEventLog { + + prizeError(PlayerDetails, int) {abstract} + + ticketDidNotWin(PlayerDetails) {abstract} + + ticketSubmitError(PlayerDetails) {abstract} + + ticketSubmitted(PlayerDetails) {abstract} + + ticketWon(PlayerDetails, int) {abstract} + } + class MongoEventLog { + - DEFAULT_DB : String {static} + - DEFAULT_EVENTS_COLLECTION : String {static} + - EMAIL : String {static} + + MESSAGE : String {static} + - PHONE : String {static} + - database : MongoDatabase + - eventsCollection : MongoCollection + - mongoClient : MongoClient + - stdOutEventLog : StdOutEventLog + + MongoEventLog() + + MongoEventLog(dbName : String, eventsCollectionName : String) + + connect() + + connect(dbName : String, eventsCollectionName : String) + + getDatabase() : MongoDatabase + + getEventsCollection() : MongoCollection + + getMongoClient() : MongoClient + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } + class StdOutEventLog { + - LOGGER : Logger {static} + + StdOutEventLog() + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } +} +LotteryAdministration --> "-wireTransfers" WireTransfers +LotteryService --> "-notifications" LotteryEventLog +LotteryAdministration --> "-repository" LotteryTicketRepository +MongoEventLog --> "-stdOutEventLog" StdOutEventLog +LotteryService --> "-wireTransfers" WireTransfers +LotteryAdministration --> "-notifications" LotteryEventLog +ConsoleAdministrationSrvImpl --> "-administration" LotteryAdministration +LotteryService --> "-repository" LotteryTicketRepository +LotteryTicketCheckResult --> "-result" CheckResult +ConsoleAdministrationSrvImpl ..|> ConsoleAdministrationSrv +InMemoryBank ..|> WireTransfers +MongoBank ..|> WireTransfers +InMemoryTicketRepository ..|> LotteryTicketRepository +MongoTicketRepository ..|> LotteryTicketRepository +MongoEventLog ..|> LotteryEventLog +StdOutEventLog ..|> LotteryEventLog +LotteryConsoleServiceImpl ..|> LotteryConsoleService +@enduml \ No newline at end of file diff --git a/hexagonal/etc/hexagonal.png b/hexagonal-architecture/etc/hexagonal.png similarity index 100% rename from hexagonal/etc/hexagonal.png rename to hexagonal-architecture/etc/hexagonal.png diff --git a/hexagonal/etc/hexagonal.ucls b/hexagonal-architecture/etc/hexagonal.ucls similarity index 100% rename from hexagonal/etc/hexagonal.ucls rename to hexagonal-architecture/etc/hexagonal.ucls diff --git a/hexagonal/etc/hexagonal.urm.puml b/hexagonal-architecture/etc/hexagonal.urm.puml similarity index 100% rename from hexagonal/etc/hexagonal.urm.puml rename to hexagonal-architecture/etc/hexagonal.urm.puml diff --git a/hexagonal/pom.xml b/hexagonal-architecture/pom.xml similarity index 90% rename from hexagonal/pom.xml rename to hexagonal-architecture/pom.xml index a3d94abc2a8b..3f19bfa9517c 100644 --- a/hexagonal/pom.xml +++ b/hexagonal-architecture/pom.xml @@ -32,8 +32,16 @@ java-design-patterns 1.26.0-SNAPSHOT - hexagonal + hexagonal-architecture + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -46,7 +54,7 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo - 3.0.0 + 4.18.1 test @@ -77,4 +85,4 @@ - + \ No newline at end of file diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/App.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/App.java similarity index 98% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/App.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/App.java index 13b173a6aa75..b2ea0e635ced 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/App.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/App.java @@ -58,9 +58,7 @@ */ public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var injector = Guice.createInjector(new LotteryTestingModule()); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java similarity index 97% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java index 718758483ad2..80c47e3d1279 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java @@ -33,15 +33,11 @@ import java.util.Scanner; import lombok.extern.slf4j.Slf4j; -/** - * Console interface for lottery administration. - */ +/** Console interface for lottery administration. */ @Slf4j public class ConsoleAdministration { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { MongoConnectionPropertiesLoader.load(); var injector = Guice.createInjector(new LotteryModule()); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java similarity index 88% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java index 44eefccc3d87..f361ede46f12 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java @@ -24,23 +24,15 @@ */ package com.iluwatar.hexagonal.administration; -/** - * Console interface for lottery administration. - */ +/** Console interface for lottery administration. */ public interface ConsoleAdministrationSrv { - /** - * Get all submitted tickets. - */ + /** Get all submitted tickets. */ void getAllSubmittedTickets(); - /** - * Draw lottery numbers. - */ + /** Draw lottery numbers. */ void performLottery(); - /** - * Begin new lottery round. - */ + /** Begin new lottery round. */ void resetLottery(); } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java similarity index 92% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java index 8617bd305e02..02612ff83685 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java @@ -25,19 +25,14 @@ package com.iluwatar.hexagonal.administration; import com.iluwatar.hexagonal.domain.LotteryAdministration; -import com.iluwatar.hexagonal.domain.LotteryNumbers; import org.slf4j.Logger; -/** - * Console implementation for lottery administration. - */ +/** Console implementation for lottery administration. */ public class ConsoleAdministrationSrvImpl implements ConsoleAdministrationSrv { private final LotteryAdministration administration; private final Logger logger; - /** - * Constructor. - */ + /** Constructor. */ public ConsoleAdministrationSrvImpl(LotteryAdministration administration, Logger logger) { this.administration = administration; this.logger = logger; @@ -45,7 +40,8 @@ public ConsoleAdministrationSrvImpl(LotteryAdministration administration, Logger @Override public void getAllSubmittedTickets() { - administration.getAllSubmittedTickets() + administration + .getAllSubmittedTickets() .forEach((k, v) -> logger.info("Key: {}, Value: {}", k, v)); } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java similarity index 93% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java index f1754a65fc6c..421d5c32dfc4 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java @@ -28,16 +28,14 @@ import java.util.HashMap; import java.util.Map; -/** - * Banking implementation. - */ +/** Banking implementation. */ public class InMemoryBank implements WireTransfers { private static final Map accounts = new HashMap<>(); static { - accounts - .put(LotteryConstants.SERVICE_BANK_ACCOUNT, LotteryConstants.SERVICE_BANK_ACCOUNT_BALANCE); + accounts.put( + LotteryConstants.SERVICE_BANK_ACCOUNT, LotteryConstants.SERVICE_BANK_ACCOUNT_BALANCE); } @Override diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java similarity index 77% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java index e13a249d48a8..c7af4693137e 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java @@ -29,82 +29,46 @@ import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.UpdateOptions; import java.util.ArrayList; +import lombok.Getter; import org.bson.Document; -/** - * Mongo based banking adapter. - */ +/** Mongo based banking adapter. */ public class MongoBank implements WireTransfers { private static final String DEFAULT_DB = "lotteryDB"; private static final String DEFAULT_ACCOUNTS_COLLECTION = "accounts"; - private MongoClient mongoClient; - private MongoDatabase database; - private MongoCollection accountsCollection; + @Getter private MongoClient mongoClient; + @Getter private MongoDatabase database; + @Getter private MongoCollection accountsCollection; - /** - * Constructor. - */ + /** Constructor. */ public MongoBank() { connect(); } - /** - * Constructor accepting parameters. - */ + /** Constructor accepting parameters. */ public MongoBank(String dbName, String accountsCollectionName) { connect(dbName, accountsCollectionName); } - /** - * Connect to database with default parameters. - */ + /** Connect to database with default parameters. */ public void connect() { connect(DEFAULT_DB, DEFAULT_ACCOUNTS_COLLECTION); } - /** - * Connect to database with given parameters. - */ + /** Connect to database with given parameters. */ public void connect(String dbName, String accountsCollectionName) { if (mongoClient != null) { mongoClient.close(); } - mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); database = mongoClient.getDatabase(dbName); accountsCollection = database.getCollection(accountsCollectionName); } - /** - * Get mongo client. - * - * @return mongo client - */ - public MongoClient getMongoClient() { - return mongoClient; - } - - /** - * Get mongo database. - * - * @return mongo database - */ - public MongoDatabase getMongoDatabase() { - return database; - } - - /** - * Get accounts collection. - * - * @return accounts collection - */ - public MongoCollection getAccountsCollection() { - return accountsCollection; - } - - @Override public void setFunds(String bankAccount, int amount) { var search = new Document("_id", bankAccount); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java similarity index 87% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java index d88827d8fd4c..f8981420933f 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java @@ -24,24 +24,15 @@ */ package com.iluwatar.hexagonal.banking; -/** - * Interface to bank accounts. - */ +/** Interface to bank accounts. */ public interface WireTransfers { - /** - * Set amount of funds for bank account. - */ + /** Set amount of funds for bank account. */ void setFunds(String bankAccount, int amount); - /** - * Get amount of funds for bank account. - */ + /** Get amount of funds for bank account. */ int getFunds(String bankAccount); - /** - * Transfer funds from one bank account to another. - */ + /** Transfer funds from one bank account to another. */ boolean transferFunds(int amount, String sourceBackAccount, String destinationBankAccount); - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java similarity index 97% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java index 8c0aa1d74637..5805e80aade6 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java @@ -30,9 +30,7 @@ import java.util.Map; import java.util.Optional; -/** - * Mock database for lottery tickets. - */ +/** Mock database for lottery tickets. */ public class InMemoryTicketRepository implements LotteryTicketRepository { private static final Map tickets = new HashMap<>(); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java similarity index 87% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java index 6302b6dd6feb..29ca16af70c3 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java @@ -29,29 +29,18 @@ import java.util.Map; import java.util.Optional; -/** - * Interface for accessing lottery tickets in database. - */ +/** Interface for accessing lottery tickets in database. */ public interface LotteryTicketRepository { - /** - * Find lottery ticket by id. - */ + /** Find lottery ticket by id. */ Optional findById(LotteryTicketId id); - /** - * Save lottery ticket. - */ + /** Save lottery ticket. */ Optional save(LotteryTicket ticket); - /** - * Get all lottery tickets. - */ + /** Get all lottery tickets. */ Map findAll(); - /** - * Delete all lottery tickets. - */ + /** Delete all lottery tickets. */ void deleteAll(); - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java similarity index 71% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java index a8d7ecdb85cc..ecb26c9c470b 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java @@ -37,11 +37,10 @@ import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import lombok.Getter; import org.bson.Document; -/** - * Mongo lottery ticket database. - */ +/** Mongo lottery ticket database. */ public class MongoTicketRepository implements LotteryTicketRepository { private static final String DEFAULT_DB = "lotteryDB"; @@ -51,41 +50,33 @@ public class MongoTicketRepository implements LotteryTicketRepository { private MongoClient mongoClient; private MongoDatabase database; - private MongoCollection ticketsCollection; - private MongoCollection countersCollection; + @Getter private MongoCollection ticketsCollection; + @Getter private MongoCollection countersCollection; - /** - * Constructor. - */ + /** Constructor. */ public MongoTicketRepository() { connect(); } - /** - * Constructor accepting parameters. - */ - public MongoTicketRepository(String dbName, String ticketsCollectionName, - String countersCollectionName) { + /** Constructor accepting parameters. */ + public MongoTicketRepository( + String dbName, String ticketsCollectionName, String countersCollectionName) { connect(dbName, ticketsCollectionName, countersCollectionName); } - /** - * Connect to database with default parameters. - */ + /** Connect to database with default parameters. */ public void connect() { connect(DEFAULT_DB, DEFAULT_TICKETS_COLLECTION, DEFAULT_COUNTERS_COLLECTION); } - /** - * Connect to database with given parameters. - */ - public void connect(String dbName, String ticketsCollectionName, - String countersCollectionName) { + /** Connect to database with given parameters. */ + public void connect(String dbName, String ticketsCollectionName, String countersCollectionName) { if (mongoClient != null) { mongoClient.close(); } - mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); database = mongoClient.getDatabase(dbName); ticketsCollection = database.getCollection(ticketsCollectionName); countersCollection = database.getCollection(countersCollectionName); @@ -112,24 +103,6 @@ public int getNextId() { return result.getInteger("seq"); } - /** - * Get tickets collection. - * - * @return tickets collection - */ - public MongoCollection getTicketsCollection() { - return ticketsCollection; - } - - /** - * Get counters collection. - * - * @return counters collection - */ - public MongoCollection getCountersCollection() { - return countersCollection; - } - @Override public Optional findById(LotteryTicketId id) { return ticketsCollection @@ -145,22 +118,19 @@ public Optional findById(LotteryTicketId id) { public Optional save(LotteryTicket ticket) { var ticketId = getNextId(); var doc = new Document(TICKET_ID, ticketId); - doc.put("email", ticket.getPlayerDetails().getEmail()); - doc.put("bank", ticket.getPlayerDetails().getBankAccount()); - doc.put("phone", ticket.getPlayerDetails().getPhoneNumber()); - doc.put("numbers", ticket.getLotteryNumbers().getNumbersAsString()); + doc.put("email", ticket.playerDetails().email()); + doc.put("bank", ticket.playerDetails().bankAccount()); + doc.put("phone", ticket.playerDetails().phoneNumber()); + doc.put("numbers", ticket.lotteryNumbers().getNumbersAsString()); ticketsCollection.insertOne(doc); return Optional.of(new LotteryTicketId(ticketId)); } @Override public Map findAll() { - return ticketsCollection - .find(new Document()) - .into(new ArrayList<>()) - .stream() + return ticketsCollection.find(new Document()).into(new ArrayList<>()).stream() .map(this::docToTicket) - .collect(Collectors.toMap(LotteryTicket::getId, Function.identity())); + .collect(Collectors.toMap(LotteryTicket::id, Function.identity())); } @Override @@ -169,11 +139,12 @@ public void deleteAll() { } private LotteryTicket docToTicket(Document doc) { - var playerDetails = new PlayerDetails(doc.getString("email"), doc.getString("bank"), - doc.getString("phone")); - var numbers = Arrays.stream(doc.getString("numbers").split(",")) - .map(Integer::parseInt) - .collect(Collectors.toSet()); + var playerDetails = + new PlayerDetails(doc.getString("email"), doc.getString("bank"), doc.getString("phone")); + var numbers = + Arrays.stream(doc.getString("numbers").split(",")) + .map(Integer::parseInt) + .collect(Collectors.toSet()); var lotteryNumbers = LotteryNumbers.create(numbers); var ticketId = new LotteryTicketId(doc.getInteger(TICKET_ID)); return new LotteryTicket(ticketId, playerDetails, lotteryNumbers); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java similarity index 85% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java index 5ce6c8884598..28cf0fbbd22a 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java @@ -33,43 +33,37 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import java.util.Map; -/** - * Lottery administration implementation. - */ +/** Lottery administration implementation. */ public class LotteryAdministration { private final LotteryTicketRepository repository; private final LotteryEventLog notifications; private final WireTransfers wireTransfers; - /** - * Constructor. - */ + /** Constructor. */ @Inject - public LotteryAdministration(LotteryTicketRepository repository, LotteryEventLog notifications, - WireTransfers wireTransfers) { + public LotteryAdministration( + LotteryTicketRepository repository, + LotteryEventLog notifications, + WireTransfers wireTransfers) { this.repository = repository; this.notifications = notifications; this.wireTransfers = wireTransfers; } - /** - * Get all the lottery tickets submitted for lottery. - */ + /** Get all the lottery tickets submitted for lottery. */ public Map getAllSubmittedTickets() { return repository.findAll(); } - /** - * Draw lottery numbers. - */ + /** Draw lottery numbers. */ public LotteryNumbers performLottery() { var numbers = LotteryNumbers.createRandom(); var tickets = getAllSubmittedTickets(); for (var id : tickets.keySet()) { var lotteryTicket = tickets.get(id); - var playerDetails = lotteryTicket.getPlayerDetails(); - var playerAccount = playerDetails.getBankAccount(); + var playerDetails = lotteryTicket.playerDetails(); + var playerAccount = playerDetails.bankAccount(); var result = LotteryUtils.checkTicketForPrize(repository, id, numbers).getResult(); if (result == LotteryTicketCheckResult.CheckResult.WIN_PRIZE) { if (wireTransfers.transferFunds(PRIZE_AMOUNT, SERVICE_BANK_ACCOUNT, playerAccount)) { @@ -84,9 +78,7 @@ public LotteryNumbers performLottery() { return numbers; } - /** - * Begin new lottery round. - */ + /** Begin new lottery round. */ public void resetLottery() { repository.deleteAll(); } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java similarity index 95% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java index 2001d57ace99..21dcbc2d48be 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java @@ -24,18 +24,14 @@ */ package com.iluwatar.hexagonal.domain; -/** - * Lottery domain constants. - */ +/** Lottery domain constants. */ public class LotteryConstants { - private LotteryConstants() { - } + private LotteryConstants() {} public static final int PRIZE_AMOUNT = 100000; public static final String SERVICE_BANK_ACCOUNT = "123-123"; public static final int TICKET_PRIZE = 3; public static final int SERVICE_BANK_ACCOUNT_BALANCE = 150000; public static final int PLAYER_MAX_BALANCE = 100; - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java similarity index 93% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java index 83836046d621..3319251f479d 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java @@ -47,17 +47,13 @@ public class LotteryNumbers { public static final int MAX_NUMBER = 20; public static final int NUM_NUMBERS = 4; - /** - * Constructor. Creates random lottery numbers. - */ + /** Constructor. Creates random lottery numbers. */ private LotteryNumbers() { numbers = new HashSet<>(); generateRandomNumbers(); } - /** - * Constructor. Uses given numbers. - */ + /** Constructor. Uses given numbers. */ private LotteryNumbers(Set givenNumbers) { numbers = new HashSet<>(); numbers.addAll(givenNumbers); @@ -99,9 +95,7 @@ public String getNumbersAsString() { return Joiner.on(',').join(numbers); } - /** - * Generates 4 unique random numbers between 1-20 into numbers set. - */ + /** Generates 4 unique random numbers between 1-20 into numbers set. */ private void generateRandomNumbers() { numbers.clear(); var generator = new RandomNumberGenerator(MIN_NUMBER, MAX_NUMBER); @@ -111,9 +105,7 @@ private void generateRandomNumbers() { } } - /** - * Helper class for generating random numbers. - */ + /** Helper class for generating random numbers. */ private static class RandomNumberGenerator { private final PrimitiveIterator.OfInt randomIterator; @@ -138,5 +130,4 @@ public int nextInt() { return randomIterator.nextInt(); } } - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java similarity index 83% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java index 61185f32ae4a..38d33ab957e4 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java @@ -33,32 +33,28 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import java.util.Optional; -/** - * Implementation for lottery service. - */ +/** Implementation for lottery service. */ public class LotteryService { private final LotteryTicketRepository repository; private final LotteryEventLog notifications; private final WireTransfers wireTransfers; - /** - * Constructor. - */ + /** Constructor. */ @Inject - public LotteryService(LotteryTicketRepository repository, LotteryEventLog notifications, - WireTransfers wireTransfers) { + public LotteryService( + LotteryTicketRepository repository, + LotteryEventLog notifications, + WireTransfers wireTransfers) { this.repository = repository; this.notifications = notifications; this.wireTransfers = wireTransfers; } - /** - * Submit lottery ticket to participate in the lottery. - */ + /** Submit lottery ticket to participate in the lottery. */ public Optional submitTicket(LotteryTicket ticket) { - var playerDetails = ticket.getPlayerDetails(); - var playerAccount = playerDetails.getBankAccount(); + var playerDetails = ticket.playerDetails(); + var playerAccount = playerDetails.bankAccount(); var result = wireTransfers.transferFunds(TICKET_PRIZE, playerAccount, SERVICE_BANK_ACCOUNT); if (!result) { notifications.ticketSubmitError(playerDetails); @@ -71,13 +67,9 @@ public Optional submitTicket(LotteryTicket ticket) { return optional; } - /** - * Check if lottery ticket has won. - */ + /** Check if lottery ticket has won. */ public LotteryTicketCheckResult checkTicketForPrize( - LotteryTicketId id, - LotteryNumbers winningNumbers - ) { + LotteryTicketId id, LotteryNumbers winningNumbers) { return LotteryUtils.checkTicketForPrize(repository, id, winningNumbers); } } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java similarity index 86% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java index 1205b3af3656..0b895f8169a2 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java @@ -24,21 +24,9 @@ */ package com.iluwatar.hexagonal.domain; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -/** - * Immutable value object representing lottery ticket. - */ -@Getter -@ToString -@RequiredArgsConstructor -public class LotteryTicket { - - private final LotteryTicketId id; - private final PlayerDetails playerDetails; - private final LotteryNumbers lotteryNumbers; +/** Immutable value object representing lottery ticket. */ +public record LotteryTicket( + LotteryTicketId id, PlayerDetails playerDetails, LotteryNumbers lotteryNumbers) { @Override public int hashCode() { @@ -74,5 +62,4 @@ public boolean equals(Object obj) { return playerDetails.equals(other.playerDetails); } } - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java similarity index 92% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java index 7b1290e23c89..ff0245f30b1e 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java @@ -28,17 +28,13 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Represents lottery ticket check result. - */ +/** Represents lottery ticket check result. */ @Getter @EqualsAndHashCode @RequiredArgsConstructor public class LotteryTicketCheckResult { - /** - * Enumeration of Type of Outcomes of a Lottery. - */ + /** Enumeration of Type of Outcomes of a Lottery. */ public enum CheckResult { WIN_PRIZE, NO_PRIZE, @@ -48,12 +44,9 @@ public enum CheckResult { private final CheckResult result; private final int prizeAmount; - /** - * Constructor. - */ + /** Constructor. */ public LotteryTicketCheckResult(CheckResult result) { this.result = result; prizeAmount = 0; } - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java similarity index 98% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java index f53c992e18b9..4bd1074fb3d9 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java @@ -29,9 +29,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Lottery ticked id. - */ +/** Lottery ticked id. */ @Getter @EqualsAndHashCode @RequiredArgsConstructor @@ -48,5 +46,4 @@ public LotteryTicketId() { public String toString() { return String.format("%d", id); } - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java similarity index 86% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java index d1b0eb9c52d4..5e4ed138c7df 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java @@ -27,25 +27,17 @@ import com.iluwatar.hexagonal.database.LotteryTicketRepository; import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult; -/** - * Lottery utilities. - */ +/** Lottery utilities. */ public class LotteryUtils { - private LotteryUtils() { - } + private LotteryUtils() {} - /** - * Checks if lottery ticket has won. - */ + /** Checks if lottery ticket has won. */ public static LotteryTicketCheckResult checkTicketForPrize( - LotteryTicketRepository repository, - LotteryTicketId id, - LotteryNumbers winningNumbers - ) { + LotteryTicketRepository repository, LotteryTicketId id, LotteryNumbers winningNumbers) { var optional = repository.findById(id); if (optional.isPresent()) { - if (optional.get().getLotteryNumbers().equals(winningNumbers)) { + if (optional.get().lotteryNumbers().equals(winningNumbers)) { return new LotteryTicketCheckResult(CheckResult.WIN_PRIZE, 1000); } else { return new LotteryTicketCheckResult(CheckResult.NO_PRIZE); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java similarity index 77% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java index 76f338e605f4..5fa3a3145253 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java @@ -24,22 +24,5 @@ */ package com.iluwatar.hexagonal.domain; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; - -/** - * Immutable value object containing lottery player details. - */ -@EqualsAndHashCode -@ToString -@Getter -@RequiredArgsConstructor -public class PlayerDetails { - - private final String email; - private final String bankAccount; - private final String phoneNumber; - -} +/** Immutable value object containing lottery player details. */ +public record PlayerDetails(String email, String bankAccount, String phoneNumber) {} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java similarity index 86% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java index b33ec322f97a..6cea51b6bbe7 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java @@ -26,34 +26,21 @@ import com.iluwatar.hexagonal.domain.PlayerDetails; -/** - * Event log for lottery events. - */ +/** Event log for lottery events. */ public interface LotteryEventLog { - /** - * lottery ticket submitted. - */ + /** lottery ticket submitted. */ void ticketSubmitted(PlayerDetails details); - /** - * error submitting lottery ticket. - */ + /** error submitting lottery ticket. */ void ticketSubmitError(PlayerDetails details); - /** - * lottery ticket did not win. - */ + /** lottery ticket did not win. */ void ticketDidNotWin(PlayerDetails details); - /** - * lottery ticket won. - */ + /** lottery ticket won. */ void ticketWon(PlayerDetails details, int prizeAmount); - /** - * error paying the prize. - */ + /** error paying the prize. */ void prizeError(PlayerDetails details, int prizeAmount); - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java similarity index 61% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java index 84e91e8b160b..47120d3d7840 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java @@ -28,11 +28,10 @@ import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import lombok.Getter; import org.bson.Document; -/** - * Mongo based event log. - */ +/** Mongo based event log. */ public class MongoEventLog implements LotteryEventLog { private static final String DEFAULT_DB = "lotteryDB"; @@ -41,90 +40,55 @@ public class MongoEventLog implements LotteryEventLog { private static final String PHONE = "phone"; public static final String MESSAGE = "message"; - private MongoClient mongoClient; - private MongoDatabase database; - private MongoCollection eventsCollection; + @Getter private MongoClient mongoClient; + @Getter private MongoDatabase database; + @Getter private MongoCollection eventsCollection; private final StdOutEventLog stdOutEventLog = new StdOutEventLog(); - /** - * Constructor. - */ + /** Constructor. */ public MongoEventLog() { connect(); } - /** - * Constructor accepting parameters. - */ + /** Constructor accepting parameters. */ public MongoEventLog(String dbName, String eventsCollectionName) { connect(dbName, eventsCollectionName); } - /** - * Connect to database with default parameters. - */ + /** Connect to database with default parameters. */ public void connect() { connect(DEFAULT_DB, DEFAULT_EVENTS_COLLECTION); } - /** - * Connect to database with given parameters. - */ + /** Connect to database with given parameters. */ public void connect(String dbName, String eventsCollectionName) { if (mongoClient != null) { mongoClient.close(); } - mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); database = mongoClient.getDatabase(dbName); eventsCollection = database.getCollection(eventsCollectionName); } - /** - * Get mongo client. - * - * @return mongo client - */ - public MongoClient getMongoClient() { - return mongoClient; - } - - /** - * Get mongo database. - * - * @return mongo database - */ - public MongoDatabase getMongoDatabase() { - return database; - } - - /** - * Get events collection. - * - * @return events collection - */ - public MongoCollection getEventsCollection() { - return eventsCollection; - } - - @Override public void ticketSubmitted(PlayerDetails details) { - var document = new Document(EMAIL, details.getEmail()); - document.put(PHONE, details.getPhoneNumber()); - document.put("bank", details.getBankAccount()); - document - .put(MESSAGE, "Lottery ticket was submitted and bank account was charged for 3 credits."); + var document = new Document(EMAIL, details.email()); + document.put(PHONE, details.phoneNumber()); + document.put("bank", details.bankAccount()); + document.put( + MESSAGE, "Lottery ticket was submitted and bank account was charged for 3 credits."); eventsCollection.insertOne(document); stdOutEventLog.ticketSubmitted(details); } @Override public void ticketSubmitError(PlayerDetails details) { - var document = new Document(EMAIL, details.getEmail()); - document.put(PHONE, details.getPhoneNumber()); - document.put("bank", details.getBankAccount()); + var document = new Document(EMAIL, details.email()); + document.put(PHONE, details.phoneNumber()); + document.put("bank", details.bankAccount()); document.put(MESSAGE, "Lottery ticket could not be submitted because lack of funds."); eventsCollection.insertOne(document); stdOutEventLog.ticketSubmitError(details); @@ -132,9 +96,9 @@ public void ticketSubmitError(PlayerDetails details) { @Override public void ticketDidNotWin(PlayerDetails details) { - var document = new Document(EMAIL, details.getEmail()); - document.put(PHONE, details.getPhoneNumber()); - document.put("bank", details.getBankAccount()); + var document = new Document(EMAIL, details.email()); + document.put(PHONE, details.phoneNumber()); + document.put("bank", details.bankAccount()); document.put(MESSAGE, "Lottery ticket was checked and unfortunately did not win this time."); eventsCollection.insertOne(document); stdOutEventLog.ticketDidNotWin(details); @@ -142,23 +106,26 @@ public void ticketDidNotWin(PlayerDetails details) { @Override public void ticketWon(PlayerDetails details, int prizeAmount) { - var document = new Document(EMAIL, details.getEmail()); - document.put(PHONE, details.getPhoneNumber()); - document.put("bank", details.getBankAccount()); - document.put(MESSAGE, String - .format("Lottery ticket won! The bank account was deposited with %d credits.", - prizeAmount)); + var document = new Document(EMAIL, details.email()); + document.put(PHONE, details.phoneNumber()); + document.put("bank", details.bankAccount()); + document.put( + MESSAGE, + String.format( + "Lottery ticket won! The bank account was deposited with %d credits.", prizeAmount)); eventsCollection.insertOne(document); stdOutEventLog.ticketWon(details, prizeAmount); } @Override public void prizeError(PlayerDetails details, int prizeAmount) { - var document = new Document(EMAIL, details.getEmail()); - document.put(PHONE, details.getPhoneNumber()); - document.put("bank", details.getBankAccount()); - document.put(MESSAGE, String - .format("Lottery ticket won! Unfortunately the bank credit transfer of %d failed.", + var document = new Document(EMAIL, details.email()); + document.put(PHONE, details.phoneNumber()); + document.put("bank", details.bankAccount()); + document.put( + MESSAGE, + String.format( + "Lottery ticket won! Unfortunately the bank credit transfer of %d failed.", prizeAmount)); eventsCollection.insertOne(document); stdOutEventLog.prizeError(details, prizeAmount); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java similarity index 68% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java index 40afe687dbe0..1ddd93d5932b 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java @@ -27,39 +27,47 @@ import com.iluwatar.hexagonal.domain.PlayerDetails; import lombok.extern.slf4j.Slf4j; -/** - * Standard output event log. - */ +/** Standard output event log. */ @Slf4j public class StdOutEventLog implements LotteryEventLog { @Override public void ticketSubmitted(PlayerDetails details) { - LOGGER.info("Lottery ticket for {} was submitted. Bank account {} was charged for 3 credits.", - details.getEmail(), details.getBankAccount()); + LOGGER.info( + "Lottery ticket for {} was submitted. Bank account {} was charged for 3 credits.", + details.email(), + details.bankAccount()); } @Override public void ticketDidNotWin(PlayerDetails details) { - LOGGER.info("Lottery ticket for {} was checked and unfortunately did not win this time.", - details.getEmail()); + LOGGER.info( + "Lottery ticket for {} was checked and unfortunately did not win this time.", + details.email()); } @Override public void ticketWon(PlayerDetails details, int prizeAmount) { - LOGGER.info("Lottery ticket for {} has won! The bank account {} was deposited with {} credits.", - details.getEmail(), details.getBankAccount(), prizeAmount); + LOGGER.info( + "Lottery ticket for {} has won! The bank account {} was deposited with {} credits.", + details.email(), + details.bankAccount(), + prizeAmount); } @Override public void prizeError(PlayerDetails details, int prizeAmount) { - LOGGER.error("Lottery ticket for {} has won! Unfortunately the bank credit transfer of" - + " {} failed.", details.getEmail(), prizeAmount); + LOGGER.error( + "Lottery ticket for {} has won! Unfortunately the bank credit transfer of" + " {} failed.", + details.email(), + prizeAmount); } @Override public void ticketSubmitError(PlayerDetails details) { - LOGGER.error("Lottery ticket for {} could not be submitted because the credit transfer" - + " of 3 credits failed.", details.getEmail()); + LOGGER.error( + "Lottery ticket for {} could not be submitted because the credit transfer" + + " of 3 credits failed.", + details.email()); } } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java similarity index 97% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java index 1f1d16d4e1cd..c45117bc6e6c 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java @@ -32,9 +32,7 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import com.iluwatar.hexagonal.eventlog.MongoEventLog; -/** - * Guice module for binding production dependencies. - */ +/** Guice module for binding production dependencies. */ public class LotteryModule extends AbstractModule { @Override protected void configure() { diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java similarity index 97% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java index 1e67f60a7135..1ed43183be3d 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java @@ -32,9 +32,7 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import com.iluwatar.hexagonal.eventlog.StdOutEventLog; -/** - * Guice module for testing dependencies. - */ +/** Guice module for testing dependencies. */ public class LotteryTestingModule extends AbstractModule { @Override protected void configure() { diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java similarity index 90% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java index fc735b9f6e88..5a90579898e9 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java @@ -26,18 +26,16 @@ import java.io.FileInputStream; import java.util.Properties; +import lombok.extern.slf4j.Slf4j; -/** - * Mongo connection properties loader. - */ +/** Mongo connection properties loader. */ +@Slf4j public class MongoConnectionPropertiesLoader { private static final String DEFAULT_HOST = "localhost"; private static final int DEFAULT_PORT = 27017; - /** - * Try to load connection properties from file. Fall back to default connection properties. - */ + /** Try to load connection properties from file. Fall back to default connection properties. */ public static void load() { var host = DEFAULT_HOST; var port = DEFAULT_PORT; @@ -50,7 +48,7 @@ public static void load() { port = Integer.parseInt(properties.getProperty("mongo-port")); } catch (Exception e) { // error occurred, use default properties - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } System.setProperty("mongo-host", host); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java new file mode 100644 index 000000000000..7d0bb4136f68 --- /dev/null +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java @@ -0,0 +1,109 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.hexagonal.sampledata; + +import com.iluwatar.hexagonal.banking.InMemoryBank; +import com.iluwatar.hexagonal.domain.LotteryConstants; +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryService; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; +import java.security.SecureRandom; +import java.util.AbstractMap.SimpleEntry; +import java.util.List; +import java.util.stream.Collectors; + +/** Utilities for creating sample lottery tickets. */ +public class SampleData { + + private static final List PLAYERS; + private static final SecureRandom RANDOM = new SecureRandom(); + + static { + PLAYERS = + List.of( + new PlayerDetails("john@google.com", "312-342", "+3242434242"), + new PlayerDetails("mary@google.com", "234-987", "+23452346"), + new PlayerDetails("steve@google.com", "833-836", "+63457543"), + new PlayerDetails("wayne@google.com", "319-826", "+24626"), + new PlayerDetails("johnie@google.com", "983-322", "+3635635"), + new PlayerDetails("andy@google.com", "934-734", "+0898245"), + new PlayerDetails("richard@google.com", "536-738", "+09845325"), + new PlayerDetails("kevin@google.com", "453-936", "+2423532"), + new PlayerDetails("arnold@google.com", "114-988", "+5646346524"), + new PlayerDetails("ian@google.com", "663-765", "+928394235"), + new PlayerDetails("robin@google.com", "334-763", "+35448"), + new PlayerDetails("ted@google.com", "735-964", "+98752345"), + new PlayerDetails("larry@google.com", "734-853", "+043842423"), + new PlayerDetails("calvin@google.com", "334-746", "+73294135"), + new PlayerDetails("jacob@google.com", "444-766", "+358042354"), + new PlayerDetails("edwin@google.com", "895-345", "+9752435"), + new PlayerDetails("mary@google.com", "760-009", "+34203542"), + new PlayerDetails("lolita@google.com", "425-907", "+9872342"), + new PlayerDetails("bruno@google.com", "023-638", "+673824122"), + new PlayerDetails("peter@google.com", "335-886", "+5432503945"), + new PlayerDetails("warren@google.com", "225-946", "+9872341324"), + new PlayerDetails("monica@google.com", "265-748", "+134124"), + new PlayerDetails("ollie@google.com", "190-045", "+34453452"), + new PlayerDetails("yngwie@google.com", "241-465", "+9897641231"), + new PlayerDetails("lars@google.com", "746-936", "+42345298345"), + new PlayerDetails("bobbie@google.com", "946-384", "+79831742"), + new PlayerDetails("tyron@google.com", "310-992", "+0498837412"), + new PlayerDetails("tyrell@google.com", "032-045", "+67834134"), + new PlayerDetails("nadja@google.com", "000-346", "+498723"), + new PlayerDetails("wendy@google.com", "994-989", "+987324454"), + new PlayerDetails("luke@google.com", "546-634", "+987642435"), + new PlayerDetails("bjorn@google.com", "342-874", "+7834325"), + new PlayerDetails("lisa@google.com", "024-653", "+980742154"), + new PlayerDetails("anton@google.com", "834-935", "+876423145"), + new PlayerDetails("bruce@google.com", "284-936", "+09843212345"), + new PlayerDetails("ray@google.com", "843-073", "+678324123"), + new PlayerDetails("ron@google.com", "637-738", "+09842354"), + new PlayerDetails("xavier@google.com", "143-947", "+375245"), + new PlayerDetails("harriet@google.com", "842-404", "+131243252")); + var wireTransfers = new InMemoryBank(); + PLAYERS.stream() + .map(PlayerDetails::bankAccount) + .map(e -> new SimpleEntry<>(e, RANDOM.nextInt(LotteryConstants.PLAYER_MAX_BALANCE))) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)) + .forEach(wireTransfers::setFunds); + } + + /** Inserts lottery tickets into the database based on the sample data. */ + public static void submitTickets(LotteryService lotteryService, int numTickets) { + for (var i = 0; i < numTickets; i++) { + var randomPlayerDetails = getRandomPlayerDetails(); + var lotteryNumbers = LotteryNumbers.createRandom(); + var lotteryTicketId = new LotteryTicketId(); + var ticket = new LotteryTicket(lotteryTicketId, randomPlayerDetails, lotteryNumbers); + lotteryService.submitTicket(ticket); + } + } + + private static PlayerDetails getRandomPlayerDetails() { + return PLAYERS.get(RANDOM.nextInt(PLAYERS.size())); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java similarity index 97% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java index b273a828f3da..8832b71a5241 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java @@ -32,15 +32,11 @@ import java.util.Scanner; import lombok.extern.slf4j.Slf4j; -/** - * Console interface for lottery players. - */ +/** Console interface for lottery players. */ @Slf4j public class ConsoleLottery { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { MongoConnectionPropertiesLoader.load(); var injector = Guice.createInjector(new LotteryModule()); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java similarity index 88% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java index baa244564fc0..39fbb08c31f4 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java @@ -28,28 +28,17 @@ import com.iluwatar.hexagonal.domain.LotteryService; import java.util.Scanner; - -/** - * Console interface for lottery service. - */ +/** Console interface for lottery service. */ public interface LotteryConsoleService { void checkTicket(LotteryService service, Scanner scanner); - /** - * Submit lottery ticket to participate in the lottery. - */ + /** Submit lottery ticket to participate in the lottery. */ void submitTicket(LotteryService service, Scanner scanner); - /** - * Add funds to lottery account. - */ + /** Add funds to lottery account. */ void addFundsToLotteryAccount(WireTransfers bank, Scanner scanner); - - /** - * Recovery funds from lottery account. - */ + /** Recovery funds from lottery account. */ void queryLotteryAccountFunds(WireTransfers bank, Scanner scanner); - } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java similarity index 88% rename from hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java rename to hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java index 44f8bea099c0..dbdd16f79a4e 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java @@ -36,16 +36,12 @@ import java.util.stream.Collectors; import org.slf4j.Logger; -/** - * Console implementation for lottery console service. - */ +/** Console implementation for lottery console service. */ public class LotteryConsoleServiceImpl implements LotteryConsoleService { private final Logger logger; - /** - * Constructor. - */ + /** Constructor. */ public LotteryConsoleServiceImpl(Logger logger) { this.logger = logger; } @@ -57,10 +53,11 @@ public void checkTicket(LotteryService service, Scanner scanner) { logger.info("Give the 4 comma separated winning numbers?"); var numbers = readString(scanner); try { - var winningNumbers = Arrays.stream(numbers.split(",")) - .map(Integer::parseInt) - .limit(4) - .collect(Collectors.toSet()); + var winningNumbers = + Arrays.stream(numbers.split(",")) + .map(Integer::parseInt) + .limit(4) + .collect(Collectors.toSet()); final var lotteryTicketId = new LotteryTicketId(Integer.parseInt(id)); final var lotteryNumbers = LotteryNumbers.create(winningNumbers); @@ -90,15 +87,15 @@ public void submitTicket(LotteryService service, Scanner scanner) { logger.info("Give 4 comma separated lottery numbers?"); var numbers = readString(scanner); try { - var chosen = Arrays.stream(numbers.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toSet()); + var chosen = + Arrays.stream(numbers.split(",")).map(Integer::parseInt).collect(Collectors.toSet()); var lotteryNumbers = LotteryNumbers.create(chosen); var lotteryTicket = new LotteryTicket(new LotteryTicketId(), details, lotteryNumbers); - service.submitTicket(lotteryTicket).ifPresentOrElse( - (id) -> logger.info("Submitted lottery ticket with id: {}", id), - () -> logger.info("Failed submitting lottery ticket - please try again.") - ); + service + .submitTicket(lotteryTicket) + .ifPresentOrElse( + (id) -> logger.info("Submitted lottery ticket with id: {}", id), + () -> logger.info("Failed submitting lottery ticket - please try again.")); } catch (Exception e) { logger.info("Failed submitting lottery ticket - please try again."); } diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/AppTest.java similarity index 94% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/AppTest.java index 1d8152b837df..1022ccb09c21 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/AppTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.hexagonal; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Unit test for simple App. - */ +import org.junit.jupiter.api.Test; + +/** Unit test for simple App. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java similarity index 98% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java index 740dea788acd..4e3c1fac7b7d 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for banking - */ +/** Tests for banking */ class InMemoryBankTest { private final WireTransfers bank = new InMemoryBank(); diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java new file mode 100644 index 000000000000..8253ac7cb279 --- /dev/null +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java @@ -0,0 +1,95 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.hexagonal.banking; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import de.flapdoodle.embed.mongo.commands.ServerAddress; +import de.flapdoodle.embed.mongo.distribution.Version; +import de.flapdoodle.embed.mongo.transitions.Mongod; +import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess; +import de.flapdoodle.reverse.TransitionWalker; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Tests for Mongo banking adapter */ +class MongoBankTest { + + private static final String TEST_DB = "lotteryDBTest"; + private static final String TEST_ACCOUNTS_COLLECTION = "testAccounts"; + + private static MongoClient mongoClient; + private static MongoDatabase mongoDatabase; + + private MongoBank mongoBank; + + private static TransitionWalker.ReachedState mongodProcess; + + private static ServerAddress serverAddress; + + @BeforeAll + static void setUp() { + mongodProcess = Mongod.instance().start(Version.Main.V7_0); + serverAddress = mongodProcess.current().getServerAddress(); + mongoClient = MongoClients.create("mongodb://" + serverAddress.toString()); + mongoClient.startSession(); + mongoDatabase = mongoClient.getDatabase(TEST_DB); + } + + @AfterAll + static void tearDown() { + mongoClient.close(); + mongodProcess.close(); + } + + @BeforeEach + void init() { + System.setProperty("mongo-host", serverAddress.getHost()); + System.setProperty("mongo-port", String.valueOf(serverAddress.getPort())); + mongoDatabase.drop(); + mongoBank = new MongoBank(mongoDatabase.getName(), TEST_ACCOUNTS_COLLECTION); + } + + @Test + void testSetup() { + assertEquals(0, mongoBank.getAccountsCollection().countDocuments()); + } + + @Test + void testFundTransfers() { + assertEquals(0, mongoBank.getFunds("000-000")); + mongoBank.setFunds("000-000", 10); + assertEquals(10, mongoBank.getFunds("000-000")); + assertEquals(0, mongoBank.getFunds("111-111")); + mongoBank.transferFunds(9, "000-000", "111-111"); + assertEquals(1, mongoBank.getFunds("000-000")); + assertEquals(9, mongoBank.getFunds("111-111")); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java similarity index 97% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java index 6fa1d5a3eb5c..5d9017033edf 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java @@ -31,9 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests for {@link LotteryTicketRepository} - */ +/** Tests for {@link LotteryTicketRepository} */ class InMemoryTicketRepositoryTest { private final LotteryTicketRepository repository = new InMemoryTicketRepository(); diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java similarity index 85% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java index 9a3785f50b74..0a45adcace8b 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java @@ -37,9 +37,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -/** - * Tests for Mongo based ticket repository - */ +/** Tests for Mongo based ticket repository */ @Disabled class MongoTicketRepositoryTest { @@ -52,12 +50,13 @@ class MongoTicketRepositoryTest { @BeforeEach void init() { MongoConnectionPropertiesLoader.load(); - var mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + var mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); mongoClient.dropDatabase(TEST_DB); mongoClient.close(); - repository = new MongoTicketRepository(TEST_DB, TEST_TICKETS_COLLECTION, - TEST_COUNTERS_COLLECTION); + repository = + new MongoTicketRepository(TEST_DB, TEST_TICKETS_COLLECTION, TEST_COUNTERS_COLLECTION); } @Test @@ -86,10 +85,10 @@ void testCrudOperations() { var found = repository.findById(saved.get()); assertTrue(found.isPresent()); var ticket = found.get(); - assertEquals("foo@bar.com", ticket.getPlayerDetails().getEmail()); - assertEquals("123-123", ticket.getPlayerDetails().getBankAccount()); - assertEquals("07001234", ticket.getPlayerDetails().getPhoneNumber()); - assertEquals(original.getLotteryNumbers(), ticket.getLotteryNumbers()); + assertEquals("foo@bar.com", ticket.playerDetails().email()); + assertEquals("123-123", ticket.playerDetails().bankAccount()); + assertEquals("07001234", ticket.playerDetails().phoneNumber()); + assertEquals(original.lotteryNumbers(), ticket.lotteryNumbers()); // clear the collection repository.deleteAll(); assertEquals(0, repository.getTicketsCollection().countDocuments()); diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java similarity index 98% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java index f2690fb391a9..0aa740690802 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java @@ -32,9 +32,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link LotteryNumbers} - */ +/** Unit tests for {@link LotteryNumbers} */ class LotteryNumbersTest { @Test diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java similarity index 80% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java index d7f4e951dabf..c71f887882ad 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java @@ -39,18 +39,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test the lottery system - */ +/** Test the lottery system */ class LotteryTest { private final Injector injector; - @Inject - private LotteryAdministration administration; - @Inject - private LotteryService service; - @Inject - private WireTransfers wireTransfers; + @Inject private LotteryAdministration administration; + @Inject private LotteryService service; + @Inject private WireTransfers wireTransfers; LotteryTest() { this.injector = Guice.createInjector(new LotteryTestingModule()); @@ -70,14 +65,20 @@ void testLottery() { assertEquals(0, administration.getAllSubmittedTickets().size()); // players submit the lottery tickets - var ticket1 = service.submitTicket(LotteryTestUtils.createLotteryTicket("cvt@bbb.com", - "123-12312", "+32425255", Set.of(1, 2, 3, 4))); + var ticket1 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "cvt@bbb.com", "123-12312", "+32425255", Set.of(1, 2, 3, 4))); assertTrue(ticket1.isPresent()); - var ticket2 = service.submitTicket(LotteryTestUtils.createLotteryTicket("ant@bac.com", - "123-12312", "+32423455", Set.of(11, 12, 13, 14))); + var ticket2 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "ant@bac.com", "123-12312", "+32423455", Set.of(11, 12, 13, 14))); assertTrue(ticket2.isPresent()); - var ticket3 = service.submitTicket(LotteryTestUtils.createLotteryTicket("arg@boo.com", - "123-12312", "+32421255", Set.of(6, 8, 13, 19))); + var ticket3 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "arg@boo.com", "123-12312", "+32421255", Set.of(6, 8, 13, 19))); assertTrue(ticket3.isPresent()); assertEquals(3, administration.getAllSubmittedTickets().size()); @@ -85,8 +86,10 @@ void testLottery() { var winningNumbers = administration.performLottery(); // cheat a bit for testing sake, use winning numbers to submit another ticket - var ticket4 = service.submitTicket(LotteryTestUtils.createLotteryTicket("lucky@orb.com", - "123-12312", "+12421255", winningNumbers.getNumbers())); + var ticket4 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "lucky@orb.com", "123-12312", "+12421255", winningNumbers.getNumbers())); assertTrue(ticket4.isPresent()); assertEquals(4, administration.getAllSubmittedTickets().size()); diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java similarity index 97% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java index 6e11a95b0144..05af100dc68d 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java @@ -30,9 +30,7 @@ import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult; import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link LotteryTicketCheckResult} - */ +/** Unit tests for {@link LotteryTicketCheckResult} */ class LotteryTicketCheckResultTest { @Test diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java similarity index 97% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java index 8456e77421f6..cde988eaa6d2 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for lottery ticket id - */ +/** Tests for lottery ticket id */ class LotteryTicketIdTest { @Test diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java similarity index 98% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java index ce316b347615..9b8e400c8957 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java @@ -30,9 +30,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; -/** - * Test Lottery Tickets for equality - */ +/** Test Lottery Tickets for equality */ class LotteryTicketTest { @Test diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java similarity index 97% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java index 732a4b14cd68..266f907797fb 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link PlayerDetails} - */ +/** Unit tests for {@link PlayerDetails} */ class PlayerDetailsTest { @Test diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java similarity index 95% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java index 3216ac7ebd51..0227d9a6da6f 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -/** - * Tests for Mongo event log - */ +/** Tests for Mongo event log */ @Disabled class MongoEventLogTest { @@ -47,8 +45,9 @@ class MongoEventLogTest { @BeforeEach void init() { MongoConnectionPropertiesLoader.load(); - var mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + var mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); mongoClient.dropDatabase(TEST_DB); mongoClient.close(); mongoEventLog = new MongoEventLog(TEST_DB, TEST_EVENTS_COLLECTION); diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java similarity index 91% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java rename to hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java index 3b6babaa13d0..3002879ba468 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java @@ -30,9 +30,7 @@ import com.iluwatar.hexagonal.domain.PlayerDetails; import java.util.Set; -/** - * Utilities for lottery tests - */ +/** Utilities for lottery tests */ public class LotteryTestUtils { /** @@ -45,8 +43,8 @@ public static LotteryTicket createLotteryTicket() { /** * @return lottery ticket */ - public static LotteryTicket createLotteryTicket(String email, String account, String phone, - Set givenNumbers) { + public static LotteryTicket createLotteryTicket( + String email, String account, String phone, Set givenNumbers) { var details = new PlayerDetails(email, account, phone); var numbers = LotteryNumbers.create(givenNumbers); return new LotteryTicket(new LotteryTicketId(), details, numbers); diff --git a/hexagonal/README.md b/hexagonal/README.md deleted file mode 100644 index 754528ef7e4b..000000000000 --- a/hexagonal/README.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Hexagonal Architecture -category: Architectural -language: en -tag: - - Decoupling ---- - -## Also known as - -* Ports and Adapters -* Clean Architecture -* Onion Architecture - -## Intent -Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases. - -## Class diagram -![Hexagonal Architecture class diagram](./etc/hexagonal.png) - -## Applicability -Use Hexagonal Architecture pattern when - -* When the application needs to be independent of any frameworks -* When it is important that the application highly maintainable and fully testable - -## Tutorials - -* [Build Maintainable Systems With Hexagonal Architecture](http://java-design-patterns.com/blog/build-maintainable-systems-with-hexagonal-architecture/) - -## Real world examples - -* [Apache Isis](https://isis.apache.org/) builds generic UI and REST API directly from the underlying domain objects - -## Credits - -* [Alistair Cockburn - Hexagonal Architecture](http://alistair.cockburn.us/Hexagonal+architecture) diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java deleted file mode 100644 index 6549e10dbccb..000000000000 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.hexagonal.sampledata; - -import com.iluwatar.hexagonal.banking.InMemoryBank; -import com.iluwatar.hexagonal.domain.LotteryConstants; -import com.iluwatar.hexagonal.domain.LotteryNumbers; -import com.iluwatar.hexagonal.domain.LotteryService; -import com.iluwatar.hexagonal.domain.LotteryTicket; -import com.iluwatar.hexagonal.domain.LotteryTicketId; -import com.iluwatar.hexagonal.domain.PlayerDetails; -import java.security.SecureRandom; -import java.util.AbstractMap.SimpleEntry; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Utilities for creating sample lottery tickets. - */ -public class SampleData { - - private static final List PLAYERS; - private static final SecureRandom RANDOM = new SecureRandom(); - - static { - PLAYERS = List.of( - new PlayerDetails("john@google.com", "312-342", "+3242434242"), - new PlayerDetails("mary@google.com", "234-987", "+23452346"), - new PlayerDetails("steve@google.com", "833-836", "+63457543"), - new PlayerDetails("wayne@google.com", "319-826", "+24626"), - new PlayerDetails("johnie@google.com", "983-322", "+3635635"), - new PlayerDetails("andy@google.com", "934-734", "+0898245"), - new PlayerDetails("richard@google.com", "536-738", "+09845325"), - new PlayerDetails("kevin@google.com", "453-936", "+2423532"), - new PlayerDetails("arnold@google.com", "114-988", "+5646346524"), - new PlayerDetails("ian@google.com", "663-765", "+928394235"), - new PlayerDetails("robin@google.com", "334-763", "+35448"), - new PlayerDetails("ted@google.com", "735-964", "+98752345"), - new PlayerDetails("larry@google.com", "734-853", "+043842423"), - new PlayerDetails("calvin@google.com", "334-746", "+73294135"), - new PlayerDetails("jacob@google.com", "444-766", "+358042354"), - new PlayerDetails("edwin@google.com", "895-345", "+9752435"), - new PlayerDetails("mary@google.com", "760-009", "+34203542"), - new PlayerDetails("lolita@google.com", "425-907", "+9872342"), - new PlayerDetails("bruno@google.com", "023-638", "+673824122"), - new PlayerDetails("peter@google.com", "335-886", "+5432503945"), - new PlayerDetails("warren@google.com", "225-946", "+9872341324"), - new PlayerDetails("monica@google.com", "265-748", "+134124"), - new PlayerDetails("ollie@google.com", "190-045", "+34453452"), - new PlayerDetails("yngwie@google.com", "241-465", "+9897641231"), - new PlayerDetails("lars@google.com", "746-936", "+42345298345"), - new PlayerDetails("bobbie@google.com", "946-384", "+79831742"), - new PlayerDetails("tyron@google.com", "310-992", "+0498837412"), - new PlayerDetails("tyrell@google.com", "032-045", "+67834134"), - new PlayerDetails("nadja@google.com", "000-346", "+498723"), - new PlayerDetails("wendy@google.com", "994-989", "+987324454"), - new PlayerDetails("luke@google.com", "546-634", "+987642435"), - new PlayerDetails("bjorn@google.com", "342-874", "+7834325"), - new PlayerDetails("lisa@google.com", "024-653", "+980742154"), - new PlayerDetails("anton@google.com", "834-935", "+876423145"), - new PlayerDetails("bruce@google.com", "284-936", "+09843212345"), - new PlayerDetails("ray@google.com", "843-073", "+678324123"), - new PlayerDetails("ron@google.com", "637-738", "+09842354"), - new PlayerDetails("xavier@google.com", "143-947", "+375245"), - new PlayerDetails("harriet@google.com", "842-404", "+131243252") - ); - var wireTransfers = new InMemoryBank(); - PLAYERS.stream() - .map(PlayerDetails::getBankAccount) - .map(e -> new SimpleEntry<>(e, RANDOM.nextInt(LotteryConstants.PLAYER_MAX_BALANCE))) - .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)) - .forEach(wireTransfers::setFunds); - } - - /** - * Inserts lottery tickets into the database based on the sample data. - */ - public static void submitTickets(LotteryService lotteryService, int numTickets) { - for (var i = 0; i < numTickets; i++) { - var randomPlayerDetails = getRandomPlayerDetails(); - var lotteryNumbers = LotteryNumbers.createRandom(); - var lotteryTicketId = new LotteryTicketId(); - var ticket = new LotteryTicket(lotteryTicketId, randomPlayerDetails, lotteryNumbers); - lotteryService.submitTicket(ticket); - } - } - - private static PlayerDetails getRandomPlayerDetails() { - return PLAYERS.get(RANDOM.nextInt(PLAYERS.size())); - } -} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java deleted file mode 100644 index 30b10ed1d60c..000000000000 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.iluwatar.hexagonal.banking; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.mongodb.MongoClientSettings; -import com.mongodb.ServerAddress; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; -import com.mongodb.client.MongoDatabase; -import de.flapdoodle.embed.mongo.MongodExecutable; -import de.flapdoodle.embed.mongo.MongodProcess; -import de.flapdoodle.embed.mongo.MongodStarter; -import de.flapdoodle.embed.mongo.config.MongodConfig; -import de.flapdoodle.embed.mongo.distribution.Version; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.List; - -/** - * Tests for Mongo banking adapter - */ -class MongoBankTest { - - private static final String TEST_DB = "lotteryDBTest"; - private static final String TEST_ACCOUNTS_COLLECTION = "testAccounts"; - private static final String TEST_HOST = "localhost"; - private static final int TEST_PORT = 27017; - - private static MongodExecutable mongodExe; - private static MongodProcess mongodProcess; - private static MongoClient mongoClient; - private static MongoDatabase mongoDatabase; - - private MongoBank mongoBank; - - @BeforeAll - static void setUp() throws Exception { - MongodStarter starter = MongodStarter.getDefaultInstance(); - MongodConfig mongodConfig = buildMongoConfig(); - - mongoClient = buildMongoClient(); - mongodExe = starter.prepare(mongodConfig); - mongodProcess = mongodExe.start(); - mongoDatabase = mongoClient.getDatabase(TEST_DB); - } - - @BeforeEach - void init() { - System.setProperty("mongo-host", TEST_HOST); - System.setProperty("mongo-port", String.valueOf(TEST_PORT)); - mongoDatabase.drop(); - mongoBank = new MongoBank(mongoDatabase.getName(), TEST_ACCOUNTS_COLLECTION); - } - - @AfterAll - static void tearDown() { - mongoClient.close(); - mongodProcess.stop(); - mongodExe.stop(); - } - - @Test - void testSetup() { - assertEquals(0, mongoBank.getAccountsCollection().countDocuments()); - } - - @Test - void testFundTransfers() { - assertEquals(0, mongoBank.getFunds("000-000")); - mongoBank.setFunds("000-000", 10); - assertEquals(10, mongoBank.getFunds("000-000")); - assertEquals(0, mongoBank.getFunds("111-111")); - mongoBank.transferFunds(9, "000-000", "111-111"); - assertEquals(1, mongoBank.getFunds("000-000")); - assertEquals(9, mongoBank.getFunds("111-111")); - } - - private static MongodConfig buildMongoConfig() { - return MongodConfig.builder() - .version(Version.Main.PRODUCTION) - .net(new de.flapdoodle.embed.mongo.config.Net(TEST_HOST, TEST_PORT, true)) - .build(); - } - - private static MongoClient buildMongoClient() { - return MongoClients.create( - MongoClientSettings.builder() - .applyToClusterSettings(builder -> builder.hosts(List.of(new ServerAddress(TEST_HOST, TEST_PORT)))) - .build() - ); - } -} diff --git a/identity-map/README.md b/identity-map/README.md index 2f864b6c8800..e2c5318bb12f 100644 --- a/identity-map/README.md +++ b/identity-map/README.md @@ -1,37 +1,41 @@ --- -title: Identity Map +title: "Identity Map Pattern in Java: Managing Object Identity for Efficiency" +shortTitle: Identity Map +description: "Learn about the Identity Map pattern in Java. This design pattern prevents duplicate objects in memory, improving performance and consistency in your applications. Explore implementation examples, benefits, and best practices." category: Behavioral language: en tag: -- Performance -- Data access + - Data access + - Decoupling + - Enterprise patterns + - Object mapping + - Persistence + - Performance --- -## Intent +## Intent of Identity Map Design Pattern -Ensures that each object gets loaded only once by keeping every loaded object in a map. -Looks up objects using the map when referring to them. +The Identity Map design pattern in Java aims to ensure that each object gets loaded only once by keeping every loaded object in a map, enhancing database performance and memory management. -## Explanation +## Detailed Explanation of Identity Map Pattern with Real-World Examples -Real world example +Real-world example -> We are writing a program which the user may use to find the records of a given person in a database. +> Imagine you are organizing a conference and have a registration desk where every attendee must check in. This scenario illustrates the Identity Map pattern in Java which prevents duplicate objects. To avoid unnecessary delays and confusion, each attendee's details are entered into a computer system the first time they check in. If the same attendee comes to the desk again, the system quickly retrieves their details without requiring them to re-submit the same information. This ensures each attendee's information is handled efficiently and consistently, similar to how the Identity Map pattern ensures that an object is loaded only once and reused throughout the application. In plain words -> Construct an Identity map which stores the records of recently searched for items in the database. When we look -> for the same record next time load it from the map do not go to the database. +> The Identity Map design pattern ensures that each unique object is loaded only once and reused from a central registry, preventing duplicate objects in an application's memory. Wikipedia says -> In the design of DBMS, the identity map pattern is a database access design pattern used to improve performance by providing -a context-specific, in-memory cache to prevent duplicate retrieval of the same object data from the database +> In the design of DBMS, the identity map pattern is a database access design pattern used to improve performance by providing a context-specific, in-memory cache to prevent duplicate retrieval of the same object data from the database. -**Programmatic Example** +## Programmatic Example of Identity Map Pattern in Java -* For the purpose of this demonstration assume we have already created a database instance **db**. -* Let's first look at the implementation of a person entity, and it's fields: +For the purpose of this demonstration in Java programming, assume we have already created a database instance, showcasing the Identity Map pattern to avoid duplicate objects in memory. + +Let's first look at the implementation of `Person` entity, and it's fields: ```java @EqualsAndHashCode(onlyExplicitlyIncluded = true) @@ -40,163 +44,174 @@ a context-specific, in-memory cache to prevent duplicate retrieval of the same o @AllArgsConstructor public final class Person implements Serializable { - private static final long serialVersionUID = 1L; - - @EqualsAndHashCode.Include - private int personNationalId; - private String name; - private long phoneNum; - - @Override - public String toString() { - - return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum; + private static final long serialVersionUID = 1L; - } + @EqualsAndHashCode.Include + private int personNationalId; + private String name; + private long phoneNum; + @Override + public String toString() { + return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum; + } } ``` -* The following is the implementation of the personFinder which is the entity that the user will utilize in order -to search for a record in our database. It has the relevant DB attached to it. It also maintains an IdentityMap -to store the recently read records. +The following is the implementation of the `PersonFinder` which is the entity that the user will utilize in order to search for a record in our database. It has the relevant DB attached to it. It also maintains an `IdentityMap` to store the recently read records. ```java @Slf4j @Getter @Setter public class PersonFinder { - private static final long serialVersionUID = 1L; - // Access to the Identity Map - private IdentityMap identityMap = new IdentityMap(); - private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); - /** - * get person corresponding to input ID. - * - * @param key : personNationalId to look for. - */ - public Person getPerson(int key) { - // Try to find person in the identity map - Person person = this.identityMap.getPerson(key); - if (person != null) { - LOGGER.info("Person found in the Map"); - return person; - } else { - // Try to find person in the database - person = this.db.find(key); - if (person != null) { - this.identityMap.addPerson(person); - LOGGER.info("Person found in DB."); - return person; - } - LOGGER.info("Person with this ID does not exist."); - return null; + private static final long serialVersionUID = 1L; + // Access to the Identity Map + private IdentityMap identityMap = new IdentityMap(); + private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + + public Person getPerson(int key) { + // Try to find person in the identity map + Person person = this.identityMap.getPerson(key); + if (person != null) { + LOGGER.info("Person found in the Map"); + return person; + } else { + // Try to find person in the database + person = this.db.find(key); + if (person != null) { + this.identityMap.addPerson(person); + LOGGER.info("Person found in DB."); + return person; + } + LOGGER.info("Person with this ID does not exist."); + return null; + } } - } } ``` -* The identity map field in the above class is simply an abstraction of a hashMap with **personNationalId** -as the keys and the corresponding person object as the value. Here is its implementation: +The identity map field in the above class is simply an abstraction of a hashMap with **personNationalId** as the keys and the corresponding person object as the value. Here is its implementation: ```java @Slf4j @Getter public class IdentityMap { - private Map personMap = new HashMap<>(); - /** - * Add person to the map. - */ - public void addPerson(Person person) { - if (!personMap.containsKey(person.getPersonNationalId())) { - personMap.put(person.getPersonNationalId(), person); - } else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes. - LOGGER.info("Key already in Map"); - } - } - - /** - * Get Person with given id. - * - * @param id : personNationalId as requested by user. - */ - public Person getPerson(int id) { - Person person = personMap.get(id); - if (person == null) { - LOGGER.info("ID not in Map."); + private Map personMap = new HashMap<>(); + + public void addPerson(Person person) { + if (!personMap.containsKey(person.getPersonNationalId())) { + personMap.put(person.getPersonNationalId(), person); + } else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes. + LOGGER.info("Key already in Map"); + } } - return person; - } - - /** - * Get the size of the map. - */ - public int size() { - if (personMap == null) { - return 0; + + public Person getPerson(int id) { + Person person = personMap.get(id); + if (person == null) { + LOGGER.info("ID not in Map."); + } + return person; } - return personMap.size(); - } + public int size() { + if (personMap == null) { + return 0; + } + return personMap.size(); + } } - ``` -* Now we should construct a dummy person for demonstration purposes and put that person in our database. +Now, let's see how the identity map works in our `App`'s `main` function. ```java - Person person1 = new Person(1, "John", 27304159); - db.insert(person1); +public static void main(String[] args) { + + // Dummy Persons + Person person1 = new Person(1, "John", 27304159); + Person person2 = new Person(2, "Thomas", 42273631); + Person person3 = new Person(3, "Arthur", 27489171); + Person person4 = new Person(4, "Finn", 20499078); + Person person5 = new Person(5, "Michael", 40599078); + + // Init database + PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + db.insert(person1); + db.insert(person2); + db.insert(person3); + db.insert(person4); + db.insert(person5); + + // Init a personFinder + PersonFinder finder = new PersonFinder(); + finder.setDb(db); + + // Find persons in DataBase not the map. + LOGGER.info(finder.getPerson(2).toString()); + LOGGER.info(finder.getPerson(4).toString()); + LOGGER.info(finder.getPerson(5).toString()); + // Find the person in the map. + LOGGER.info(finder.getPerson(2).toString()); +} ``` -* Now let's create a person finder object and look for person with personNationalId = 1(assume that the personFinder -object already has the db and an IdentityMap attached to it.): - -```java - PersonFinder finder = new PersonFinder(); - finder.getPerson(1); +Running the example produces the following console output: + +``` +11:19:43.775 [main] INFO com.iluwatar.identitymap.IdentityMap -- ID not in Map. +11:19:43.780 [main] INFO com.iluwatar.identitymap.PersonDbSimulatorImplementation -- Person ID is : 2 ; Person Name is : Thomas ; Phone Number is :42273631 +11:19:43.780 [main] INFO com.iluwatar.identitymap.PersonFinder -- Person found in DB. +11:19:43.780 [main] INFO com.iluwatar.identitymap.App -- Person ID is : 2 ; Person Name is : Thomas ; Phone Number is :42273631 +11:19:43.780 [main] INFO com.iluwatar.identitymap.IdentityMap -- ID not in Map. +11:19:43.780 [main] INFO com.iluwatar.identitymap.PersonDbSimulatorImplementation -- Person ID is : 4 ; Person Name is : Finn ; Phone Number is :20499078 +11:19:43.780 [main] INFO com.iluwatar.identitymap.PersonFinder -- Person found in DB. +11:19:43.780 [main] INFO com.iluwatar.identitymap.App -- Person ID is : 4 ; Person Name is : Finn ; Phone Number is :20499078 +11:19:43.780 [main] INFO com.iluwatar.identitymap.IdentityMap -- ID not in Map. +11:19:43.780 [main] INFO com.iluwatar.identitymap.PersonDbSimulatorImplementation -- Person ID is : 5 ; Person Name is : Michael ; Phone Number is :40599078 +11:19:43.780 [main] INFO com.iluwatar.identitymap.PersonFinder -- Person found in DB. +11:19:43.780 [main] INFO com.iluwatar.identitymap.App -- Person ID is : 5 ; Person Name is : Michael ; Phone Number is :40599078 +11:19:43.780 [main] INFO com.iluwatar.identitymap.IdentityMap -- Person ID is : 2 ; Person Name is : Thomas ; Phone Number is :42273631 +11:19:43.780 [main] INFO com.iluwatar.identitymap.PersonFinder -- Person found in the Map +11:19:43.780 [main] INFO com.iluwatar.identitymap.App -- Person ID is : 2 ; Person Name is : Thomas ; Phone Number is :42273631 ``` -* At this stage this record will be loaded from the database and the output would be: +## When to Use the Identity Map Pattern in Java -```java - ID not in Map. - Person ID is:1;Person Name is:John;Phone Number is:27304159 - Person found in DB. -``` +The Identity Map design pattern is used in Java applications where multiple accesses to the same data occur within a single session or transaction, ensuring efficient object mapping and consistency. -* However, the next we search for this record again we will find it in the map hence we will not need to go -to the database. +## Identity Map Pattern Java Tutorials -```java - Person ID is:1;Person Name is:John;Phone Number is:27304159 - Person found in Map. -``` +* [Identity Map Pattern (Source Code Examples)](https://www.sourcecodeexamples.net/2018/04/identity-map-pattern.html) -* If the corresponding record is not in the DB at all then an Exception is thrown. Here is its implementation. +## Real-World Applications of Identity Map Pattern in Java -```java -public class IdNotFoundException extends RuntimeException { - public IdNotFoundException(final String message) { - super(message); - } -} -``` -## Class diagram +* ORM (Object-Relational Mapping) frameworks in Java often implement Identity Maps to handle database interactions more efficiently, demonstrating the pattern’s importance in Java design patterns. +* Enterprise applications to maintain consistent data states across different business processes. + +## Benefits and Trade-offs of Identity Map Pattern + +Benefits: + +* Reduces memory usage by ensuring that only one copy of each object resides in memory. +* Prevents inconsistencies during updates, as all parts of the application refer to the same instance. +* Improves performance by avoiding repeated database reads for the same data. + +Trade-offs: -![alt text](./etc/IdentityMap.png "Identity Map Pattern") +* Increases complexity in object management and persistence logic. +* Can lead to stale data if not managed correctly, especially in concurrent environments. -## Applicability +## Related Java Design Patterns -* The idea behind the Identity Map pattern is that every time we read a record from the database, - we first check the Identity Map to see if the record has already been retrieved. - This allows us to simply return a new reference to the in-memory record rather than creating a new object, - maintaining referential integrity. -* A secondary benefit to the Identity Map is that, since it acts as a cache, - it reduces the number of database calls needed to retrieve objects, which yields a performance enhancement. +* [Data Mapper](https://java-design-patterns.com/patterns/data-mapper/): Separates persistence logic from domain logic. Identity Map can be used by a Data Mapper to ensure that each object is loaded only once, enhancing performance and data consistency. +* [Unit of Work](https://java-design-patterns.com/patterns/unit-of-work/): Coordinates the actions of multiple objects by keeping track of changes and handling transactional consistency. Identity Map is used within the Unit of Work to track the objects being affected by a transaction. -## Credits +## References and Credits -* [Identity Map](https://www.sourcecodeexamples.net/2018/04/identity-map-pattern.html) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Java Persistence with Hibernate](https://amzn.to/4aUfyhd) +* [Pro Java EE Spring Patterns: Best Practices and Design Strategies Implementing Java EE Patterns with the Spring Framework](https://amzn.to/49YQN24) diff --git a/identity-map/pom.xml b/identity-map/pom.xml index 5125e1130278..cf151c1ffb0a 100644 --- a/identity-map/pom.xml +++ b/identity-map/pom.xml @@ -37,13 +37,16 @@ identity-map - org.junit.jupiter - junit-jupiter-api - test + org.slf4j + slf4j-api - junit - junit + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/App.java b/identity-map/src/main/java/com/iluwatar/identitymap/App.java index 1d0a32256b8d..e3538f4b3f83 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/App.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/App.java @@ -27,12 +27,12 @@ import lombok.extern.slf4j.Slf4j; /** - * The basic idea behind the Identity Map is to have a series of maps containing objects that have been pulled from the database. - * The below example demonstrates the identity map pattern by creating a sample DB. - * Since only 1 DB has been created we only have 1 map corresponding to it for the purpose of this demo. - * When you load an object from the database, you first check the map. - * If there’s an object in it that corresponds to the one you’re loading, you return it. If not, you go to the database, - * putting the objects on the map for future reference as you load them. + * The basic idea behind the Identity Map is to have a series of maps containing objects that have + * been pulled from the database. The below example demonstrates the identity map pattern by + * creating a sample DB. Since only 1 DB has been created we only have 1 map corresponding to it for + * the purpose of this demo. When you load an object from the database, you first check the map. If + * there’s an object in it that corresponds to the one you’re loading, you return it. If not, you go + * to the database, putting the objects on the map for future reference as you load them. */ @Slf4j public class App { @@ -68,6 +68,5 @@ public static void main(String[] args) { LOGGER.info(finder.getPerson(5).toString()); // Find the person in the map. LOGGER.info(finder.getPerson(2).toString()); - } } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java b/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java index a3246189bcc1..9a02108545a1 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.identitymap; -/** - * Using Runtime Exception to control the flow in case Person Id doesn not exist. - */ +/** Using Runtime Exception to control the flow in case Person Id doesn't exist. */ public class IdNotFoundException extends RuntimeException { public IdNotFoundException(final String message) { super(message); diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java b/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java index 633dff35d703..7543cfb0bc61 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java @@ -30,20 +30,20 @@ import lombok.extern.slf4j.Slf4j; /** - * This class stores the map into which we will be caching records after loading them from a DataBase. - * Stores the records as a Hash Map with the personNationalIDs as keys. + * This class stores the map into which we will be caching records after loading them from a + * DataBase. Stores the records as a Hash Map with the personNationalIDs as keys. */ @Slf4j @Getter public class IdentityMap { private Map personMap = new HashMap<>(); - /** - * Add person to the map. - */ + + /** Add person to the map. */ public void addPerson(Person person) { if (!personMap.containsKey(person.getPersonNationalId())) { personMap.put(person.getPersonNationalId(), person); - } else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes. + } else { // Ensure that addPerson does not update a record. This situation will never arise in + // our implementation. Added only for testing purposes. LOGGER.info("Key already in Map"); } } @@ -63,14 +63,11 @@ public Person getPerson(int id) { return person; } - /** - * Get the size of the map. - */ + /** Get the size of the map. */ public int size() { if (personMap == null) { return 0; } return personMap.size(); } - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/Person.java b/identity-map/src/main/java/com/iluwatar/identitymap/Person.java index d15e7faf695d..bc14bff50451 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/Person.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/Person.java @@ -24,33 +24,34 @@ */ package com.iluwatar.identitymap; +import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; -/** - * Person definition. - */ +/** Person definition. */ @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Getter @Setter @AllArgsConstructor public final class Person implements Serializable { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; - @EqualsAndHashCode.Include - private int personNationalId; + @EqualsAndHashCode.Include private int personNationalId; private String name; private long phoneNum; @Override public String toString() { - return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum; - + return "Person ID is : " + + personNationalId + + " ; Person Name is : " + + name + + " ; Phone Number is :" + + phoneNum; } - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java index 7cee5e06a95f..8a2d7481ddd2 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java @@ -24,9 +24,7 @@ */ package com.iluwatar.identitymap; -/** - * Simulator interface for Person DB. - */ +/** Simulator interface for Person DB. */ public interface PersonDbSimulator { Person find(int personNationalId); @@ -35,5 +33,4 @@ public interface PersonDbSimulator { void update(Person person); void delete(int personNationalId); - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java index 22abb44f2da4..e6c4be3eb89b 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java @@ -30,26 +30,26 @@ import lombok.extern.slf4j.Slf4j; /** - * This is a sample database implementation. The database is in the form of an arraylist which stores records of - * different persons. The personNationalId acts as the primary key for a record. - * Operations : - * -> find (look for object with a particular ID) - * -> insert (insert record for a new person into the database) - * -> update (update the record of a person). To do this, create a new person instance with the same ID as the record you - * want to update. Then call this method with that person as an argument. - * -> delete (delete the record for a particular ID) + * This is a sample database implementation. The database is in the form of an arraylist which + * stores records of different persons. The personNationalId acts as the primary key for a record. + * Operations : -> find (look for object with a particular ID) -> insert (insert record for a new + * person into the database) -> update (update the record of a person). To do this, create a new + * person instance with the same ID as the record you want to update. Then call this method with + * that person as an argument. -> delete (delete the record for a particular ID) */ @Slf4j public class PersonDbSimulatorImplementation implements PersonDbSimulator { - //This simulates a table in the database. To extend logic to multiple tables just add more lists to the implementation. + // This simulates a table in the database. To extend logic to multiple tables just add more lists + // to the implementation. private List personList = new ArrayList<>(); static final String NOT_IN_DATA_BASE = " not in DataBase"; static final String ID_STR = "ID : "; @Override public Person find(int personNationalId) throws IdNotFoundException { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == personNationalId).findFirst(); + Optional elem = + personList.stream().filter(p -> p.getPersonNationalId() == personNationalId).findFirst(); if (elem.isEmpty()) { throw new IdNotFoundException(ID_STR + personNationalId + NOT_IN_DATA_BASE); } @@ -59,7 +59,10 @@ public Person find(int personNationalId) throws IdNotFoundException { @Override public void insert(Person person) { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst(); + Optional elem = + personList.stream() + .filter(p -> p.getPersonNationalId() == person.getPersonNationalId()) + .findFirst(); if (elem.isPresent()) { LOGGER.info("Record already exists."); return; @@ -69,7 +72,10 @@ public void insert(Person person) { @Override public void update(Person person) throws IdNotFoundException { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst(); + Optional elem = + personList.stream() + .filter(p -> p.getPersonNationalId() == person.getPersonNationalId()) + .findFirst(); if (elem.isPresent()) { elem.get().setName(person.getName()); elem.get().setPhoneNum(person.getPhoneNum()); @@ -85,7 +91,8 @@ public void update(Person person) throws IdNotFoundException { * @param id : personNationalId for person whose record is to be deleted. */ public void delete(int id) throws IdNotFoundException { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == id).findFirst(); + Optional elem = + personList.stream().filter(p -> p.getPersonNationalId() == id).findFirst(); if (elem.isPresent()) { personList.remove(elem.get()); LOGGER.info("Record deleted successfully."); @@ -94,14 +101,11 @@ public void delete(int id) throws IdNotFoundException { throw new IdNotFoundException(ID_STR + id + NOT_IN_DATA_BASE); } - /** - * Return the size of the database. - */ + /** Return the size of the database. */ public int size() { if (personList == null) { return 0; } return personList.size(); } - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java index d358e24e4857..35cbcf84ea67 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java @@ -29,11 +29,11 @@ import lombok.extern.slf4j.Slf4j; /** - * Any object of this class stores a DataBase and an Identity Map. When we try to look for a key we first check if - * it has been cached in the Identity Map and return it if it is indeed in the map. - * If that is not the case then go to the DataBase, get the record, store it in the - * Identity Map and then return the record. Now if we look for the record again we will find it in the table itself which - * will make lookup faster. + * Any object of this class stores a DataBase and an Identity Map. When we try to look for a key we + * first check if it has been cached in the Identity Map and return it if it is indeed in the map. + * If that is not the case then go to the DataBase, get the record, store it in the Identity Map and + * then return the record. Now if we look for the record again we will find it in the table itself + * which will make lookup faster. */ @Slf4j @Getter @@ -43,6 +43,7 @@ public class PersonFinder { // Access to the Identity Map private IdentityMap identityMap = new IdentityMap(); private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + /** * get person corresponding to input ID. * diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java index b946a44a602e..8ef0e78d6078 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java @@ -23,13 +23,14 @@ * THE SOFTWARE. */ package com.iluwatar.identitymap; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - class AppTest { +import org.junit.jupiter.api.Test; + +class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java index 74bbe3d412f5..98289e6d5ec7 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java @@ -29,7 +29,7 @@ class IdentityMapTest { @Test - void addToMap(){ + void addToMap() { // new instance of an identity map(not connected to any DB here) IdentityMap idMap = new IdentityMap(); // Dummy person instances @@ -46,10 +46,12 @@ void addToMap(){ idMap.addPerson(person4); idMap.addPerson(person5); // Test no duplicate in our Map. - Assertions.assertEquals(4,idMap.size(),"Size of the map is incorrect"); + Assertions.assertEquals(4, idMap.size(), "Size of the map is incorrect"); // Test record not updated by add method. - Assertions.assertEquals(27304159,idMap.getPerson(11).getPhoneNum(),"Incorrect return value for phone number"); + Assertions.assertEquals( + 27304159, idMap.getPerson(11).getPhoneNum(), "Incorrect return value for phone number"); } + @Test void testGetFromMap() { // new instance of an identity map(not connected to any DB here) @@ -67,8 +69,8 @@ void testGetFromMap() { idMap.addPerson(person4); idMap.addPerson(person5); // Test for dummy persons in the map - Assertions.assertEquals(person1,idMap.getPerson(11),"Incorrect person record returned"); - Assertions.assertEquals(person4,idMap.getPerson(44),"Incorrect person record returned"); + Assertions.assertEquals(person1, idMap.getPerson(11), "Incorrect person record returned"); + Assertions.assertEquals(person4, idMap.getPerson(44), "Incorrect person record returned"); // Test for person with given id not in map Assertions.assertNull(idMap.getPerson(1), "Incorrect person record returned"); } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java index 6823f39193cb..7861cb2f6200 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java @@ -29,10 +29,10 @@ class PersonDbSimulatorImplementationTest { @Test - void testInsert(){ + void testInsert() { // DataBase initialization. PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); - Assertions.assertEquals(0,db.size(),"Size of null database should be 0"); + Assertions.assertEquals(0, db.size(), "Size of null database should be 0"); // Dummy persons. Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); @@ -41,71 +41,77 @@ void testInsert(){ db.insert(person2); db.insert(person3); // Test size after insertion. - Assertions.assertEquals(3,db.size(),"Incorrect size for database."); + Assertions.assertEquals(3, db.size(), "Incorrect size for database."); Person person4 = new Person(4, "Finn", 20499078); Person person5 = new Person(5, "Michael", 40599078); db.insert(person4); db.insert(person5); // Test size after more insertions. - Assertions.assertEquals(5,db.size(),"Incorrect size for database."); - Person person5duplicate = new Person(5,"Kevin",89589122); + Assertions.assertEquals(5, db.size(), "Incorrect size for database."); + Person person5duplicate = new Person(5, "Kevin", 89589122); db.insert(person5duplicate); // Test size after attempt to insert record with duplicate key. - Assertions.assertEquals(5,db.size(),"Incorrect size for data base"); + Assertions.assertEquals(5, db.size(), "Incorrect size for data base"); } + @Test - void findNotInDb(){ + void findNotInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); // Test if IdNotFoundException is thrown where expected. - Assertions.assertThrows(IdNotFoundException.class,()->db.find(3)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.find(3)); } + @Test - void findInDb(){ + void findInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); - Assertions.assertEquals(person2,db.find(2),"Record that was found was incorrect."); + Assertions.assertEquals(person2, db.find(2), "Record that was found was incorrect."); } + @Test - void updateNotInDb(){ + void updateNotInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); - Person person3 = new Person(3,"Micheal",25671234); + Person person3 = new Person(3, "Micheal", 25671234); // Test if IdNotFoundException is thrown when person with ID 3 is not in DB. - Assertions.assertThrows(IdNotFoundException.class,()->db.update(person3)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.update(person3)); } + @Test - void updateInDb(){ + void updateInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); - Person person = new Person(2,"Thomas",42273690); + Person person = new Person(2, "Thomas", 42273690); db.update(person); - Assertions.assertEquals(person,db.find(2),"Incorrect update."); + Assertions.assertEquals(person, db.find(2), "Incorrect update."); } + @Test - void deleteNotInDb(){ + void deleteNotInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); // Test if IdNotFoundException is thrown when person with this ID not in DB. - Assertions.assertThrows(IdNotFoundException.class,()->db.delete(3)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.delete(3)); } + @Test - void deleteInDb(){ + void deleteInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); @@ -114,9 +120,8 @@ void deleteInDb(){ // delete the record. db.delete(1); // test size of database after deletion. - Assertions.assertEquals(1,db.size(),"Size after deletion is incorrect."); + Assertions.assertEquals(1, db.size(), "Size after deletion is incorrect."); // try to find deleted record in db. - Assertions.assertThrows(IdNotFoundException.class,()->db.find(1)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.find(1)); } - } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java index 88f70604844b..9257a3b0a28c 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java @@ -29,7 +29,7 @@ class PersonFinderTest { @Test - void personFoundInDB(){ + void personFoundInDB() { // personFinderInstance PersonFinder personFinder = new PersonFinder(); // init database for our personFinder @@ -48,14 +48,20 @@ void personFoundInDB(){ db.insert(person5); personFinder.setDb(db); - Assertions.assertEquals(person1,personFinder.getPerson(1),"Find person returns incorrect record."); - Assertions.assertEquals(person3,personFinder.getPerson(3),"Find person returns incorrect record."); - Assertions.assertEquals(person2,personFinder.getPerson(2),"Find person returns incorrect record."); - Assertions.assertEquals(person5,personFinder.getPerson(5),"Find person returns incorrect record."); - Assertions.assertEquals(person4,personFinder.getPerson(4),"Find person returns incorrect record."); + Assertions.assertEquals( + person1, personFinder.getPerson(1), "Find person returns incorrect record."); + Assertions.assertEquals( + person3, personFinder.getPerson(3), "Find person returns incorrect record."); + Assertions.assertEquals( + person2, personFinder.getPerson(2), "Find person returns incorrect record."); + Assertions.assertEquals( + person5, personFinder.getPerson(5), "Find person returns incorrect record."); + Assertions.assertEquals( + person4, personFinder.getPerson(4), "Find person returns incorrect record."); } + @Test - void personFoundInIdMap(){ + void personFoundInIdMap() { // personFinderInstance PersonFinder personFinder = new PersonFinder(); // init database for our personFinder @@ -76,19 +82,20 @@ void personFoundInIdMap(){ // Assure key is not in the ID map. Assertions.assertFalse(personFinder.getIdentityMap().getPersonMap().containsKey(3)); // Assure key is in the database. - Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record."); + Assertions.assertEquals(person3, personFinder.getPerson(3), "Finder returns incorrect record."); // Assure that the record for this key is cached in the Map now. Assertions.assertTrue(personFinder.getIdentityMap().getPersonMap().containsKey(3)); // Find the record again. This time it will be found in the map. - Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record."); + Assertions.assertEquals(person3, personFinder.getPerson(3), "Finder returns incorrect record."); } + @Test - void personNotFoundInDB(){ + void personNotFoundInDB() { PersonFinder personFinder = new PersonFinder(); // init database for our personFinder PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); personFinder.setDb(db); - Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(1)); + Assertions.assertThrows(IdNotFoundException.class, () -> personFinder.getPerson(1)); // Dummy persons Person person1 = new Person(1, "John", 27304159); Person person2 = new Person(2, "Thomas", 42273631); @@ -102,11 +109,10 @@ void personNotFoundInDB(){ db.insert(person5); personFinder.setDb(db); // Assure that the database has been updated. - Assertions.assertEquals(person4,personFinder.getPerson(4),"Find returns incorrect record"); + Assertions.assertEquals(person4, personFinder.getPerson(4), "Find returns incorrect record"); // Assure key is in DB now. - Assertions.assertDoesNotThrow(()->personFinder.getPerson(1)); + Assertions.assertDoesNotThrow(() -> personFinder.getPerson(1)); // Assure key not in DB. - Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(6)); - + Assertions.assertThrows(IdNotFoundException.class, () -> personFinder.getPerson(6)); } } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java index 7c491bf597f5..8199daaa2a62 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java @@ -29,15 +29,15 @@ class PersonTest { @Test - void testEquality(){ + void testEquality() { // dummy persons. - Person person1 = new Person(1,"Harry",989950022); - Person person2 = new Person(2,"Kane",989920011); - Assertions.assertNotEquals(person1,person2,"Incorrect equality condition"); + Person person1 = new Person(1, "Harry", 989950022); + Person person2 = new Person(2, "Kane", 989920011); + Assertions.assertNotEquals(person1, person2, "Incorrect equality condition"); // person with duplicate nationalID. - Person person3 = new Person(2,"John",789012211); + Person person3 = new Person(2, "John", 789012211); // If nationalID is equal then persons are equal(even if name or phoneNum are different). // This situation will never arise in this implementation. Only for testing. - Assertions.assertEquals(person2,person3,"Incorrect inequality condition"); + Assertions.assertEquals(person2, person3, "Incorrect inequality condition"); } } diff --git a/intercepting-filter/README.md b/intercepting-filter/README.md index 3883fcd03355..c6f6723f534f 100644 --- a/intercepting-filter/README.md +++ b/intercepting-filter/README.md @@ -1,173 +1,164 @@ --- -title: Intercepting Filter -category: Behavioral +title: "Intercepting Filter Pattern in Java: Enhancing Request Processing in Web Applications" +shortTitle: Intercepting Filter +description: "Learn about the Intercepting Filter Pattern in Java. Discover how to design, implement, and use this pattern to enhance web request handling with practical examples and detailed explanations." +category: Architectural language: en tag: - - Decoupling + - API design + - Decoupling + - Layered architecture + - Performance + - Security + - Web development --- -## Intent -An intercepting filter is a useful Java Design Pattern used when you want to pre-process -or post-process a request in an application. These filters are created and applied to the -request before it is given to the target application. Such examples of uses include authentication, -which is necessary to be processed before the request is given to the application. +## Intent of Intercepting Filter Design Pattern -## Explanation -Real-world Example -> An example of using the Intercepting Filter design pattern is relevant when making an ecommerce -> platform. It is important to implement various filters for authentication of account, authentication -> of payment, logging and caching. Important types of filters in this example are authentication, logging, -> security, and caching filters. +The Intercepting Filter Pattern in Java is a powerful design pattern that allows for efficient web request handling. This pattern enables the application of multiple filters in a filter chain to process and modify requests and responses. + +## Detailed Explanation of Intercepting Filter Pattern with Real-World Examples + +Real-world example + +> Consider entering a secure office building where you pass through several checkpoints: a security desk checks your ID, a metal detector ensures safety, and a registration desk logs your visit. Each checkpoint acts like a filter in the Intercepting Filter pattern, processing and validating your entry step-by-step, similar to how filters handle different aspects of web requests and responses in a software system. In plain words -> An intercepting filter in Java is like a series of security checkpoints for requests and responses in a -> software application. It checks and processes data as it comes in and goes out, helping with tasks like -> authentication, logging, and security, while keeping the core application safe and clean. + +> The Intercepting Filter design pattern allows you to define processing steps (filters) that execute sequentially to handle and modify web requests and responses before they reach the application or are sent to the client. Wikipedia says -> Intercepting Filter is a Java pattern which creates pluggable filters to process common services in a -> standard manner without requiring changes to core request processing code. -## Programmatic Example -As an example, we can create a basic Filter class and define an Authentication Filter. The filter has missing logic, -but +> Intercepting Filter is a Java pattern which creates pluggable filters to process common services in a standard manner without requiring changes to core request processing code. + +Architecture diagram + +![Intercepting Filter Architecture Diagram](./etc/intercepting-filter-architecture-diagram.png) + +## Programmatic Example of Intercepting Filter Pattern in Java + +In this article, we delve into the Intercepting Filter Pattern and provide a Java example to illustrate its use. This pattern is essential for Java web development, offering a modular approach to handling common services such as logging, authentication, and data compression. + +The Java implementation of the Intercepting Filter Pattern includes classes like `FilterManager` and `Client`, which facilitate the management and application of filters. Each filter in the chain performs specific tasks, ensuring a clean and efficient design. + +The `App` class is the entry point of the application. It creates an instance of `FilterManager`, adds various filters to it, and sets it to a `Client`. + ```java -// 1. Define a Filter interface -interface Filter { - void runFilter(String request); +public class App { + + public static void main(String[] args) { + var filterManager = new FilterManager(); + filterManager.addFilter(new NameFilter()); + filterManager.addFilter(new ContactFilter()); + filterManager.addFilter(new AddressFilter()); + filterManager.addFilter(new DepositFilter()); + filterManager.addFilter(new OrderFilter()); + + var client = new Client(); + client.setFilterManager(filterManager); + } } -// 2. Create a Authentication filter -class AuthenticationFilter implements Filter { - public void runFilter(String request) { - // Authentication logic would be passed in here - if (request.contains("authenticated=true")) { - System.out.println("Authentication successful for request: " + request); - } else { - System.out.println("Authentication failed for request: " + request); - } - } -} -// 3. Create a Client to send requests and activate the filter -class Client { - // create an instance of the filter in the Client class - private Filter filter; - - // create constructor - public Client(Filter filter) { - this.filter = filter; - } +``` - // send the String request to the filter, the request does not have to be a string - // it can be anything - public void sendRequest(String request) { - filter.runFilter(request); +The `FilterManager` class manages the filters and applies them to the requests. + +```java +public class FilterManager { + private final List filters = new ArrayList<>(); + + public void addFilter(Filter filter) { + filters.add(filter); + } + + public void filterRequest(String request) { + for (Filter filter : filters) { + filter.execute(request); } + } } -// 4. Demonstrate the Authentication Filter -public class AuthenticationFilterExample { - public static void main(String[] args) { - Filter authenticationFilter = new AuthenticationFilter(); - Client client = new Client(authenticationFilter); - - // Simulate requests for false - client.sendRequest("GET /public-page"); - // this request would come back as true as the link includes an argument - // for successful authentication - client.sendRequest("GET /private-page?authenticated=true"); - } +``` + +The `Client` class sends the request to the `FilterManager`. + +```java +public class Client { + private FilterManager filterManager; + + public void setFilterManager(FilterManager filterManager) { + this.filterManager = filterManager; + } + + public void sendRequest(String request) { + filterManager.filterRequest(request); + } } ``` -This is a basic example of how to implement the skeleton of a filter. The authentication logic in AuthenticationFilterExample is missing, but can be filled into the gaps. -Additionally, the client can be setup to run multiple filters on its request using a For loop populated with filters as can be seen below: +The `Filter` interface and its implementations (`NameFilter`, `ContactFilter`, `AddressFilter`, `DepositFilter`, `OrderFilter`) define the filters that can be applied to the requests. + ```java -// 1. Define a Filter interface -interface Filter { - void runFilter(String request); +public interface Filter { + void execute(String request); } -// 2. Create an Authentication filter -class AuthenticationFilter implements Filter { - public void runFilter(String request) { - // Authentication logic would be placed here - if (request.contains("authenticated=true")) { - System.out.println("Authentication successful for request: " + request); +public class NameFilter extends AbstractFilter { + + @Override + public String execute(Order order) { + var result = super.execute(order); + var name = order.getName(); + if (name == null || name.isEmpty() || name.matches(".*[^\\w|\\s]+.*")) { + return result + "Invalid name! "; } else { - System.out.println("Authentication failed for request: " + request); + return result; } } } -// 3. Create a Client to send requests and activate multiple filters -class Client { - // create a list of filters in the Client class - private List filters = new ArrayList<>(); +// Other Filter implementations... +``` - // add filters to the list - public void addFilter(Filter filter) { - filters.add(filter); - } +In this example, the `App` class sets up a `FilterManager` with various filters and assigns it to a `Client`. When the `Client` sends a request, the `FilterManager` applies all the filters to the request. This is a basic example of the Intercepting Filter pattern, where common processing tasks are encapsulated in filters and applied to requests in a standard manner. - // send the request through all the filters - public void sendRequest(String request) { - for (Filter filter : filters) { - filter.runFilter(request); - } - } -} +## When to Use the Intercepting Filter Pattern in Java -// 4. Demonstrate multiple filters -public class MultipleFiltersExample { - public static void main(String[] args) { - // Create a client - Client client = new Client(); +Use the Intercepting Filter pattern when - // Add filters to the client - Filter authenticationFilter = new AuthenticationFilter(); - client.addFilter(authenticationFilter); +* In Java web applications to manage cross-cutting concerns. +* When you need to apply pre-processing and post-processing steps to requests and responses, typically in web applications. +* Suitable for handling cross-cutting concerns such as logging, authentication, data compression, and encryption transparently. - // Add more filters as needed - // Filter anotherFilter = new AnotherFilter(); - // client.addFilter(anotherFilter); +## Intercepting Filter Pattern Java Tutorials - // Simulate requests - client.sendRequest("GET /public-page"); - client.sendRequest("GET /private-page?authenticated=true"); - } -} -``` -This method allows quick and easy manipulation and checking of data before authenticating a login or finishing some other sort of action. +* [Introduction to Intercepting Filter Pattern in Java (Baeldung)](https://www.baeldung.com/intercepting-filter-pattern-in-java) +* [Design Pattern - Intercepting Filter Pattern (TutorialsPoint)](http://www.tutorialspoint.com/design_pattern/intercepting_filter_pattern.htm) -## Class diagram -![alt text](./etc/intercepting-filter.png "Intercepting Filter") +## Real-World Applications of Intercepting Filter Pattern in Java -## Applicability -Use the Intercepting Filter pattern when +* Frameworks like Spring MVC and web servers such as Apache Tomcat utilize the Intercepting Filter Pattern to enhance Java web development. This pattern's ability to centralize control and streamline web request handling makes it a go-to choice for developers. +* [javax.servlet.FilterChain](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/FilterChain.html) and [javax.servlet.Filter](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/Filter.html) +* [Struts 2 - Interceptors](https://struts.apache.org/core-developers/interceptors.html) -* A program needs to pre-process or post-process data -* A system needs authorisation/authentication services to access sensitive data -* You want to log/audit requests or responses for debugging or storing purposes, such as timestamps and user actions -* You want to transform data of a type to another type before it is given to the end process -* You want to implement specific exception handling +## Benefits and Trade-offs of Intercepting Filter Pattern -## Consequences -Consequences that come with implementing Intercepting Filter +Benefits: -* Increase in code complexity, diminishing ease of readability -* Can have issues in the order that filters are applied if order is important -* Applying multiple filters to a request can create a delay in response time -* Testing the effects of multiple filters on a request can be hard -* Compatibility and version management can be difficult if you have a lot of filters +* Promotes separation of concerns by allowing filters to be independently developed, tested, and reused. +* Enhances flexibility through configurable filter chains. +* Simplifies application maintenance by centralizing control in filter management. -## Tutorials +Trade-offs: -* [Introduction to Intercepting Filter Pattern in Java](https://www.baeldung.com/intercepting-filter-pattern-in-java) +* Introducing many filters can lead to performance overhead due to the processing of each request and response through multiple filters. +* Debugging and tracing the request flow through multiple filters can be complex. -## Real world examples +## Related Java Design Patterns -* [javax.servlet.FilterChain](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/FilterChain.html) and [javax.servlet.Filter](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/Filter.html) -* [Struts 2 - Interceptors](https://struts.apache.org/core-developers/interceptors.html) +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Filters in the Intercepting Filter pattern can be considered as decorators that add additional responsibilities to request handling. They modify the request/response without altering their fundamental behavior. +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Filters are linked in a chain, where each filter processes the request or response and optionally passes it to the next filter in the chain, similar to how responsibilities are passed along in the Chain of Responsibility pattern. -## Credits +## References and Credits -* [TutorialsPoint - Intercepting Filter](http://www.tutorialspoint.com/design_pattern/intercepting_filter_pattern.htm) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/intercepting-filter/etc/intercepting-filter-architecture-diagram.png b/intercepting-filter/etc/intercepting-filter-architecture-diagram.png new file mode 100644 index 000000000000..d53b5fd67a4c Binary files /dev/null and b/intercepting-filter/etc/intercepting-filter-architecture-diagram.png differ diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java index 6972873254b2..7803ef878571 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java @@ -24,15 +24,12 @@ */ package com.iluwatar.intercepting.filter; -/** - * Base class for order processing filters. Handles chain management. - */ +/** Base class for order processing filters. Handles chain management. */ public abstract class AbstractFilter implements Filter { private Filter next; - public AbstractFilter() { - } + public AbstractFilter() {} public AbstractFilter(Filter next) { this.next = next; diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java index c584939ba4b3..0f8aeeb5567e 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java @@ -27,8 +27,6 @@ /** * Concrete implementation of filter This filter is responsible for checking/filtering the input in * the address field. - * - * @author joshzambales */ public class AddressFilter extends AbstractFilter { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java index 3cd2bd92c0d0..53559ede1a78 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java @@ -44,8 +44,6 @@ * *

In this example we check whether the order request is valid through pre-processing done via * {@link Filter}. Each field has its own corresponding {@link Filter}. - * - * @author joshzambales */ public class App { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java index ac0211050adb..96f2e2acdc6f 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java @@ -27,6 +27,7 @@ import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; +import java.io.Serial; import java.util.Arrays; import javax.swing.JButton; import javax.swing.JFrame; @@ -44,12 +45,10 @@ * *

This is where {@link Filter}s come to play as the client pre-processes the request before * being displayed in the {@link Target}. - * - * @author joshzambales */ public class Client extends JFrame { // NOSONAR - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private transient FilterManager filterManager; private final JLabel jl; @@ -58,9 +57,7 @@ public class Client extends JFrame { // NOSONAR private final JButton clearButton; private final JButton processButton; - /** - * Constructor. - */ + /** Constructor. */ public Client() { super("Client System"); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); @@ -99,10 +96,11 @@ private void setup() { panel.add(clearButton); panel.add(processButton); - clearButton.addActionListener(e -> { - Arrays.stream(jtAreas).forEach(i -> i.setText("")); - Arrays.stream(jtFields).forEach(i -> i.setText("")); - }); + clearButton.addActionListener( + e -> { + Arrays.stream(jtAreas).forEach(i -> i.setText("")); + Arrays.stream(jtFields).forEach(i -> i.setText("")); + }); processButton.addActionListener(this::actionPerformed); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java index 56f925f89b67..14b4914444d5 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java @@ -28,8 +28,6 @@ * Concrete implementation of filter This filter checks for the contact field in which it checks if * the input consist of numbers and it also checks if the input follows the length constraint (11 * digits). - * - * @author joshzambales */ public class ContactFilter extends AbstractFilter { @@ -37,7 +35,7 @@ public class ContactFilter extends AbstractFilter { public String execute(Order order) { var result = super.execute(order); var contactNumber = order.getContactNumber(); - if (contactNumber == null || contactNumber.isEmpty() + if (contactNumber == null || contactNumber.matches(".*[^\\d]+.*") || contactNumber.length() != 11) { return result + "Invalid contact number! "; diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java index d1a409b1def6..2c13d831e5ac 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java @@ -24,11 +24,7 @@ */ package com.iluwatar.intercepting.filter; -/** - * Concrete implementation of filter This checks for the deposit code. - * - * @author joshzambales - */ +/** Concrete implementation of filter This checks for the deposit code. */ public class DepositFilter extends AbstractFilter { @Override diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java index c3ca2e4cec21..fcadf86a7a4a 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java @@ -27,28 +27,18 @@ /** * Filters perform certain tasks prior or after execution of request by request handler. In this * case, before the request is handled by the target, the request undergoes through each Filter - * - * @author joshzambales */ public interface Filter { - /** - * Execute order processing filter. - */ + /** Execute order processing filter. */ String execute(Order order); - /** - * Set next filter in chain after this. - */ + /** Set next filter in chain after this. */ void setNext(Filter filter); - /** - * Get next filter in chain after this. - */ + /** Get next filter in chain after this. */ Filter getNext(); - /** - * Get last filter in the chain. - */ + /** Get last filter in the chain. */ Filter getLast(); } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java index a4d46f23c545..1fc27095a341 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java @@ -24,20 +24,12 @@ */ package com.iluwatar.intercepting.filter; - -/** - * Filter Chain carries multiple filters and help to execute them in defined order on target. - * - * @author joshzambales - */ +/** Filter Chain carries multiple filters and help to execute them in defined order on target. */ public class FilterChain { private Filter chain; - - /** - * Adds filter. - */ + /** Adds filter. */ public void addFilter(Filter filter) { if (chain == null) { chain = filter; @@ -46,9 +38,7 @@ public void addFilter(Filter filter) { } } - /** - * Execute filter chain. - */ + /** Execute filter chain. */ public String execute(Order order) { if (chain != null) { return chain.execute(order); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java index 9cd5b497d0f7..93303dd9ad60 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java @@ -24,11 +24,7 @@ */ package com.iluwatar.intercepting.filter; -/** - * Filter Manager manages the filters and {@link FilterChain}. - * - * @author joshzambales - */ +/** Filter Manager manages the filters and {@link FilterChain}. */ public class FilterManager { private final FilterChain filterChain; diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java index 0ba5d3c33b7b..19f80a4eec08 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java @@ -27,8 +27,6 @@ /** * Concrete implementation of filter. This filter checks if the input in the Name field is valid. * (alphanumeric) - * - * @author joshzambales */ public class NameFilter extends AbstractFilter { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java index dd2a9c8b59cb..576b26a4c3e1 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java @@ -24,9 +24,16 @@ */ package com.iluwatar.intercepting.filter; -/** - * Order class carries the order data. - */ +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** Order class carries the order data. */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor public class Order { private String name; @@ -34,61 +41,4 @@ public class Order { private String address; private String depositNumber; private String orderItem; - - public Order() { - } - - /** - * Constructor. - */ - public Order( - String name, String contactNumber, String address, - String depositNumber, String order - ) { - this.name = name; - this.contactNumber = contactNumber; - this.address = address; - this.depositNumber = depositNumber; - this.orderItem = order; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getContactNumber() { - return contactNumber; - } - - public void setContactNumber(String contactNumber) { - this.contactNumber = contactNumber; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getDepositNumber() { - return depositNumber; - } - - public void setDepositNumber(String depositNumber) { - this.depositNumber = depositNumber; - } - - public String getOrderItem() { - return orderItem; - } - - public void setOrderItem(String order) { - this.orderItem = order; - } } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java index fa004bb369a3..f3eed5f1a1a1 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java @@ -24,11 +24,7 @@ */ package com.iluwatar.intercepting.filter; -/** - * Concrete implementation of filter. This checks for the order field. - * - * @author joshzambales - */ +/** Concrete implementation of filter. This checks for the order field. */ public class OrderFilter extends AbstractFilter { @Override diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java index 12772ab45d6e..92b76745e1c9 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java @@ -28,6 +28,7 @@ import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.Serial; import java.util.stream.IntStream; import javax.swing.JButton; import javax.swing.JFrame; @@ -38,28 +39,23 @@ import javax.swing.WindowConstants; import javax.swing.table.DefaultTableModel; -/** - * This is where the requests are displayed after being validated by filters. - * - * @author mjoshzambales - */ -public class Target extends JFrame { //NOSONAR +/** This is where the requests are displayed after being validated by filters. */ +public class Target extends JFrame { // NOSONAR - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private final JTable jt; private final DefaultTableModel dtm; private final JButton del; - /** - * Constructor. - */ + /** Constructor. */ public Target() { super("Order System"); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setSize(640, 480); - dtm = new DefaultTableModel( - new Object[]{"Name", "Contact Number", "Address", "Deposit Number", "Order"}, 0); + dtm = + new DefaultTableModel( + new Object[] {"Name", "Contact Number", "Address", "Deposit Number", "Order"}, 0); jt = new JTable(dtm); del = new JButton("Delete"); setup(); @@ -84,7 +80,7 @@ private void setup() { } public void execute(String[] request) { - dtm.addRow(new Object[]{request[0], request[1], request[2], request[3], request[4]}); + dtm.addRow(new Object[] {request[0], request[1], request[2], request[3], request[4]}); } class TargetListener implements ActionListener { diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java index 78d4d0bdd7d9..27e5aaba2e05 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.intercepting.filter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test. - */ +import org.junit.jupiter.api.Test; + +/** Application test. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java index a3d47a72cae7..969386c823cc 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java @@ -34,11 +34,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/13/15 - 3:01 PM - * - * @author Jeroen Meulemeester - */ +/** FilterManagerTest */ class FilterManagerTest { @Test @@ -67,4 +63,4 @@ void testAddFilter() { verify(filter, times(1)).execute(any(Order.class)); verifyNoMoreInteractions(target, filter, order); } -} \ No newline at end of file +} diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java index 1d2b94c0b7d0..60b12a9302e7 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java @@ -33,11 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * Date: 12/13/15 - 2:17 PM - * - * @author Jeroen Meulemeester - */ +/** FilterTest */ class FilterTest { private static final Order PERFECT_ORDER = @@ -50,41 +46,36 @@ class FilterTest { static List getTestData() { return List.of( - new Object[]{new NameFilter(), PERFECT_ORDER, ""}, - new Object[]{new NameFilter(), WRONG_NAME, "Invalid name!"}, - new Object[]{new NameFilter(), WRONG_CONTACT, ""}, - new Object[]{new NameFilter(), WRONG_ADDRESS, ""}, - new Object[]{new NameFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new NameFilter(), WRONG_ORDER, ""}, - - new Object[]{new ContactFilter(), PERFECT_ORDER, ""}, - new Object[]{new ContactFilter(), WRONG_NAME, ""}, - new Object[]{new ContactFilter(), WRONG_CONTACT, "Invalid contact number!"}, - new Object[]{new ContactFilter(), WRONG_ADDRESS, ""}, - new Object[]{new ContactFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new ContactFilter(), WRONG_ORDER, ""}, - - new Object[]{new AddressFilter(), PERFECT_ORDER, ""}, - new Object[]{new AddressFilter(), WRONG_NAME, ""}, - new Object[]{new AddressFilter(), WRONG_CONTACT, ""}, - new Object[]{new AddressFilter(), WRONG_ADDRESS, "Invalid address!"}, - new Object[]{new AddressFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new AddressFilter(), WRONG_ORDER, ""}, - - new Object[]{new DepositFilter(), PERFECT_ORDER, ""}, - new Object[]{new DepositFilter(), WRONG_NAME, ""}, - new Object[]{new DepositFilter(), WRONG_CONTACT, ""}, - new Object[]{new DepositFilter(), WRONG_ADDRESS, ""}, - new Object[]{new DepositFilter(), WRONG_DEPOSIT, "Invalid deposit number!"}, - new Object[]{new DepositFilter(), WRONG_ORDER, ""}, - - new Object[]{new OrderFilter(), PERFECT_ORDER, ""}, - new Object[]{new OrderFilter(), WRONG_NAME, ""}, - new Object[]{new OrderFilter(), WRONG_CONTACT, ""}, - new Object[]{new OrderFilter(), WRONG_ADDRESS, ""}, - new Object[]{new OrderFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new OrderFilter(), WRONG_ORDER, "Invalid order!"} - ); + new Object[] {new NameFilter(), PERFECT_ORDER, ""}, + new Object[] {new NameFilter(), WRONG_NAME, "Invalid name!"}, + new Object[] {new NameFilter(), WRONG_CONTACT, ""}, + new Object[] {new NameFilter(), WRONG_ADDRESS, ""}, + new Object[] {new NameFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new NameFilter(), WRONG_ORDER, ""}, + new Object[] {new ContactFilter(), PERFECT_ORDER, ""}, + new Object[] {new ContactFilter(), WRONG_NAME, ""}, + new Object[] {new ContactFilter(), WRONG_CONTACT, "Invalid contact number!"}, + new Object[] {new ContactFilter(), WRONG_ADDRESS, ""}, + new Object[] {new ContactFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new ContactFilter(), WRONG_ORDER, ""}, + new Object[] {new AddressFilter(), PERFECT_ORDER, ""}, + new Object[] {new AddressFilter(), WRONG_NAME, ""}, + new Object[] {new AddressFilter(), WRONG_CONTACT, ""}, + new Object[] {new AddressFilter(), WRONG_ADDRESS, "Invalid address!"}, + new Object[] {new AddressFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new AddressFilter(), WRONG_ORDER, ""}, + new Object[] {new DepositFilter(), PERFECT_ORDER, ""}, + new Object[] {new DepositFilter(), WRONG_NAME, ""}, + new Object[] {new DepositFilter(), WRONG_CONTACT, ""}, + new Object[] {new DepositFilter(), WRONG_ADDRESS, ""}, + new Object[] {new DepositFilter(), WRONG_DEPOSIT, "Invalid deposit number!"}, + new Object[] {new DepositFilter(), WRONG_ORDER, ""}, + new Object[] {new OrderFilter(), PERFECT_ORDER, ""}, + new Object[] {new OrderFilter(), WRONG_NAME, ""}, + new Object[] {new OrderFilter(), WRONG_CONTACT, ""}, + new Object[] {new OrderFilter(), WRONG_ADDRESS, ""}, + new Object[] {new OrderFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new OrderFilter(), WRONG_ORDER, "Invalid order!"}); } @ParameterizedTest @@ -101,5 +92,4 @@ void testNext(Filter filter) { assertNull(filter.getNext()); assertSame(filter, filter.getLast()); } - } diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java index 9013ed1004b4..3cb6ed3c8acf 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java @@ -28,11 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/13/15 - 2:57 PM - * - * @author Jeroen Meulemeester - */ +/** OrderTest */ class OrderTest { private static final String EXPECTED_VALUE = "test"; @@ -71,5 +67,4 @@ void testSetOrder() { order.setOrderItem(EXPECTED_VALUE); assertEquals(EXPECTED_VALUE, order.getOrderItem()); } - } diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java index dd4e09b84166..c202020f7f55 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java @@ -25,22 +25,18 @@ package com.iluwatar.intercepting.filter; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * Date: 01/29/23 - 1:33 PM - * - * @author Rahul Raj - */ +/** TargetTest */ class TargetTest { - - @Test - void testSetup(){ - final var target = new Target(); - assertEquals(target.getSize().getWidth(), Double.valueOf(640)); - assertEquals(target.getSize().getHeight(), Double.valueOf(480)); - assertEquals(true,target.isVisible()); - } + + @Test + void testSetup() { + final var target = new Target(); + assertEquals(target.getSize().getWidth(), Double.valueOf(640)); + assertEquals(target.getSize().getHeight(), Double.valueOf(480)); + assertTrue(target.isVisible()); + } } diff --git a/interpreter/README.md b/interpreter/README.md index df3c4d827bec..0eeb035dcfe3 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -1,176 +1,217 @@ --- -title: Interpreter +title: "Interpreter Pattern in Java: Building Custom Parsers for Java Applications" +shortTitle: Interpreter +description: "Explore the Interpreter Design Pattern in Java with real-world examples, class diagrams, and step-by-step implementation. Learn when and how to use this powerful behavioral pattern." category: Behavioral language: en tag: - - Gang of Four + - Abstraction + - Data transformation + - Decoupling + - Domain + - Gang of Four + - Polymorphism + - Runtime --- -## Intent +## Intent of Interpreter Design Pattern -Given a language, define a representation for its grammar along with an interpreter that uses the -representation to interpret sentences in the language. +The Interpreter design pattern is used to define a grammatical representation for a language and provides an interpreter to handle this grammar. This pattern is useful in scenarios where a specific set of rules or grammar needs to be interpreted and acted upon, such as in arithmetic expressions or scripting languages. -## Explanation +## Detailed Explanation of Interpreter Pattern with Real-World Examples Real-world example -> The halfling kids are learning basic math at school. They start from the very basics "1 + 1", -> "4 - 2", "5 + 5", and so forth. +> Consider a calculator application designed to interpret and calculate expressions entered by users. The application uses the Interpreter pattern in Java to parse and evaluate arithmetic expressions such as "5 + 3 * 2". Here, the Interpreter translates each part of the expression into objects that represent numbers and operations. These objects follow a defined grammar that allows the application to understand and compute the result correctly based on the rules of arithmetic. Each element of the expression corresponds to a class in the program's structure, simplifying the parsing and evaluation process for any inputted arithmetic formula. In plain words -> Interpreter pattern interprets sentences in the desired language. +> The Interpreter design pattern defines a representation for a language's grammar along with an interpreter that uses the representation to interpret sentences in the language. Wikipedia says -> In computer programming, the interpreter pattern is a design pattern that specifies how to -> evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or -> nonterminal) in a specialized computer language. The syntax tree of a sentence in the language -> is an instance of the composite pattern and is used to evaluate (interpret) the sentence for -> a client. +> In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence for a client. -**Programmatic example** +## Programmatic Example of Interpreter Pattern in Java -To be able to interpret basic math, we need a hierarchy of expressions. The basic abstraction for -it is the `Expression` class. +To be able to interpret basic math in Java, we need a hierarchy of expressions. The `Expression` class is the base, and concrete implementations like `NumberExpression` handle specific parts of the grammar. The Interpreter pattern in Java simplifies parsing and evaluating arithmetic expressions by translating them into a structured format that the application can process. ```java public abstract class Expression { - public abstract int interpret(); + public abstract int interpret(); - @Override - public abstract String toString(); + @Override + public abstract String toString(); } ``` -The simplest of the expressions is the `NumberExpression` that contains only a single integer -number. +The simplest of the expressions is the `NumberExpression` that contains only a single integer number. ```java public class NumberExpression extends Expression { - private final int number; + private final int number; - public NumberExpression(int number) { - this.number = number; - } + public NumberExpression(int number) { + this.number = number; + } - public NumberExpression(String s) { - this.number = Integer.parseInt(s); - } + public NumberExpression(String s) { + this.number = Integer.parseInt(s); + } - @Override - public int interpret() { - return number; - } + @Override + public int interpret() { + return number; + } - @Override - public String toString() { - return "number"; - } + @Override + public String toString() { + return "number"; + } } ``` -The more complex expressions are operations such as `PlusExpression`, `MinusExpression`, and -`MultiplyExpression`. Here's the first of them, the others are similar. +The more complex expressions are operations such as `PlusExpression`, `MinusExpression`, and `MultiplyExpression`. Here's the first of them, the others are similar. ```java public class PlusExpression extends Expression { - private final Expression leftExpression; - private final Expression rightExpression; + private final Expression leftExpression; + private final Expression rightExpression; - public PlusExpression(Expression leftExpression, Expression rightExpression) { - this.leftExpression = leftExpression; - this.rightExpression = rightExpression; - } + public PlusExpression(Expression leftExpression, Expression rightExpression) { + this.leftExpression = leftExpression; + this.rightExpression = rightExpression; + } - @Override - public int interpret() { - return leftExpression.interpret() + rightExpression.interpret(); - } + @Override + public int interpret() { + return leftExpression.interpret() + rightExpression.interpret(); + } - @Override - public String toString() { - return "+"; - } + @Override + public String toString() { + return "+"; + } } ``` -Now we are able to show the interpreter pattern in action parsing some simple math. +Now, we are able to show the interpreter pattern in action parsing some simple math. ```java - // the halfling kids are learning some basic math at school - // define the math string we want to parse - final var tokenString = "4 3 2 - 1 + *"; - - // the stack holds the parsed expressions - var stack = new Stack(); - - // tokenize the string and go through them one by one - var tokenList = tokenString.split(" "); - for (var s : tokenList) { - if (isOperator(s)) { - // when an operator is encountered we expect that the numbers can be popped from the top of - // the stack - var rightExpression = stack.pop(); - var leftExpression = stack.pop(); - LOGGER.info("popped from stack left: {} right: {}", - leftExpression.interpret(), rightExpression.interpret()); - var operator = getOperatorInstance(s, leftExpression, rightExpression); - LOGGER.info("operator: {}", operator); - var result = operator.interpret(); - // the operation result is pushed on top of the stack - var resultExpression = new NumberExpression(result); - stack.push(resultExpression); - LOGGER.info("push result to stack: {}", resultExpression.interpret()); - } else { - // numbers are pushed on top of the stack - var i = new NumberExpression(s); - stack.push(i); - LOGGER.info("push to stack: {}", i.interpret()); +@Slf4j +public class App { + + public static void main(String[] args) { + + // the halfling kids are learning some basic math at school + // define the math string we want to parse + final var tokenString = "4 3 2 - 1 + *"; + + // the stack holds the parsed expressions + var stack = new Stack(); + + // tokenize the string and go through them one by one + var tokenList = tokenString.split(" "); + for (var s : tokenList) { + if (isOperator(s)) { + // when an operator is encountered we expect that the numbers can be popped from the top of + // the stack + var rightExpression = stack.pop(); + var leftExpression = stack.pop(); + LOGGER.info("popped from stack left: {} right: {}", + leftExpression.interpret(), rightExpression.interpret()); + var operator = getOperatorInstance(s, leftExpression, rightExpression); + LOGGER.info("operator: {}", operator); + var result = operator.interpret(); + // the operation result is pushed on top of the stack + var resultExpression = new NumberExpression(result); + stack.push(resultExpression); + LOGGER.info("push result to stack: {}", resultExpression.interpret()); + } else { + // numbers are pushed on top of the stack + var i = new NumberExpression(s); + stack.push(i); + LOGGER.info("push to stack: {}", i.interpret()); + } } + // in the end, the final result lies on top of the stack + LOGGER.info("result: {}", stack.pop().interpret()); + } + + public static boolean isOperator(String s) { + return s.equals("+") || s.equals("-") || s.equals("*"); } - // in the end, the final result lies on top of the stack - LOGGER.info("result: {}", stack.pop().interpret()); -``` + + public static Expression getOperatorInstance(String s, Expression left, Expression right) { + return switch (s) { + case "+" -> new PlusExpression(left, right); + case "-" -> new MinusExpression(left, right); + default -> new MultiplyExpression(left, right); + }; + } +} + ``` Executing the program produces the following console output. ``` -popped from stack left: 1 right: 1 -operator: + -push result to stack: 2 -popped from stack left: 4 right: 2 -operator: * -push result to stack: 8 -result: 8 +13:33:15.437 [main] INFO com.iluwatar.interpreter.App -- push to stack: 4 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push to stack: 3 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push to stack: 2 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- popped from stack left: 3 right: 2 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- operator: - +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push result to stack: 1 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push to stack: 1 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- popped from stack left: 1 right: 1 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- operator: + +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push result to stack: 2 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- popped from stack left: 4 right: 2 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- operator: * +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push result to stack: 8 +13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- result: 8 ``` -## Class diagram +## Detailed Explanation of Interpreter Pattern with Real-World Examples -![alt text](./etc/interpreter_1.png "Interpreter") +![Interpreter](./etc/interpreter_1.png "Interpreter") -## Applicability +## When to Use the Interpreter Pattern in Java -Use the Interpreter pattern when there is a language to interpret, and you can represent statements -in the language as abstract syntax trees. The Interpreter pattern works best when +Use the Interpreter design pattern when there is a language to interpret, and you can represent statements in the language as abstract syntax trees. The Interpreter pattern works best when -* The grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time -* Efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, the translator can be implemented by the Interpreter pattern, so the pattern is still applicable +* The grammar is simple. For complex grammars, the class hierarchy for the grammar becomes large and unmanageable. Tools such as parser generators are a better alternative in such cases. They can interpret expressions without building abstract syntax trees, which can save space and possibly time. +* Efficiency is not a critical concern. The most efficient interpreters are usually not implemented by interpreting parse trees directly but by first translating them into another form. For example, regular expressions are often transformed into state machines. But even then, the translator can be implemented by the Interpreter pattern, so the pattern is still applicable. -## Known uses +## Real-World Applications of Interpreter Pattern in Java * [java.util.Pattern](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) * [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html) * All subclasses of [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html) * [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html) +* SQL parsers in various database management systems. + +## Benefits and Trade-offs of Interpreter Pattern + +Benefits: + +* Adds new operations to interpret expressions easily without changing the grammar or classes of data. +* Implements grammar directly in the language, making it easy to modify or extend. + +Trade-offs: + +* Can become complex and inefficient for large grammars. +* Each rule in the grammar results in a class, leading to a large number of classes for complex grammars. + +## Related Java Design Patterns +* [Composite](https://java-design-patterns.com/patterns/composite/): Often used together, where the Interpreter pattern leverages the Composite pattern to represent the grammar as a tree structure. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Useful for sharing state to reduce memory usage in the Interpreter pattern, particularly for interpreters that deal with repetitive elements in a language. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/interpreter/pom.xml b/interpreter/pom.xml index 9f2129424fa4..86c45ce224b2 100644 --- a/interpreter/pom.xml +++ b/interpreter/pom.xml @@ -34,6 +34,14 @@ interpreter + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/App.java b/interpreter/src/main/java/com/iluwatar/interpreter/App.java index df4dd1ec441b..7f0c0b2444b2 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/App.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/App.java @@ -38,13 +38,13 @@ * *

Expressions can be evaluated using prefix, infix or postfix notations This sample uses * postfix, where operator comes after the operands. - * */ @Slf4j public class App { /** * Program entry point. + * * @param args program arguments */ public static void main(String[] args) { @@ -64,8 +64,10 @@ public static void main(String[] args) { // the stack var rightExpression = stack.pop(); var leftExpression = stack.pop(); - LOGGER.info("popped from stack left: {} right: {}", - leftExpression.interpret(), rightExpression.interpret()); + LOGGER.info( + "popped from stack left: {} right: {}", + leftExpression.interpret(), + rightExpression.interpret()); var operator = getOperatorInstance(s, leftExpression, rightExpression); LOGGER.info("operator: {}", operator); var result = operator.interpret(); @@ -86,6 +88,7 @@ public static void main(String[] args) { /** * Checks whether the input parameter is an operator. + * * @param s input string * @return true if the input parameter is an operator */ @@ -95,6 +98,7 @@ public static boolean isOperator(String s) { /** * Returns correct expression based on the parameters. + * * @param s input string * @param left expression * @param right expression diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java b/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java index a5f364d368b0..44b4ea278b0c 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * Expression. - */ +/** Expression. */ public abstract class Expression { public abstract int interpret(); diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java index 12738add6d9e..c6d7f55812f1 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * MinusExpression. - */ +/** MinusExpression. */ public class MinusExpression extends Expression { private final Expression leftExpression; @@ -46,5 +44,4 @@ public int interpret() { public String toString() { return "-"; } - } diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java index d3803526d955..a5c1efe4fb63 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * MultiplyExpression. - */ +/** MultiplyExpression. */ public class MultiplyExpression extends Expression { private final Expression leftExpression; @@ -46,5 +44,4 @@ public int interpret() { public String toString() { return "*"; } - } diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java index 49f8c39ef5c3..31a632c3469a 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * NumberExpression. - */ +/** NumberExpression. */ public class NumberExpression extends Expression { private final int number; diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java index 2fbea275aa75..4109f397d8ef 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * PlusExpression. - */ +/** PlusExpression. */ public class PlusExpression extends Expression { private final Expression leftExpression; diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java index 56acffee5d81..4c72970c9742 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.interpreter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java index b9f7102bbd5c..80d76416e43d 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java @@ -37,12 +37,9 @@ import org.junit.jupiter.params.provider.MethodSource; /** - * Date: 12/14/15 - 11:48 AM - *

* Test Case for Expressions * * @param Type of Expression - * @author Jeroen Meulemeester */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class ExpressionTest { @@ -57,19 +54,15 @@ static Stream prepareParameters(final IntBinaryOperator resultCalc) { final var testData = new ArrayList(); for (var i = -10; i < 10; i++) { for (var j = -10; j < 10; j++) { - testData.add(Arguments.of( - new NumberExpression(i), - new NumberExpression(j), - resultCalc.applyAsInt(i, j) - )); + testData.add( + Arguments.of( + new NumberExpression(i), new NumberExpression(j), resultCalc.applyAsInt(i, j))); } } return testData.stream(); } - /** - * The expected {@link E#toString()} response - */ + /** The expected {@link E#toString()} response */ private final String expectedToString; /** @@ -81,11 +74,11 @@ static Stream prepareParameters(final IntBinaryOperator resultCalc) { * Create a new test instance with the given parameters and expected results * * @param expectedToString The expected {@link E#toString()} response - * @param factory Factory, used to create a new test object instance + * @param factory Factory, used to create a new test object instance */ - ExpressionTest(final String expectedToString, - final BiFunction factory - ) { + ExpressionTest( + final String expectedToString, + final BiFunction factory) { this.expectedToString = expectedToString; this.factory = factory; } @@ -97,9 +90,7 @@ static Stream prepareParameters(final IntBinaryOperator resultCalc) { */ public abstract Stream expressionProvider(); - /** - * Verify if the expression calculates the correct result when calling {@link E#interpret()} - */ + /** Verify if the expression calculates the correct result when calling {@link E#interpret()} */ @ParameterizedTest @MethodSource("expressionProvider") void testInterpret(NumberExpression first, NumberExpression second, int result) { @@ -108,9 +99,7 @@ void testInterpret(NumberExpression first, NumberExpression second, int result) assertEquals(result, expression.interpret()); } - /** - * Verify if the expression has the expected {@link E#toString()} value - */ + /** Verify if the expression has the expected {@link E#toString()} value */ @ParameterizedTest @MethodSource("expressionProvider") void testToString(NumberExpression first, NumberExpression second) { diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java index 0f68912744ce..d773a89707a6 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java @@ -27,11 +27,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; -/** - * Date: 12/14/15 - 12:08 PM - * - * @author Jeroen Meulemeester - */ +/** MinusExpressionTest */ class MinusExpressionTest extends ExpressionTest { /** @@ -44,11 +40,8 @@ public Stream expressionProvider() { return prepareParameters((f, s) -> f - s); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public MinusExpressionTest() { super("-", MinusExpression::new); } - -} \ No newline at end of file +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java index 7b25a16eb6f7..6d73341a3f93 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java @@ -27,11 +27,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; -/** - * Date: 12/14/15 - 12:08 PM - * - * @author Jeroen Meulemeester - */ +/** MultiplyExpressionTest */ class MultiplyExpressionTest extends ExpressionTest { /** @@ -44,11 +40,8 @@ public Stream expressionProvider() { return prepareParameters((f, s) -> f * s); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public MultiplyExpressionTest() { super("*", MultiplyExpression::new); } - -} \ No newline at end of file +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java index deb37e45e001..9d917c03abcb 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java @@ -31,11 +31,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -/** - * Date: 12/14/15 - 12:08 PM - * - * @author Jeroen Meulemeester - */ +/** NumberExpressionTest */ class NumberExpressionTest extends ExpressionTest { /** @@ -48,9 +44,7 @@ public Stream expressionProvider() { return prepareParameters((f, s) -> f); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public NumberExpressionTest() { super("number", (f, s) -> f); } @@ -60,11 +54,10 @@ public NumberExpressionTest() { */ @ParameterizedTest @MethodSource("expressionProvider") - void testFromString(NumberExpression first) throws Exception { + void testFromString(NumberExpression first) { final var expectedValue = first.interpret(); final var testStringValue = String.valueOf(expectedValue); final var numberExpression = new NumberExpression(testStringValue); assertEquals(expectedValue, numberExpression.interpret()); } - -} \ No newline at end of file +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java index a4711d18bef8..a93f27c84abd 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java @@ -27,11 +27,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; -/** - * Date: 12/14/15 - 12:08 PM - * - * @author Jeroen Meulemeester - */ +/** PlusExpressionTest */ class PlusExpressionTest extends ExpressionTest { /** @@ -44,11 +40,8 @@ public Stream expressionProvider() { return prepareParameters(Integer::sum); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public PlusExpressionTest() { super("+", PlusExpression::new); } - -} \ No newline at end of file +} diff --git a/iterator/README.md b/iterator/README.md index b1f92f1e05ae..49bca61a09f5 100644 --- a/iterator/README.md +++ b/iterator/README.md @@ -1,40 +1,43 @@ --- -title: Iterator +title: "Iterator Pattern in Java: Mastering Sequential Element Access" +shortTitle: Iterator +description: "Learn how to implement the Iterator Design Pattern in Java. Access elements of a collection sequentially without exposing its underlying structure. Explore real-world examples, code snippets, and benefits of using iterators." category: Behavioral language: en tag: - - Gang of Four + - Data access + - Data transformation + - Decoupling + - Gang of Four + - Object composition + - Polymorphism --- ## Also known as -Cursor +* Cursor -## Intent -Provide a way to access the elements of an aggregate object sequentially without exposing its -underlying representation. +## Intent of Iterator Design Pattern -## Explanation +The Iterator Design Pattern in Java provides a way to access elements of an aggregate object sequentially without exposing its underlying representation. This behavioral pattern is crucial for efficient collection traversal. + +## Detailed Explanation of Iterator Pattern with Real-World Examples Real-world example -> Treasure chest contains a set of magical items. There multiple types of items such as rings, -> potions, and weapons. The items can be browsed by type using an iterator the treasure chest -> provides. +> Imagine visiting a library with a vast collection of books organized in different sections such as fiction, non-fiction, science, etc. Instead of searching through every shelf yourself, the librarian provides you with a specific guidebook or a digital catalog for each section. This guidebook acts as an "iterator," allowing you to go through the books section by section, or even skip to specific types of books, without needing to know how the books are organized on the shelves. Each guidebook handles the traversal through its section, providing a consistent and efficient way to access the books, much like how the Iterator design pattern offers a uniform method to traverse different data structures in a software application. In plain words -> Containers can provide a representation agnostic iterator interface to provide access to the -> elements. +> The Java Iterator Design Pattern provides a method to sequentially access elements of a collection without exposing its underlying structure. This pattern is widely used in Java programming for efficient data access. Wikipedia says -> In object-oriented programming, the iterator pattern is a design pattern in which an iterator is -> used to traverse a container and access the container's elements. +> In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. -**Programmatic Example** +## Programmatic Example of Iterator Pattern in Java -The main class in our example is the `TreasureChest` that contains items. +The main class in our Java Iterator Design Pattern example is the `TreasureChest` that contains items. This demonstrates how to implement and use iterators for efficient collection traversal in Java. ```java public class TreasureChest { @@ -110,44 +113,140 @@ public interface Iterator { } ``` -In the following example, we iterate through the ring-type items found in the chest. +In the following example, we demonstrate different kinds of iterators. ```java -var itemIterator = TREASURE_CHEST.iterator(ItemType.RING); -while (itemIterator.hasNext()) { - LOGGER.info(itemIterator.next().toString()); +@Slf4j +public class App { + + private static final TreasureChest TREASURE_CHEST = new TreasureChest(); + + private static void demonstrateTreasureChestIteratorForType(ItemType itemType) { + LOGGER.info("------------------------"); + LOGGER.info("Item Iterator for ItemType " + itemType + ": "); + var itemIterator = TREASURE_CHEST.iterator(itemType); + while (itemIterator.hasNext()) { + LOGGER.info(itemIterator.next().toString()); + } + } + + private static void demonstrateBstIterator() { + LOGGER.info("------------------------"); + LOGGER.info("BST Iterator: "); + var root = buildIntegerBst(); + var bstIterator = new BstIterator<>(root); + while (bstIterator.hasNext()) { + LOGGER.info("Next node: " + bstIterator.next().getVal()); + } + } + + private static TreeNode buildIntegerBst() { + var root = new TreeNode<>(8); + + root.insert(3); + root.insert(10); + root.insert(1); + root.insert(6); + root.insert(14); + root.insert(4); + root.insert(7); + root.insert(13); + + return root; + } + + public static void main(String[] args) { + demonstrateTreasureChestIteratorForType(RING); + demonstrateTreasureChestIteratorForType(POTION); + demonstrateTreasureChestIteratorForType(WEAPON); + demonstrateTreasureChestIteratorForType(ANY); + demonstrateBstIterator(); + } } ``` Program output: -```java -Ring of shadows -Ring of armor +``` +13:36:37.087 [main] INFO com.iluwatar.iterator.App -- ------------------------ +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Item Iterator for ItemType RING: +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Ring of shadows +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Ring of armor +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- ------------------------ +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Item Iterator for ItemType POTION: +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of courage +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of wisdom +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of blood +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of rust +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of healing +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- ------------------------ +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Item Iterator for ItemType WEAPON: +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Sword of silver +1 +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Steel halberd +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Dagger of poison +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- ------------------------ +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Item Iterator for ItemType ANY: +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of courage +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Ring of shadows +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of wisdom +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of blood +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Sword of silver +1 +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of rust +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Potion of healing +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Ring of armor +13:36:37.089 [main] INFO com.iluwatar.iterator.App -- Steel halberd +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Dagger of poison +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- ------------------------ +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- BST Iterator: +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 1 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 3 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 4 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 6 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 7 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 8 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 10 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 13 +13:36:37.090 [main] INFO com.iluwatar.iterator.App -- Next node: 14 ``` -## Class diagram - -![alt text](./etc/iterator_1.png "Iterator") - -## Applicability +## When to Use the Iterator Pattern in Java -Use the Iterator pattern +Use the Iterator design pattern in Java * To access an aggregate object's contents without exposing its internal representation. * To support multiple traversals of aggregate objects. * To provide a uniform interface for traversing different aggregate structures. -## Tutorials +## Iterator Pattern Java Tutorials -* [How to Use Iterator?](http://www.tutorialspoint.com/java/java_using_iterator.htm) +* [Java - How to Use Iterator? (TutorialsPoint)](http://www.tutorialspoint.com/java/java_using_iterator.htm) -## Known uses +## Real-World Applications of Iterator Pattern in Java +* Java Collections Framework utilizes iterators extensively to allow different ways to traverse through collections. +* Databases often use iterators to navigate through data records fetched through SQL queries. * [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html) * [java.util.Enumeration](http://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) -## Credits +## Benefits and Trade-offs of Iterator Pattern + +Benefits: + +* Reduces the coupling between data structures and algorithms used for iteration. +* Provides a uniform interface for iterating over various types of data structures, enhancing code reusability and flexibility. + +Trade-offs: + +* Overhead of using an iterator object may slightly reduce performance compared to direct traversal methods. +* Complex aggregate structures may require complex iterators that can be difficult to manage or extend. + +## Related Java Design Patterns + +* [Composite](https://java-design-patterns.com/patterns/composite/): Iterators are often used to traverse Composite trees. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Used to create appropriate iterators for different data structures. +* [Visitor](https://java-design-patterns.com/patterns/visitor/): Can be used with Iterator to apply operations over elements of an object structure. + +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) diff --git a/iterator/pom.xml b/iterator/pom.xml index 4afb9278c09c..4578eed1baa0 100644 --- a/iterator/pom.xml +++ b/iterator/pom.xml @@ -34,6 +34,14 @@ iterator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java b/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java index 8796a7939d09..a027a286265f 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java @@ -33,7 +33,7 @@ * expect to retrieve TreeNodes according to the Integer's natural ordering (1, 2, 3...) * * @param This Iterator has been implemented with generic typing to allow for TreeNodes of - * different value types + * different value types */ public class BstIterator> implements Iterator> { @@ -83,5 +83,4 @@ public TreeNode next() throws NoSuchElementException { pushPathToNextSmallest(next.getRight()); return next; } - } diff --git a/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java b/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java index 5b49ec58caef..a24f98d58bcd 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java +++ b/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java @@ -24,6 +24,9 @@ */ package com.iluwatar.iterator.bst; +import lombok.Getter; +import lombok.Setter; + /** * TreeNode Class, representing one node in a Binary Search Tree. Allows for a generically typed * value. @@ -33,8 +36,10 @@ public class TreeNode> { private final T val; - private TreeNode left; - private TreeNode right; + + @Getter @Setter private TreeNode left; + + @Getter @Setter private TreeNode right; /** * Creates a TreeNode with a given value, and null children. @@ -51,22 +56,6 @@ public T getVal() { return val; } - public TreeNode getLeft() { - return left; - } - - private void setLeft(TreeNode left) { - this.left = left; - } - - public TreeNode getRight() { - return right; - } - - private void setRight(TreeNode right) { - this.right = right; - } - /** * Inserts new TreeNode based on a given value into the subtree represented by self. * @@ -136,5 +125,4 @@ private boolean isLessThanOrEqualTo(T val) { public String toString() { return val.toString(); } - } diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/Item.java b/iterator/src/main/java/com/iluwatar/iterator/list/Item.java index 480e795695c0..0188078a1b02 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/Item.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/Item.java @@ -24,29 +24,19 @@ */ package com.iluwatar.iterator.list; -/** - * Item. - */ +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +/** Item. */ +@AllArgsConstructor public class Item { - private ItemType type; + @Getter @Setter private ItemType type; private final String name; - public Item(ItemType type, String name) { - this.setType(type); - this.name = name; - } - @Override public String toString() { return name; } - - public ItemType getType() { - return type; - } - - public final void setType(ItemType type) { - this.type = type; - } } diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java b/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java index 1325cd3655bd..2655a7aa091f 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java @@ -24,11 +24,10 @@ */ package com.iluwatar.iterator.list; -/** - * ItemType enumeration. - */ +/** ItemType enumeration. */ public enum ItemType { - - ANY, WEAPON, RING, POTION - + ANY, + WEAPON, + RING, + POTION } diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java index 2a0472dcb4a9..1eb3905a7fd0 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java @@ -28,39 +28,33 @@ import java.util.ArrayList; import java.util.List; -/** - * TreasureChest, the collection class. - */ +/** TreasureChest, the collection class. */ public class TreasureChest { private final List items; - /** - * Constructor. - */ + /** Constructor. */ public TreasureChest() { - items = List.of( - new Item(ItemType.POTION, "Potion of courage"), - new Item(ItemType.RING, "Ring of shadows"), - new Item(ItemType.POTION, "Potion of wisdom"), - new Item(ItemType.POTION, "Potion of blood"), - new Item(ItemType.WEAPON, "Sword of silver +1"), - new Item(ItemType.POTION, "Potion of rust"), - new Item(ItemType.POTION, "Potion of healing"), - new Item(ItemType.RING, "Ring of armor"), - new Item(ItemType.WEAPON, "Steel halberd"), - new Item(ItemType.WEAPON, "Dagger of poison")); + items = + List.of( + new Item(ItemType.POTION, "Potion of courage"), + new Item(ItemType.RING, "Ring of shadows"), + new Item(ItemType.POTION, "Potion of wisdom"), + new Item(ItemType.POTION, "Potion of blood"), + new Item(ItemType.WEAPON, "Sword of silver +1"), + new Item(ItemType.POTION, "Potion of rust"), + new Item(ItemType.POTION, "Potion of healing"), + new Item(ItemType.RING, "Ring of armor"), + new Item(ItemType.WEAPON, "Steel halberd"), + new Item(ItemType.WEAPON, "Dagger of poison")); } public Iterator iterator(ItemType itemType) { return new TreasureChestItemIterator(this, itemType); } - /** - * Get all items. - */ + /** Get all items. */ public List getItems() { return new ArrayList<>(items); } - } diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java index 25f6e1434421..51c33f931acb 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java @@ -26,18 +26,14 @@ import com.iluwatar.iterator.Iterator; -/** - * TreasureChestItemIterator. - */ +/** TreasureChestItemIterator. */ public class TreasureChestItemIterator implements Iterator { private final TreasureChest chest; private int idx; private final ItemType type; - /** - * Constructor. - */ + /** Constructor. */ public TreasureChestItemIterator(TreasureChest chest, ItemType type) { this.chest = chest; this.type = type; diff --git a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java index da2ed3c67034..13766bbc9847 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.iterator; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Test - */ +import org.junit.jupiter.api.Test; + +/** Application Test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java b/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java index 2014aadc8e21..a075ce75f3ea 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java @@ -56,7 +56,9 @@ void createTrees() { @Test void nextForEmptyTree() { var iter = new BstIterator<>(emptyRoot); - assertThrows(NoSuchElementException.class, iter::next, + assertThrows( + NoSuchElementException.class, + iter::next, "next() should throw an IllegalStateException if hasNext() is false."); } @@ -100,5 +102,4 @@ void nextAndHasNextOverEntirePopulatedTree() { assertEquals(Integer.valueOf(7), iter.next().getVal(), "Sixth Node is 7."); assertFalse(iter.hasNext(), "Iterator hasNext() should be false, end of tree."); } - } diff --git a/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java b/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java index 7f0c27cd1379..3c298180d065 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java @@ -32,11 +32,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * Date: 12/14/15 - 2:58 PM - * - * @author Jeroen Meulemeester - */ +/** TreasureChestTest */ class TreasureChestTest { /** @@ -46,17 +42,16 @@ class TreasureChestTest { */ public static List dataProvider() { return List.of( - new Object[]{new Item(ItemType.POTION, "Potion of courage")}, - new Object[]{new Item(ItemType.RING, "Ring of shadows")}, - new Object[]{new Item(ItemType.POTION, "Potion of wisdom")}, - new Object[]{new Item(ItemType.POTION, "Potion of blood")}, - new Object[]{new Item(ItemType.WEAPON, "Sword of silver +1")}, - new Object[]{new Item(ItemType.POTION, "Potion of rust")}, - new Object[]{new Item(ItemType.POTION, "Potion of healing")}, - new Object[]{new Item(ItemType.RING, "Ring of armor")}, - new Object[]{new Item(ItemType.WEAPON, "Steel halberd")}, - new Object[]{new Item(ItemType.WEAPON, "Dagger of poison")} - ); + new Object[] {new Item(ItemType.POTION, "Potion of courage")}, + new Object[] {new Item(ItemType.RING, "Ring of shadows")}, + new Object[] {new Item(ItemType.POTION, "Potion of wisdom")}, + new Object[] {new Item(ItemType.POTION, "Potion of blood")}, + new Object[] {new Item(ItemType.WEAPON, "Sword of silver +1")}, + new Object[] {new Item(ItemType.POTION, "Potion of rust")}, + new Object[] {new Item(ItemType.POTION, "Potion of healing")}, + new Object[] {new Item(ItemType.RING, "Ring of armor")}, + new Object[] {new Item(ItemType.WEAPON, "Steel halberd")}, + new Object[] {new Item(ItemType.WEAPON, "Dagger of poison")}); } /** @@ -83,7 +78,6 @@ void testIterator(Item expectedItem) { } fail("Expected to find item [" + expectedItem + "] using iterator, but we didn't."); - } /** @@ -92,7 +86,7 @@ void testIterator(Item expectedItem) { */ @ParameterizedTest @MethodSource("dataProvider") - void testGetItems(Item expectedItem) throws Exception { + void testGetItems(Item expectedItem) { final var chest = new TreasureChest(); final var items = chest.getItems(); assertNotNull(items); @@ -110,7 +104,5 @@ void testGetItems(Item expectedItem) throws Exception { } fail("Expected to find item [" + expectedItem + "] in the item list, but we didn't."); - } - -} \ No newline at end of file +} diff --git a/layered-architecture/README.md b/layered-architecture/README.md new file mode 100644 index 000000000000..9692d6e0e2db --- /dev/null +++ b/layered-architecture/README.md @@ -0,0 +1,131 @@ +--- +title: "Layered Architecture Pattern in Java: Building Scalable and Maintainable Applications" +shortTitle: Layered Architecture +description: "Explore the Layered Architecture design pattern in Java. Learn its benefits, real-world examples, use cases, and how it enhances maintainability and scalability in enterprise applications." +category: Architectural +language: en +tag: + - Abstraction + - Decoupling + - Enterprise patterns + - Layered architecture + - Scalability +--- + +## Also known as + +* N-Tier Architecture + +## Intent of Layered Architecture Design Pattern + +The Layered Architecture design pattern helps organize applications into groups of subtasks at different levels of abstraction, facilitating independent development and maintenance of each layer. + +## Detailed Explanation of Layered Architecture Pattern with Real-World Examples + +Real-world example + +> Imagine constructing a modern high-rise building, analogous to using the Layered Architecture design pattern in software development. This pattern allows each software layer, such as the data layer, service layer, and presentation layer, to support seamless interaction while maintaining independence, enhancing maintainability and scalability. Just as a building is divided into layers such as the foundation, structural floors, residential floors, and the rooftop, each with specific functions and built using different materials and techniques, a software application can be similarly structured. +> +> In this analogy, the foundation represents the data layer, responsible for managing database operations. The structural floors are akin to the service layer, which contains business logic and rules. The residential floors parallel the presentation layer, which deals with user interfaces and interactions. Finally, the rooftop could be seen as the API layer, allowing external systems to communicate with the application. +> +> Just as each floor in a building is constructed to support the layers above and below, each software layer supports seamless interaction with its neighboring layers while maintaining a degree of independence. This structure allows for easy maintenance and updates, such as refurbishing the interiors (presentation layer) without affecting the underlying structures (business logic and data layers). + +In plain words + +> The Layered Architecture pattern organizes software into hierarchical groups of tasks, each encapsulated in distinct layers that interact with each other, facilitating ease of maintenance, scalability, and clear separation of concerns. + +Wikipedia says + +> In software engineering, multitier architecture (often referred to as n-tier architecture) or multilayered architecture is a client–server architecture in which presentation, application processing, and data management functions are physically separated. + +Architecture diagram + +![Layered Architecture Diagram](./etc/layered-architecture-diagram.png) + +## Programmatic Example of Layered Architecture in Java + +On the data layer, we keep our cake building blocks. `Cake` consist of layers and topping. + +```java +@Entity +public class Cake { + @Id + @GeneratedValue + private Long id; + @OneToOne(cascade = CascadeType.REMOVE) + private CakeTopping topping; + @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER) + private Set layers; +} +``` + +The service layer offers `CakeBakingService` for easy access to different aspects of cakes. + +```java +public interface CakeBakingService { + void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException; + List getAllCakes(); + void saveNewTopping(CakeToppingInfo toppingInfo); + List getAvailableToppings(); + void saveNewLayer(CakeLayerInfo layerInfo); + List getAvailableLayers(); +} +``` + +On the top we have our `View` responsible for rendering the cakes. + +```java +public interface View { + void render(); +} +@Slf4j +public class CakeViewImpl implements View { + private final CakeBakingService cakeBakingService; + public CakeViewImpl(CakeBakingService cakeBakingService) { + this.cakeBakingService = cakeBakingService; + } + public void render() { + cakeBakingService.getAllCakes().forEach(cake -> LOGGER.info(cake.toString())); + } +} +``` + +## When to Use the Layered Architecture Pattern in Java + +This pattern is suitable for structuring applications that can be divided into groups where each group has a specific role or responsibility. Common in enterprise applications, it simplifies dependencies, enhances maintainability, and supports scaling and technology stack segregation. + +Use the Layers architecture when + +* You want clearly divide software responsibilities into different parts of the program. +* You want to prevent a change from propagating throughout the application. +* You want to make your application more maintainable and testable. + +## Real-World Applications of Layered Architecture Pattern in Java + +* Web applications where the presentation, business logic, and data access layers are distinctly separated. +* Enterprise systems where core functionalities are isolated from interface applications and databases. + +## Benefits and Trade-offs of Layered Architecture Pattern + +Benefits + +* Improved manageability with separation of concerns +* Easier to update or modify one layer without affecting others +* Promotes reuse of functionalities. + +Trade-offs + +* Potential performance overhead due to layer interaction +* Complexity in layer management +* Challenges in designing an effective layer distribution. + +## Related Java Design Patterns + +* [Model-View-Controller](https://java-design-patterns.com/patterns/model-view-controller/): Shares separation of concerns by dividing application into input, processing, and output. Layered Architecture often implements an MVC within its presentation layer. +* Service-Oriented Architecture (SOA): Both patterns emphasize modularization but SOA focuses more on distributed services that can be reused across different systems. + +## References and Credits + +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR) +* [Java Design Pattern Essentials](https://amzn.to/4drLhHU) +* [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3xZ1ELU) diff --git a/layered-architecture/etc/layered-architecture-diagram.png b/layered-architecture/etc/layered-architecture-diagram.png new file mode 100644 index 000000000000..8bdc13afeb52 Binary files /dev/null and b/layered-architecture/etc/layered-architecture-diagram.png differ diff --git a/layers/etc/layers.png b/layered-architecture/etc/layers.png similarity index 100% rename from layers/etc/layers.png rename to layered-architecture/etc/layers.png diff --git a/layers/etc/layers.ucls b/layered-architecture/etc/layers.ucls similarity index 100% rename from layers/etc/layers.ucls rename to layered-architecture/etc/layers.ucls diff --git a/layers/etc/layers.urm.puml b/layered-architecture/etc/layers.urm.puml similarity index 100% rename from layers/etc/layers.urm.puml rename to layered-architecture/etc/layers.urm.puml diff --git a/layers/pom.xml b/layered-architecture/pom.xml similarity index 73% rename from layers/pom.xml rename to layered-architecture/pom.xml index 425b8eaedc6f..c91fc733ef64 100644 --- a/layers/pom.xml +++ b/layered-architecture/pom.xml @@ -1,8 +1,11 @@ + THE SOFTWARE. + +--> 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 3.0.2 - + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT - com.iluwatar - layers + layered-architecture layers layers + org.springframework.boot @@ -43,17 +47,11 @@ org.springframework.boot spring-boot-starter-data-jpa - com.h2database h2 runtime - - org.projectlombok - lombok - true - org.springframework.boot spring-boot-starter-test @@ -64,16 +62,19 @@ - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.layers.Runner + + + + + diff --git a/layered-architecture/src/main/java/com/iluwatar/layers/Runner.java b/layered-architecture/src/main/java/com/iluwatar/layers/Runner.java new file mode 100644 index 000000000000..4c4fb68377ae --- /dev/null +++ b/layered-architecture/src/main/java/com/iluwatar/layers/Runner.java @@ -0,0 +1,114 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.layers; + +import dto.CakeInfo; +import dto.CakeLayerInfo; +import dto.CakeToppingInfo; +import exception.CakeBakingException; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.stereotype.Component; +import service.CakeBakingService; +import view.CakeViewImpl; + +/** + * The Runner class is the entry point of the application. It implements CommandLineRunner, which + * means it will execute the run method after the application context is loaded. + * + *

The Runner class is responsible for initializing the cake baking service with sample data and + * creating a view to render the cakes. It uses the CakeBakingService to save new layers and + * toppings and to bake new cakes. It also handles exceptions that might occur during the cake + * baking process. + */ +@EntityScan(basePackages = "entity") +@ComponentScan(basePackages = {"com.iluwatar.layers", "service", "dto", "exception", "view", "dao"}) +@Component +@Slf4j +public class Runner implements CommandLineRunner { + private final CakeBakingService cakeBakingService; + public static final String STRAWBERRY = "strawberry"; + + @Autowired + public Runner(CakeBakingService cakeBakingService) { + this.cakeBakingService = cakeBakingService; + } + + @Override + public void run(String... args) { + // initialize sample data + initializeData(); + // create view and render it + var cakeView = new CakeViewImpl(cakeBakingService); + cakeView.render(); + } + + public static void main(String[] args) { + SpringApplication.run(Runner.class, args); + } + + /** Initializes the example data. */ + private void initializeData() { + cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200)); + cakeBakingService.saveNewLayer(new CakeLayerInfo("banana", 900)); + cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950)); + cakeBakingService.saveNewLayer(new CakeLayerInfo("lemon", 950)); + cakeBakingService.saveNewLayer(new CakeLayerInfo("vanilla", 950)); + cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950)); + + cakeBakingService.saveNewTopping(new CakeToppingInfo("candies", 350)); + cakeBakingService.saveNewTopping(new CakeToppingInfo("cherry", 350)); + + var cake1 = + new CakeInfo( + new CakeToppingInfo("candies", 0), + List.of( + new CakeLayerInfo("chocolate", 0), + new CakeLayerInfo("banana", 0), + new CakeLayerInfo(STRAWBERRY, 0))); + try { + cakeBakingService.bakeNewCake(cake1); + } catch (CakeBakingException e) { + LOGGER.error("Cake baking exception", e); + } + var cake2 = + new CakeInfo( + new CakeToppingInfo("cherry", 0), + List.of( + new CakeLayerInfo("vanilla", 0), + new CakeLayerInfo("lemon", 0), + new CakeLayerInfo(STRAWBERRY, 0))); + try { + cakeBakingService.bakeNewCake(cake2); + } catch (CakeBakingException e) { + LOGGER.error("Cake baking exception", e); + } + } +} diff --git a/layered-architecture/src/main/java/com/iluwatar/layers/app/LayersApp.java b/layered-architecture/src/main/java/com/iluwatar/layers/app/LayersApp.java new file mode 100644 index 000000000000..2870366e00bd --- /dev/null +++ b/layered-architecture/src/main/java/com/iluwatar/layers/app/LayersApp.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.layers.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +/** + * The Layers pattern is a structural design pattern that organizes system architecture into + * distinct layers, each with a specific responsibility and abstraction level. This separation + * allows for increased modularity, facilitating independent development, maintenance, and reuse of + * each layer. Commonly, layers interact with each other through well-defined interfaces, with + * higher layers (more abstract) depending on lower layers (more concrete), but not vice versa, + * promoting a clear hierarchy and separation of concerns. + */ +@SpringBootApplication +@EnableJpaRepositories(basePackages = "dao") +@EntityScan(basePackages = "entity") +@ComponentScan(basePackages = {"com.iluwatar.layers", "service", "dto", "exception", "view", "dao"}) +public class LayersApp { + + public static void main(String[] args) { + SpringApplication.run(LayersApp.class, args); + } +} diff --git a/layers/src/main/java/dao/CakeDao.java b/layered-architecture/src/main/java/dao/CakeDao.java similarity index 97% rename from layers/src/main/java/dao/CakeDao.java rename to layered-architecture/src/main/java/dao/CakeDao.java index 06d74bace26c..aae92a175bb0 100644 --- a/layers/src/main/java/dao/CakeDao.java +++ b/layered-architecture/src/main/java/dao/CakeDao.java @@ -28,8 +28,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -/** - * CRUD repository for cakes. - */ +/** CRUD repository for cakes. */ @Repository public interface CakeDao extends JpaRepository {} diff --git a/layers/src/main/java/dao/CakeLayerDao.java b/layered-architecture/src/main/java/dao/CakeLayerDao.java similarity index 96% rename from layers/src/main/java/dao/CakeLayerDao.java rename to layered-architecture/src/main/java/dao/CakeLayerDao.java index 90a523f16892..0e4415ce4c42 100644 --- a/layers/src/main/java/dao/CakeLayerDao.java +++ b/layered-architecture/src/main/java/dao/CakeLayerDao.java @@ -28,10 +28,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -/** - * CRUD repository for cake layers. - */ +/** CRUD repository for cake layers. */ @Repository -public interface CakeLayerDao extends JpaRepository { - -} +public interface CakeLayerDao extends JpaRepository {} diff --git a/layers/src/main/java/dao/CakeToppingDao.java b/layered-architecture/src/main/java/dao/CakeToppingDao.java similarity index 96% rename from layers/src/main/java/dao/CakeToppingDao.java rename to layered-architecture/src/main/java/dao/CakeToppingDao.java index 0d0c8836dcaa..bc7b4a56f723 100644 --- a/layers/src/main/java/dao/CakeToppingDao.java +++ b/layered-architecture/src/main/java/dao/CakeToppingDao.java @@ -24,16 +24,10 @@ */ package dao; - - import entity.CakeTopping; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -/** - * CRUD repository cake toppings. - */ +/** CRUD repository cake toppings. */ @Repository -public interface CakeToppingDao extends JpaRepository { - -} +public interface CakeToppingDao extends JpaRepository {} diff --git a/layered-architecture/src/main/java/dto/CakeInfo.java b/layered-architecture/src/main/java/dto/CakeInfo.java new file mode 100644 index 000000000000..475c994481ab --- /dev/null +++ b/layered-architecture/src/main/java/dto/CakeInfo.java @@ -0,0 +1,64 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package dto; + +import java.util.List; + +/** DTO for cakes. */ +public class CakeInfo { + + public final Long id; + public final CakeToppingInfo cakeToppingInfo; + public final List cakeLayerInfos; + + /** Constructor. */ + public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { + this.id = id; + this.cakeToppingInfo = cakeToppingInfo; + this.cakeLayerInfos = cakeLayerInfos; + } + + /** Constructor. */ + public CakeInfo(CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { + this.id = null; + this.cakeToppingInfo = cakeToppingInfo; + this.cakeLayerInfos = cakeLayerInfos; + } + + /** Calculate calories. */ + public int calculateTotalCalories() { + var total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0; + total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum(); + return total; + } + + @Override + public String toString() { + return String.format( + "CakeInfo id=%d topping=%s layers=%s totalCalories=%d", + id, cakeToppingInfo, cakeLayerInfos, calculateTotalCalories()); + } +} diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java b/layered-architecture/src/main/java/dto/CakeLayerInfo.java similarity index 71% rename from thread-pool/src/main/java/com/iluwatar/threadpool/Task.java rename to layered-architecture/src/main/java/dto/CakeLayerInfo.java index 6fa329048778..e7e464e2dc13 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java +++ b/layered-architecture/src/main/java/dto/CakeLayerInfo.java @@ -22,35 +22,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; -import java.util.concurrent.atomic.AtomicInteger; +package dto; -/** - * Abstract base class for tasks. - */ -public abstract class Task { - - private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); +/** DTO for cake layers. */ +public class CakeLayerInfo { - private final int id; - private final int timeMs; - - public Task(final int timeMs) { - this.id = ID_GENERATOR.incrementAndGet(); - this.timeMs = timeMs; - } + public final Long id; + public final String name; + public final int calories; - public int getId() { - return id; + /** Constructor. */ + public CakeLayerInfo(Long id, String name, int calories) { + this.id = id; + this.name = name; + this.calories = calories; } - public int getTimeMs() { - return timeMs; + /** Constructor. */ + public CakeLayerInfo(String name, int calories) { + this.id = null; + this.name = name; + this.calories = calories; } @Override public String toString() { - return String.format("id=%d timeMs=%d", id, timeMs); + return String.format("CakeLayerInfo id=%d name=%s calories=%d", id, name, calories); } } diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/Message.java b/layered-architecture/src/main/java/dto/CakeToppingInfo.java similarity index 71% rename from priority-queue/src/main/java/com/iluwatar/priority/queue/Message.java rename to layered-architecture/src/main/java/dto/CakeToppingInfo.java index 09bae85b32cd..9c48d869ce65 100644 --- a/priority-queue/src/main/java/com/iluwatar/priority/queue/Message.java +++ b/layered-architecture/src/main/java/dto/CakeToppingInfo.java @@ -22,31 +22,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.priority.queue; -/** - * Message bean. - */ -public class Message implements Comparable { - private final String message; - private final int priority; // define message priority in queue +package dto; + +/** DTO for cake toppings. */ +public class CakeToppingInfo { + public final Long id; + public final String name; + public final int calories; - public Message(String message, int priority) { - this.message = message; - this.priority = priority; + /** Constructor. */ + public CakeToppingInfo(Long id, String name, int calories) { + this.id = id; + this.name = name; + this.calories = calories; } - @Override - public int compareTo(Message o) { - return priority - o.priority; + /** Constructor. */ + public CakeToppingInfo(String name, int calories) { + this.id = null; + this.name = name; + this.calories = calories; } @Override public String toString() { - return "Message{" - + "message='" + message + '\'' - + ", priority=" + priority - + '}'; + return String.format("CakeToppingInfo id=%d name=%s calories=%d", id, name, calories); } } diff --git a/layers/src/main/java/entity/Cake.java b/layered-architecture/src/main/java/entity/Cake.java similarity index 62% rename from layers/src/main/java/entity/Cake.java rename to layered-architecture/src/main/java/entity/Cake.java index a7d78e622252..58a8af659a60 100644 --- a/layers/src/main/java/entity/Cake.java +++ b/layered-architecture/src/main/java/entity/Cake.java @@ -22,68 +22,45 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package entity; -import java.util.HashSet; -import java.util.Set; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; -import jakarta.persistence.CascadeType; -import jakarta.persistence.FetchType; +import java.util.HashSet; +import java.util.Set; +import lombok.Getter; +import lombok.Setter; -/** - * Cake entity. - */ +/** Cake entity. */ @Entity +@Getter +@Setter public class Cake { - @Id - @GeneratedValue - private Long id; - - @OneToOne(cascade = CascadeType.REMOVE) - private CakeTopping topping; - - @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER) - private Set layers; - - public Cake() { - setLayers(new HashSet<>()); - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public CakeTopping getTopping() { - return topping; - } + @Id @GeneratedValue private Long id; - public void setTopping(CakeTopping topping) { - this.topping = topping; - } + @OneToOne(cascade = CascadeType.REMOVE) + private CakeTopping topping; - public Set getLayers() { - return layers; - } + @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER) + private Set layers; - public void setLayers(Set layers) { - this.layers = layers; - } + public Cake() { + setLayers(new HashSet<>()); + } - public void addLayer(CakeLayer layer) { - this.layers.add(layer); - } + public void addLayer(CakeLayer layer) { + this.layers.add(layer); + } - @Override - public String toString() { - return String.format("id=%s topping=%s layers=%s", id, topping, layers.toString()); - } + @Override + public String toString() { + return String.format("id=%s topping=%s layers=%s", id, topping, layers.toString()); + } } diff --git a/layered-architecture/src/main/java/entity/CakeLayer.java b/layered-architecture/src/main/java/entity/CakeLayer.java new file mode 100644 index 000000000000..798ecbd17664 --- /dev/null +++ b/layered-architecture/src/main/java/entity/CakeLayer.java @@ -0,0 +1,68 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** CakeLayer entity. */ +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode +public class CakeLayer { + + @Id @GeneratedValue private Long id; + + private String name; + + private int calories; + + @ManyToOne(cascade = CascadeType.ALL) + private Cake cake; + + public CakeLayer(String name, int calories) { + this.setName(name); + this.setCalories(calories); + } + + @Override + public String toString() { + return String.format("id=%s name=%s calories=%d", id, name, calories); + } +} diff --git a/layered-architecture/src/main/java/entity/CakeTopping.java b/layered-architecture/src/main/java/entity/CakeTopping.java new file mode 100644 index 000000000000..8b1c5f38be30 --- /dev/null +++ b/layered-architecture/src/main/java/entity/CakeTopping.java @@ -0,0 +1,68 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** CakeTopping entity. */ +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode +public class CakeTopping { + + @Id @GeneratedValue private Long id; + + private String name; + + private int calories; + + @OneToOne(cascade = CascadeType.ALL) + private Cake cake; + + public CakeTopping(String name, int calories) { + this.setName(name); + this.setCalories(calories); + } + + @Override + public String toString() { + return String.format("id=%s name=%s calories=%d", id, name, calories); + } +} diff --git a/layers/src/main/java/exception/CakeBakingException.java b/layered-architecture/src/main/java/exception/CakeBakingException.java similarity index 85% rename from layers/src/main/java/exception/CakeBakingException.java rename to layered-architecture/src/main/java/exception/CakeBakingException.java index 95a2c11716e4..13af2b550261 100644 --- a/layers/src/main/java/exception/CakeBakingException.java +++ b/layered-architecture/src/main/java/exception/CakeBakingException.java @@ -22,22 +22,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package exception; +import java.io.Serial; import org.springframework.stereotype.Component; -/** - * Custom exception used in cake baking. - */ +/** Custom exception used in cake baking. */ @Component public class CakeBakingException extends Exception { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; - public CakeBakingException() { - } + public CakeBakingException() {} - public CakeBakingException(String message) { - super(message); - } + public CakeBakingException(String message) { + super(message); + } } diff --git a/layers/src/main/java/service/CakeBakingService.java b/layered-architecture/src/main/java/service/CakeBakingService.java similarity index 68% rename from layers/src/main/java/service/CakeBakingService.java rename to layered-architecture/src/main/java/service/CakeBakingService.java index 7909b3f99be4..be34797d66c4 100644 --- a/layers/src/main/java/service/CakeBakingService.java +++ b/layered-architecture/src/main/java/service/CakeBakingService.java @@ -22,56 +22,41 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package service; import dto.CakeInfo; import dto.CakeLayerInfo; import dto.CakeToppingInfo; import exception.CakeBakingException; -import org.springframework.stereotype.Service; - import java.util.List; +import org.springframework.stereotype.Service; -/** - * Service for cake baking operations. - */ +/** Service for cake baking operations. */ @Service public interface CakeBakingService { - /** - * Bakes new cake according to parameters. - */ - void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException; - - /** - * Get all cakes. - */ - List getAllCakes(); + /** Bakes new cake according to parameters. */ + void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException; - /** - * Store new cake topping. - */ - void saveNewTopping(CakeToppingInfo toppingInfo); + /** Get all cakes. */ + List getAllCakes(); - /** - * Get available cake toppings. - */ - List getAvailableToppings(); + /** Store new cake topping. */ + void saveNewTopping(CakeToppingInfo toppingInfo); - /** - * Add new cake layer. - */ - void saveNewLayer(CakeLayerInfo layerInfo); + /** Get available cake toppings. */ + List getAvailableToppings(); - /** - * Get available cake layers. - */ - List getAvailableLayers(); + /** Add new cake layer. */ + void saveNewLayer(CakeLayerInfo layerInfo); - void deleteAllCakes(); + /** Get available cake layers. */ + List getAvailableLayers(); - void deleteAllLayers(); + void deleteAllCakes(); - void deleteAllToppings(); + void deleteAllLayers(); + void deleteAllToppings(); } diff --git a/layered-architecture/src/main/java/service/CakeBakingServiceImpl.java b/layered-architecture/src/main/java/service/CakeBakingServiceImpl.java new file mode 100644 index 000000000000..6af2123d9d68 --- /dev/null +++ b/layered-architecture/src/main/java/service/CakeBakingServiceImpl.java @@ -0,0 +1,199 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package service; + +import dao.CakeDao; +import dao.CakeLayerDao; +import dao.CakeToppingDao; +import dto.CakeInfo; +import dto.CakeLayerInfo; +import dto.CakeToppingInfo; +import entity.Cake; +import entity.CakeLayer; +import entity.CakeTopping; +import exception.CakeBakingException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** Implementation of CakeBakingService. */ +@Service +@Transactional +public class CakeBakingServiceImpl implements CakeBakingService { + + private final CakeDao cakeDao; + private final CakeLayerDao cakeLayerDao; + private final CakeToppingDao cakeToppingDao; + + /** + * Constructs a new instance of CakeBakingServiceImpl. + * + * @param cakeDao the DAO for cake-related operations + * @param cakeLayerDao the DAO for cake layer-related operations + * @param cakeToppingDao the DAO for cake topping-related operations + */ + @Autowired + public CakeBakingServiceImpl( + CakeDao cakeDao, CakeLayerDao cakeLayerDao, CakeToppingDao cakeToppingDao) { + this.cakeDao = cakeDao; + this.cakeLayerDao = cakeLayerDao; + this.cakeToppingDao = cakeToppingDao; + } + + @Override + public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException { + var allToppings = getAvailableToppingEntities(); + var matchingToppings = + allToppings.stream() + .filter(t -> t.getName().equals(cakeInfo.cakeToppingInfo.name)) + .toList(); + if (matchingToppings.isEmpty()) { + throw new CakeBakingException( + String.format("Topping %s is not available", cakeInfo.cakeToppingInfo.name)); + } + var allLayers = getAvailableLayerEntities(); + Set foundLayers = new HashSet<>(); + for (var info : cakeInfo.cakeLayerInfos) { + var found = allLayers.stream().filter(layer -> layer.getName().equals(info.name)).findFirst(); + if (found.isEmpty()) { + throw new CakeBakingException(String.format("Layer %s is not available", info.name)); + } else { + foundLayers.add(found.get()); + } + } + + var topping = cakeToppingDao.findById(matchingToppings.iterator().next().getId()); + if (topping.isPresent()) { + var cake = new Cake(); + cake.setTopping(topping.get()); + cake.setLayers(foundLayers); + cakeDao.save(cake); + topping.get().setCake(cake); + cakeToppingDao.save(topping.get()); + Set foundLayersToUpdate = + new HashSet<>(foundLayers); // copy set to avoid a ConcurrentModificationException + + for (var layer : foundLayersToUpdate) { + layer.setCake(cake); + cakeLayerDao.save(layer); + } + + } else { + throw new CakeBakingException( + String.format("Topping %s is not available", cakeInfo.cakeToppingInfo.name)); + } + } + + @Override + public void saveNewTopping(CakeToppingInfo toppingInfo) { + cakeToppingDao.save(new CakeTopping(toppingInfo.name, toppingInfo.calories)); + } + + @Override + public void saveNewLayer(CakeLayerInfo layerInfo) { + cakeLayerDao.save(new CakeLayer(layerInfo.name, layerInfo.calories)); + } + + private List getAvailableToppingEntities() { + List result = new ArrayList<>(); + for (CakeTopping topping : cakeToppingDao.findAll()) { + if (topping.getCake() == null) { + result.add(topping); + } + } + return result; + } + + @Override + public List getAvailableToppings() { + List result = new ArrayList<>(); + for (CakeTopping next : cakeToppingDao.findAll()) { + if (next.getCake() == null) { + result.add(new CakeToppingInfo(next.getId(), next.getName(), next.getCalories())); + } + } + return result; + } + + private List getAvailableLayerEntities() { + List result = new ArrayList<>(); + for (CakeLayer next : cakeLayerDao.findAll()) { + if (next.getCake() == null) { + result.add(next); + } + } + return result; + } + + @Override + public List getAvailableLayers() { + List result = new ArrayList<>(); + for (CakeLayer next : cakeLayerDao.findAll()) { + if (next.getCake() == null) { + result.add(new CakeLayerInfo(next.getId(), next.getName(), next.getCalories())); + } + } + return result; + } + + @Override + public void deleteAllCakes() { + cakeDao.deleteAll(); + } + + @Override + public void deleteAllLayers() { + cakeLayerDao.deleteAll(); + } + + @Override + public void deleteAllToppings() { + cakeToppingDao.deleteAll(); + } + + @Override + public List getAllCakes() { + List result = new ArrayList<>(); + for (Cake cake : cakeDao.findAll()) { + var cakeToppingInfo = + new CakeToppingInfo( + cake.getTopping().getId(), + cake.getTopping().getName(), + cake.getTopping().getCalories()); + List cakeLayerInfos = new ArrayList<>(); + for (var layer : cake.getLayers()) { + cakeLayerInfos.add(new CakeLayerInfo(layer.getId(), layer.getName(), layer.getCalories())); + } + var cakeInfo = new CakeInfo(cake.getId(), cakeToppingInfo, cakeLayerInfos); + result.add(cakeInfo); + } + return result; + } +} diff --git a/layers/src/main/java/view/CakeViewImpl.java b/layered-architecture/src/main/java/view/CakeViewImpl.java similarity index 76% rename from layers/src/main/java/view/CakeViewImpl.java rename to layered-architecture/src/main/java/view/CakeViewImpl.java index a060c7910f9e..a01d1c1600a0 100644 --- a/layers/src/main/java/view/CakeViewImpl.java +++ b/layered-architecture/src/main/java/view/CakeViewImpl.java @@ -22,26 +22,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package view; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import service.CakeBakingService; -/** - * View implementation for displaying cakes. - */ +/** View implementation for displaying cakes. */ public class CakeViewImpl implements View { - private final CakeBakingService cakeBakingService; + private final CakeBakingService cakeBakingService; - private static final Logger LOGGER = LoggerFactory.getLogger(CakeViewImpl.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CakeViewImpl.class); - public CakeViewImpl(CakeBakingService cakeBakingService) { - this.cakeBakingService = cakeBakingService; - } + public CakeViewImpl(CakeBakingService cakeBakingService) { + this.cakeBakingService = cakeBakingService; + } - public void render() { - cakeBakingService.getAllCakes().forEach(cake -> LOGGER.info(cake.toString())); - } + public void render() { + cakeBakingService.getAllCakes().forEach(cake -> LOGGER.info(cake.toString())); + } } diff --git a/layers/src/main/java/view/View.java b/layered-architecture/src/main/java/view/View.java similarity index 96% rename from layers/src/main/java/view/View.java rename to layered-architecture/src/main/java/view/View.java index fb49fc1b74f1..78403c6013a5 100644 --- a/layers/src/main/java/view/View.java +++ b/layered-architecture/src/main/java/view/View.java @@ -22,13 +22,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package view; -/** - * View interface. - */ +/** View interface. */ public interface View { - void render(); - + void render(); } diff --git a/layers/src/main/resources/META-INF/logback.xml b/layered-architecture/src/main/resources/META-INF/logback.xml similarity index 100% rename from layers/src/main/resources/META-INF/logback.xml rename to layered-architecture/src/main/resources/META-INF/logback.xml diff --git a/layers/src/main/resources/application.properties b/layered-architecture/src/main/resources/application.properties similarity index 100% rename from layers/src/main/resources/application.properties rename to layered-architecture/src/main/resources/application.properties diff --git a/layered-architecture/src/test/java/com/iluwatar/layers/app/LayersAppTests.java b/layered-architecture/src/test/java/com/iluwatar/layers/app/LayersAppTests.java new file mode 100644 index 000000000000..e51bca34d12f --- /dev/null +++ b/layered-architecture/src/test/java/com/iluwatar/layers/app/LayersAppTests.java @@ -0,0 +1,48 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.layers.app; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +@SpringBootTest(classes = LayersApp.class) +class LayersAppTests { + + private final ApplicationContext applicationContext; + + @Autowired + LayersAppTests(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Test + void contextLoads() { + assertNotNull(applicationContext); + } +} diff --git a/layers/src/test/java/com/iluwatar/layers/entity/CakeTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/entity/CakeTest.java similarity index 85% rename from layers/src/test/java/com/iluwatar/layers/entity/CakeTest.java rename to layered-architecture/src/test/java/com/iluwatar/layers/entity/CakeTest.java index e452419bc0d7..eea31e9355ac 100644 --- a/layers/src/test/java/com/iluwatar/layers/entity/CakeTest.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/entity/CakeTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.layers.entity; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -29,18 +30,17 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.HashSet; -import java.util.Set; - import entity.Cake; import entity.CakeLayer; import entity.CakeTopping; +import java.util.HashSet; +import java.util.Set; import org.junit.jupiter.api.Test; /** - * Date: 12/15/15 - 8:02 PM - * - * @author Jeroen Meulemeester + * This class contains unit tests for the Cake class. It tests the functionality of setting and + * getting the id, topping, and layers of a Cake object. It also tests the functionality of adding a + * layer to a Cake object and converting a Cake object to a string. */ class CakeTest { @@ -70,10 +70,11 @@ void testSetLayers() { assertNotNull(cake.getLayers()); assertTrue(cake.getLayers().isEmpty()); - final var expectedLayers = Set.of( - new CakeLayer("layer1", 1000), - new CakeLayer("layer2", 2000), - new CakeLayer("layer3", 3000)); + final var expectedLayers = + Set.of( + new CakeLayer("layer1", 1000), + new CakeLayer("layer2", 2000), + new CakeLayer("layer3", 3000)); cake.setLayers(expectedLayers); assertEquals(expectedLayers, cake.getLayers()); } @@ -114,10 +115,9 @@ void testToString() { cake.setTopping(topping); cake.addLayer(layer); - final var expected = "id=1234 topping=id=2345 name=topping calories=20 " - + "layers=[id=3456 name=layer calories=100]"; + final var expected = + "id=1234 topping=id=2345 name=topping calories=20 " + + "layers=[id=3456 name=layer calories=100]"; assertEquals(expected, cake.toString()); - } - } diff --git a/layered-architecture/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java new file mode 100644 index 000000000000..9acb49d2a289 --- /dev/null +++ b/layered-architecture/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java @@ -0,0 +1,69 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.layers.exception; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import exception.CakeBakingException; +import org.junit.jupiter.api.Test; + +/** + * Tests for the {@link CakeBakingException} class. This class contains unit tests to verify the + * correct functionality of the {@code CakeBakingException} class constructors, including the + * default constructor and the constructor that accepts a message parameter. + */ +class CakeBakingExceptionTest { + + /** + * Tests the default constructor of {@link CakeBakingException}. Ensures that an exception created + * with the default constructor has {@code null} as its message and cause. + */ + @Test + void testConstructor() { + final var exception = new CakeBakingException(); + assertNull(exception.getMessage(), "The message should be null for the default constructor."); + assertNull(exception.getCause(), "The cause should be null for the default constructor."); + } + + /** + * Tests the constructor of {@link CakeBakingException} that accepts a message. Ensures that an + * exception created with this constructor correctly stores the provided message and has {@code + * null} as its cause. + */ + @Test + void testConstructorWithMessage() { + final var expectedMessage = "message"; + final var exception = new CakeBakingException(expectedMessage); + assertEquals( + expectedMessage, + exception.getMessage(), + "The stored message should match the expected message."); + assertNull( + exception.getCause(), + "The cause should be null when an exception is created with only a message."); + } +} diff --git a/layers/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java similarity index 90% rename from layers/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java rename to layered-architecture/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java index 2770689b7c8d..a14c0076073a 100644 --- a/layers/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.layers.service; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -35,22 +36,15 @@ import dto.CakeLayerInfo; import dto.CakeToppingInfo; import exception.CakeBakingException; +import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import service.CakeBakingServiceImpl; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - - -/** - * Date: 12/15/15 - 9:55 PM - * - * @author Jeroen Meulemeester - */ +/** Constructs a new instance of CakeBakingServiceImplTest. */ @SpringBootTest(classes = LayersApp.class) class CakeBakingServiceImplTest { @@ -62,15 +56,13 @@ class CakeBakingServiceImplTest { } @BeforeEach - void setUp() { - cakeBakingService.deleteAllCakes(); - cakeBakingService.deleteAllLayers(); - cakeBakingService.deleteAllToppings(); - } - + void setUp() { + cakeBakingService.deleteAllCakes(); + cakeBakingService.deleteAllLayers(); + cakeBakingService.deleteAllToppings(); + } @Test - void testLayers() { final var initialLayers = cakeBakingService.getAvailableLayers(); assertNotNull(initialLayers); @@ -88,7 +80,6 @@ void testLayers() { assertNotNull(layer.toString()); assertTrue(layer.calories > 0); } - } @Test @@ -109,7 +100,6 @@ void testToppings() { assertNotNull(topping.toString()); assertTrue(topping.calories > 0); } - } @Test @@ -145,7 +135,6 @@ void testBakeCakes() throws CakeBakingException { assertFalse(cakeInfo.cakeLayerInfos.isEmpty()); assertTrue(cakeInfo.calculateTotalCalories() > 0); } - } @Test @@ -156,7 +145,9 @@ void testBakeCakeMissingTopping() { cakeBakingService.saveNewLayer(layer2); final var missingTopping = new CakeToppingInfo("Topping1", 1000); - assertThrows(CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(missingTopping, List.of(layer1, layer2)))); + assertThrows( + CakeBakingException.class, + () -> cakeBakingService.bakeNewCake(new CakeInfo(missingTopping, List.of(layer1, layer2)))); } @Test @@ -172,7 +163,9 @@ void testBakeCakeMissingLayer() { cakeBakingService.saveNewLayer(layer1); final var missingLayer = new CakeLayerInfo("Layer2", 2000); - assertThrows(CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, missingLayer)))); + assertThrows( + CakeBakingException.class, + () -> cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, missingLayer)))); } @Test @@ -192,7 +185,10 @@ void testBakeCakesUsedLayer() throws CakeBakingException { cakeBakingService.saveNewLayer(layer2); cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, layer2))); - assertThrows(CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer2)))); + assertThrows( + CakeBakingException.class, + () -> + cakeBakingService.bakeNewCake( + new CakeInfo(topping2, Collections.singletonList(layer2)))); } - } diff --git a/layers/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java similarity index 89% rename from layers/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java rename to layered-architecture/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java index 89487203758b..2e3b1ae598b8 100644 --- a/layers/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.layers.view; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,19 +35,19 @@ import dto.CakeInfo; import dto.CakeLayerInfo; import dto.CakeToppingInfo; -import service.CakeBakingService; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; +import service.CakeBakingService; import view.CakeViewImpl; /** - * Date: 12/15/15 - 10:04 PM - * - * @author Jeroen Meulemeester + * This class contains unit tests for the CakeViewImpl class. It tests the functionality of + * rendering cakes using the CakeViewImpl class. It also tests the logging functionality of the + * CakeViewImpl class. */ class CakeViewImplTest { @@ -62,13 +63,12 @@ void tearDown() { appender.stop(); } - /** - * Verify if the cake view renders the expected result - */ + /** Verify if the cake view renders the expected result. */ @Test void testRender() { - final var layers = List.of( + final var layers = + List.of( new CakeLayerInfo("layer1", 1000), new CakeLayerInfo("layer2", 2000), new CakeLayerInfo("layer3", 3000)); @@ -85,10 +85,9 @@ void testRender() { cakeView.render(); assertEquals(cake.toString(), appender.getLastMessage()); - } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); @@ -110,5 +109,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/layers/README.md b/layers/README.md deleted file mode 100644 index c57307eed29b..000000000000 --- a/layers/README.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Layers -category: Architectural -language: en -tag: -- Decoupling ---- - -## Intent - -Layers is an architectural pattern where software responsibilities are divided among the different -layers of the application. - -## Explanation - -Real world example - -> Consider a website displaying decorated cakes for weddings and such. Instead of the web page -> directly reaching into the database, it relies on a service to deliver this information. The -> service then queries the data layer to assimilate the needed information. -In plain words - -> With Layers architectural pattern different concerns reside on separate layers. View layer is -> interested only in rendering, service layer assembles the requested data from various sources, and -> data layer gets the bits from the data storage. -Wikipedia says - -> In software engineering, multitier architecture (often referred to as n-tier architecture) or -> multilayered architecture is a client–server architecture in which presentation, application -> processing, and data management functions are physically separated. - -**Programmatic Example** - -On the data layer, we keep our cake building blocks. `Cake` consist of layers and topping. - -```java -@Entity -public class Cake { - @Id - @GeneratedValue - private Long id; - @OneToOne(cascade = CascadeType.REMOVE) - private CakeTopping topping; - @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER) - private Set layers; -} -``` - -The service layer offers `CakeBakingService` for easy access to different aspects of cakes. - -```java -public interface CakeBakingService { - void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException; - List getAllCakes(); - void saveNewTopping(CakeToppingInfo toppingInfo); - List getAvailableToppings(); - void saveNewLayer(CakeLayerInfo layerInfo); - List getAvailableLayers(); -} -``` - -On the top we have our `View` responsible of rendering the cakes. - -```java -public interface View { - void render(); -} -@Slf4j -public class CakeViewImpl implements View { - private final CakeBakingService cakeBakingService; - public CakeViewImpl(CakeBakingService cakeBakingService) { - this.cakeBakingService = cakeBakingService; - } - public void render() { - cakeBakingService.getAllCakes().forEach(cake -> LOGGER.info(cake.toString())); - } -} -``` - -## Class diagram - -![alt text](./etc/layers.png "Layers") - -## Applicability - -Use the Layers architecture when - -* You want clearly divide software responsibilities into different parts of the program. -* You want to prevent a change from propagating throughout the application. -* You want to make your application more maintainable and testable. - -## Credits - -* [Pattern Oriented Software Architecture Volume 1: A System of Patterns](https://www.amazon.com/gp/product/0471958697/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0471958697&linkCode=as2&tag=javadesignpat-20&linkId=e3f42d7a2a4cc8c619bbc0136b20dadb) diff --git a/layers/src/main/java/com/iluwatar/layers/Runner.java b/layers/src/main/java/com/iluwatar/layers/Runner.java deleted file mode 100644 index 6acfca8b7d1c..000000000000 --- a/layers/src/main/java/com/iluwatar/layers/Runner.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.iluwatar.layers; - -import dto.CakeInfo; -import dto.CakeLayerInfo; -import dto.CakeToppingInfo; -import exception.CakeBakingException; -import lombok.extern.slf4j.Slf4j; -import service.CakeBakingService; -import view.CakeViewImpl; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -@Slf4j -public class Runner implements CommandLineRunner { - private final CakeBakingService cakeBakingService; - public static final String STRAWBERRY = "strawberry"; - - @Autowired - public Runner(CakeBakingService cakeBakingService) { - this.cakeBakingService = cakeBakingService; - } - @Override - public void run(String... args) { - //initialize sample data - initializeData(); - // create view and render it - var cakeView = new CakeViewImpl(cakeBakingService); - cakeView.render(); - } - - /** - * Initializes the example data. - */ - private void initializeData() { - cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200)); - cakeBakingService.saveNewLayer(new CakeLayerInfo("banana", 900)); - cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950)); - cakeBakingService.saveNewLayer(new CakeLayerInfo("lemon", 950)); - cakeBakingService.saveNewLayer(new CakeLayerInfo("vanilla", 950)); - cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950)); - - cakeBakingService.saveNewTopping(new CakeToppingInfo("candies", 350)); - cakeBakingService.saveNewTopping(new CakeToppingInfo("cherry", 350)); - - var cake1 = new CakeInfo(new CakeToppingInfo("candies", 0), List.of( - new CakeLayerInfo("chocolate", 0), - new CakeLayerInfo("banana", 0), - new CakeLayerInfo(STRAWBERRY, 0))); - try { - cakeBakingService.bakeNewCake(cake1); - } catch (CakeBakingException e) { - LOGGER.error("Cake baking exception", e); - } - var cake2 = new CakeInfo(new CakeToppingInfo("cherry", 0), List.of( - new CakeLayerInfo("vanilla", 0), - new CakeLayerInfo("lemon", 0), - new CakeLayerInfo(STRAWBERRY, 0))); - try { - cakeBakingService.bakeNewCake(cake2); - } catch (CakeBakingException e) { - LOGGER.error("Cake baking exception", e); - } - } -} diff --git a/layers/src/main/java/com/iluwatar/layers/app/LayersApp.java b/layers/src/main/java/com/iluwatar/layers/app/LayersApp.java deleted file mode 100644 index af73f651f8d1..000000000000 --- a/layers/src/main/java/com/iluwatar/layers/app/LayersApp.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.iluwatar.layers.app; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@SpringBootApplication -@EnableJpaRepositories(basePackages = "dao") -@EntityScan(basePackages = "entity") -@ComponentScan(basePackages = {"com.iluwatar.layers", "service", "dto", "exception", "view" ,"dao"}) -public class LayersApp { - - public static void main(String[] args) { - SpringApplication.run(LayersApp.class, args); - - } - -} diff --git a/layers/src/main/java/dto/CakeInfo.java b/layers/src/main/java/dto/CakeInfo.java deleted file mode 100644 index 8cc25e4ecb4b..000000000000 --- a/layers/src/main/java/dto/CakeInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package dto; - -import java.util.List; -import java.util.Optional; - -/** - * DTO for cakes. - */ -public class CakeInfo { - - public final Optional id; - public final CakeToppingInfo cakeToppingInfo; - public final List cakeLayerInfos; - - /** - * Constructor. - */ - public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { - this.id = Optional.of(id); - this.cakeToppingInfo = cakeToppingInfo; - this.cakeLayerInfos = cakeLayerInfos; - } - - /** - * Constructor. - */ - public CakeInfo(CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { - this.id = Optional.empty(); - this.cakeToppingInfo = cakeToppingInfo; - this.cakeLayerInfos = cakeLayerInfos; - } - - /** - * Calculate calories. - */ - public int calculateTotalCalories() { - var total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0; - total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum(); - return total; - } - - @Override - public String toString() { - return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id.orElse(-1L), - cakeToppingInfo, cakeLayerInfos, calculateTotalCalories()); - } -} diff --git a/layers/src/main/java/dto/CakeLayerInfo.java b/layers/src/main/java/dto/CakeLayerInfo.java deleted file mode 100644 index 4c74f47dbda5..000000000000 --- a/layers/src/main/java/dto/CakeLayerInfo.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package dto; - -import java.util.Optional; - -/** - * DTO for cake layers. - */ -public class CakeLayerInfo { - - public final Optional id; - public final String name; - public final int calories; - - /** - * Constructor. - */ - public CakeLayerInfo(Long id, String name, int calories) { - this.id = Optional.of(id); - this.name = name; - this.calories = calories; - } - - /** - * Constructor. - */ - public CakeLayerInfo(String name, int calories) { - this.id = Optional.empty(); - this.name = name; - this.calories = calories; - } - - @Override - public String toString() { - return String.format("CakeLayerInfo id=%d name=%s calories=%d", id.orElse(-1L), name, calories); - } -} diff --git a/layers/src/main/java/dto/CakeToppingInfo.java b/layers/src/main/java/dto/CakeToppingInfo.java deleted file mode 100644 index 7a3feab0ebc7..000000000000 --- a/layers/src/main/java/dto/CakeToppingInfo.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package dto; - - -import java.util.Optional; - -/** - * DTO for cake toppings. - */ -public class CakeToppingInfo { - - public final Optional id; - public final String name; - public final int calories; - - /** - * Constructor. - */ - public CakeToppingInfo(Long id, String name, int calories) { - this.id = Optional.of(id); - this.name = name; - this.calories = calories; - } - - /** - * Constructor. - */ - public CakeToppingInfo(String name, int calories) { - this.id = Optional.empty(); - this.name = name; - this.calories = calories; - } - - @Override - public String toString() { - return String.format("CakeToppingInfo id=%d name=%s calories=%d", - id.orElse(-1L), name, calories); - } -} diff --git a/layers/src/main/java/service/CakeBakingServiceImpl.java b/layers/src/main/java/service/CakeBakingServiceImpl.java deleted file mode 100644 index 486e3e6d5d4d..000000000000 --- a/layers/src/main/java/service/CakeBakingServiceImpl.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package service; - -import dao.CakeDao; -import dao.CakeLayerDao; -import dao.CakeToppingDao; -import dto.CakeInfo; -import dto.CakeLayerInfo; -import dto.CakeToppingInfo; -import entity.Cake; -import entity.CakeLayer; -import entity.CakeTopping; -import exception.CakeBakingException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * Implementation of CakeBakingService. - */ -@Service -@Transactional -public class CakeBakingServiceImpl implements CakeBakingService { - - private final CakeDao cakeDao; - private final CakeLayerDao cakeLayerDao; - private final CakeToppingDao cakeToppingDao; - - @Autowired - public CakeBakingServiceImpl(CakeDao cakeDao, CakeLayerDao cakeLayerDao, CakeToppingDao cakeToppingDao) { - this.cakeDao = cakeDao; - this.cakeLayerDao = cakeLayerDao; - this.cakeToppingDao = cakeToppingDao; - } - - @Override - public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException { - var allToppings = getAvailableToppingEntities(); - var matchingToppings = - allToppings.stream().filter(t -> t.getName().equals(cakeInfo.cakeToppingInfo.name)) - .toList(); - if (matchingToppings.isEmpty()) { - throw new CakeBakingException(String.format("Topping %s is not available", - cakeInfo.cakeToppingInfo.name)); - } - var allLayers = getAvailableLayerEntities(); - Set foundLayers = new HashSet<>(); - for (var info : cakeInfo.cakeLayerInfos) { - var found = allLayers.stream().filter(layer -> layer.getName().equals(info.name)).findFirst(); - if (found.isEmpty()) { - throw new CakeBakingException(String.format("Layer %s is not available", info.name)); - } else { - foundLayers.add(found.get()); - } - } - - var topping = cakeToppingDao.findById(matchingToppings.iterator().next().getId()); - if (topping.isPresent()) { - var cake = new Cake(); - cake.setTopping(topping.get()); - cake.setLayers(foundLayers); - cakeDao.save(cake); - topping.get().setCake(cake); - cakeToppingDao.save(topping.get()); - Set foundLayersToUpdate = new HashSet<>(foundLayers); // copy set to avoid a ConcurrentModificationException - - for (var layer : foundLayersToUpdate) { - layer.setCake(cake); - cakeLayerDao.save(layer); - } - - } else { - throw new CakeBakingException(String.format("Topping %s is not available", - cakeInfo.cakeToppingInfo.name)); - } - } - - @Override - public void saveNewTopping(CakeToppingInfo toppingInfo) { - cakeToppingDao.save(new CakeTopping(toppingInfo.name, toppingInfo.calories)); - } - - @Override - public void saveNewLayer(CakeLayerInfo layerInfo) { - cakeLayerDao.save(new CakeLayer(layerInfo.name, layerInfo.calories)); - } - - private List getAvailableToppingEntities() { - List result = new ArrayList<>(); - for (CakeTopping topping : cakeToppingDao.findAll()) { - if (topping.getCake() == null) { - result.add(topping); - } - } - return result; - } - - @Override - public List getAvailableToppings() { - List result = new ArrayList<>(); - for (CakeTopping next : cakeToppingDao.findAll()) { - if (next.getCake() == null) { - result.add(new CakeToppingInfo(next.getId(), next.getName(), next.getCalories())); - } - } - return result; - } - - private List getAvailableLayerEntities() { - List result = new ArrayList<>(); - for (CakeLayer next : cakeLayerDao.findAll()) { - if (next.getCake() == null) { - result.add(next); - } - } - return result; - } - - @Override - public List getAvailableLayers() { - List result = new ArrayList<>(); - for (CakeLayer next : cakeLayerDao.findAll()) { - if (next.getCake() == null) { - result.add(new CakeLayerInfo(next.getId(), next.getName(), next.getCalories())); - } - } - return result; - } - - @Override - public void deleteAllCakes() { - cakeDao.deleteAll(); - } - - @Override - public void deleteAllLayers() { - cakeLayerDao.deleteAll(); - } - - @Override - public void deleteAllToppings() { - cakeToppingDao.deleteAll(); - } - - @Override - public List getAllCakes() { - List result = new ArrayList<>(); - for (Cake cake : cakeDao.findAll()) { - var cakeToppingInfo = - new CakeToppingInfo(cake.getTopping().getId(), cake.getTopping().getName(), cake - .getTopping().getCalories()); - List cakeLayerInfos = new ArrayList<>(); - for (var layer : cake.getLayers()) { - cakeLayerInfos.add(new CakeLayerInfo(layer.getId(), layer.getName(), layer.getCalories())); - } - var cakeInfo = new CakeInfo(cake.getId(), cakeToppingInfo, cakeLayerInfos); - result.add(cakeInfo); - } - return result; - } -} diff --git a/layers/src/test/java/com/iluwatar/layers/app/LayersAppTests.java b/layers/src/test/java/com/iluwatar/layers/app/LayersAppTests.java deleted file mode 100644 index e7e50a266757..000000000000 --- a/layers/src/test/java/com/iluwatar/layers/app/LayersAppTests.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.iluwatar.layers.app; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -@SpringBootTest(classes = LayersApp.class) -class LayersAppTests { - - private final ApplicationContext applicationContext; - - @Autowired - LayersAppTests(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - @Test - void contextLoads() { - assertNotNull(applicationContext); - } - -} diff --git a/lazy-loading/README.md b/lazy-loading/README.md index 959dadb77a1d..ea761250c929 100644 --- a/lazy-loading/README.md +++ b/lazy-loading/README.md @@ -1,30 +1,174 @@ --- -title: Lazy Loading -category: Idiom +title: "Lazy Loading Pattern in Java: Enhancing Performance with On-Demand Object Initialization" +shortTitle: Lazy Loading +description: "Learn how to implement the Lazy Loading design pattern in Java to optimize memory usage and improve application startup times. Discover practical examples, benefits, and best practices for efficient resource management." +category: Performance optimization language: en tag: - - Performance + - Instantiation + - Memory management + - Optimization + - Performance + - Persistence + - Resource management --- -## Intent -Lazy loading is a design pattern commonly used to defer -initialization of an object until the point at which it is needed. It can -contribute to efficiency in the program's operation if properly and -appropriately used. +## Also known as -## Class diagram -![alt text](./etc/lazy-loading.png "Lazy Loading") +* Lazy Initialization -## Applicability -Use the Lazy Loading idiom when +## Intent of Lazy Loading Design Pattern -* eager loading is expensive or the object to be loaded might not be needed at all +The Lazy Loading design pattern in Java defers object initialization until the object is actually needed, minimizing memory usage and reducing startup times. This technique is crucial for optimizing Java application performance. -## Real world examples +## Detailed Explanation of Lazy Loading Pattern with Real-World Examples +Real-world example + +> A real-world analogy for the Lazy Loading pattern in Java is using lights in a smart home. Instead of switching all lights on at once when someone enters the house, motion sensors detect and turn on lights only in rooms being used. This mirrors how Java developers can optimize performance by delaying object creation. + +In plain words + +> The Lazy Loading pattern defers the creation of an object or resource until it's actually needed, optimizing memory usage and improving performance. + +Wikipedia says + +> Lazy loading (also known as asynchronous loading) is a technique used in computer programming, especially web design and web development, to defer initialization of an object until it is needed. It can contribute to efficiency in the program's operation if properly and appropriately used. This makes it ideal in use cases where network content is accessed and initialization times are to be kept at a minimum, such as in the case of web pages. For example, deferring loading of images on a web page until they are needed for viewing can make the initial display of the web page faster. The opposite of lazy loading is eager loading. + +## Programmatic Example of Lazy Loading Pattern in Java + +The Lazy Loading design pattern is a performance optimization technique that delays the initialization of an object or a costly computation until it's absolutely necessary. This pattern can significantly improve the performance of your application by avoiding unnecessary computation and reducing memory usage. + +In the provided code, we can see an example of the Lazy Loading pattern in the `App`, `HolderNaive`, `HolderThreadSafe`, and `Java8Holder` classes. + +The `App` class is the entry point of the application. It creates instances of `HolderNaive`, `HolderThreadSafe`, and `Java8Holder`, and retrieves the `Heavy` object from them. + +```java +@Slf4j +public class App { + + public static void main(String[] args) { + + var holderNaive = new HolderNaive(); + var heavy = holderNaive.getHeavy(); + LOGGER.info("heavy={}", heavy); + + var holderThreadSafe = new HolderThreadSafe(); + var another = holderThreadSafe.getHeavy(); + LOGGER.info("another={}", another); + + var java8Holder = new Java8Holder(); + var next = java8Holder.getHeavy(); + LOGGER.info("next={}", next); + } +} +``` + +The `HolderNaive`, `HolderThreadSafe`, and `Java8Holder` classes are implementations of the Lazy Loading pattern with increasing sophistication. + +The `HolderNaive` class is a simple, non-thread-safe implementation of the pattern. + +```java +public class HolderNaive { + private Heavy heavy; + + public HolderNaive() { + LOGGER.info("HolderNaive created"); + } + + public Heavy getHeavy() { + if (heavy == null) { + heavy = new Heavy(); + } + return heavy; + } +} +``` + +The `HolderThreadSafe` class is a thread-safe implementation of the pattern, but with heavy synchronization on each access. + +```java +public class HolderThreadSafe { + private Heavy heavy; + + public synchronized Heavy getHeavy() { + if (heavy == null) { + heavy = new Heavy(); + } + return heavy; + } +} +``` + +The `Java8Holder` class is the most efficient implementation of the pattern, utilizing Java 8 features. + +```java +public class Java8Holder { + + private Supplier heavy = this::createAndCacheHeavy; + + public Java8Holder() { + LOGGER.info("Java8Holder created"); + } + + public Heavy getHeavy() { + return heavy.get(); + } + + private synchronized Heavy createAndCacheHeavy() { + class HeavyFactory implements Supplier { + private final Heavy heavyInstance = new Heavy(); + + public Heavy get() { + return heavyInstance; + } + } + + if (!(heavy instanceof HeavyFactory)) { + heavy = new HeavyFactory(); + } + + return heavy.get(); + } +} +``` + +In this example, the `App` class retrieves a `Heavy` object from `HolderNaive`, `HolderThreadSafe`, and `Java8Holder`. These classes delay the creation of the `Heavy` object until it's actually needed, demonstrating the Lazy Loading pattern. + +## When to Use the Lazy Loading Pattern in Java + +Use Lazy Loading when: + +* An object is resource-intensive to create and might not always be used. +* You need to delay object creation to optimize memory usage or improve startup times. +* Loading data or resources should happen just-in-time rather than at application startup. + +## Real-World Applications of Lazy Loading Pattern in Java + +* Hibernate (Java ORM Framework): Delays loading of related objects until they are accessed, leveraging the Lazy Loading pattern to optimize Java application performance. * JPA annotations @OneToOne, @OneToMany, @ManyToOne, @ManyToMany and fetch = FetchType.LAZY +* Spring Framework (Dependency Injection): Loads beans only when required, reducing application startup time. + +## Benefits and Trade-offs of Lazy Loading Pattern + +Benefits: + +* Reduces memory usage by initializing objects only when required. +* Improves application startup performance by postponing expensive object creation. + +Trade-offs: + +* Complexity in implementation if objects are interdependent. +* Risk of latency spikes if initialization occurs at an unexpected point. + +## Related Java Design Patterns + +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Can act as a placeholder for lazy-loaded objects, deferring their actual loading until necessary. +* Virtual Proxy: Specific type of Proxy that handles object creation on demand. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often combined with Lazy Loading to ensure only one instance of an object is created and loaded lazily. -## Credits +## References and Credits -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Java Performance: The Definitive Guide: Getting the Most Out of Your Code](https://amzn.to/3Wu5neF) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index 8d777fc768f6..9b2061c2cc6a 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -34,6 +34,14 @@ lazy-loading + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java index 31f90b68f32a..0c96768b67a1 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Heavy objects are expensive to create. - */ +/** Heavy objects are expensive to create. */ @Slf4j public class Heavy { - /** - * Constructor. - */ + /** Constructor. */ public Heavy() { LOGGER.info("Creating Heavy ..."); try { diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java index 054ab6bccd7a..55281a7e666a 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java @@ -26,24 +26,18 @@ import lombok.extern.slf4j.Slf4j; -/** - * Simple implementation of the lazy loading idiom. However, this is not thread safe. - */ +/** Simple implementation of the lazy loading idiom. However, this is not thread safe. */ @Slf4j public class HolderNaive { private Heavy heavy; - /** - * Constructor. - */ + /** Constructor. */ public HolderNaive() { LOGGER.info("HolderNaive created"); } - /** - * Get heavy object. - */ + /** Get heavy object. */ public Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java index cb0f7a2a8b1d..f698a05791da 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java @@ -35,16 +35,12 @@ public class HolderThreadSafe { private Heavy heavy; - /** - * Constructor. - */ + /** Constructor. */ public HolderThreadSafe() { LOGGER.info("HolderThreadSafe created"); } - /** - * Get heavy object. - */ + /** Get heavy object. */ public synchronized Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java index aea316e485b2..72cdd9e478d5 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java @@ -32,11 +32,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/19/15 - 11:58 AM - * - * @author Jeroen Meulemeester - */ +/** AbstractHolderTest */ public abstract class AbstractHolderTest { /** @@ -51,19 +47,20 @@ public abstract class AbstractHolderTest { * * @return The lazy loaded {@link Heavy} object */ - abstract Heavy getHeavy() throws Exception; + abstract Heavy getHeavy(); /** * This test shows that the heavy field is not instantiated until the method getHeavy is called */ @Test - void testGetHeavy() throws Exception { - assertTimeout(ofMillis(3000), () -> { - assertNull(getInternalHeavyValue()); - assertNotNull(getHeavy()); - assertNotNull(getInternalHeavyValue()); - assertSame(getHeavy(), getInternalHeavyValue()); - }); + void testGetHeavy() { + assertTimeout( + ofMillis(3000), + () -> { + assertNull(getInternalHeavyValue()); + assertNotNull(getHeavy()); + assertNotNull(getInternalHeavyValue()); + assertSame(getHeavy(), getInternalHeavyValue()); + }); } - } diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java index 3b45c27c0b34..3f3e5b28a063 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java @@ -24,19 +24,15 @@ */ package com.iluwatar.lazy.loading; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * - * Application test - * - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java index f1ee85b808c7..e165c4a9b6a6 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java @@ -24,13 +24,7 @@ */ package com.iluwatar.lazy.loading; -import java.lang.reflect.Field; - -/** - * Date: 12/19/15 - 12:05 PM - * - * @author Jeroen Meulemeester - */ +/** HolderNaiveTest */ class HolderNaiveTest extends AbstractHolderTest { private final HolderNaive holder = new HolderNaive(); @@ -46,5 +40,4 @@ Heavy getInternalHeavyValue() throws Exception { Heavy getHeavy() { return holder.getHeavy(); } - -} \ No newline at end of file +} diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java index 6d5fda756e32..e6c8efb4b370 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java @@ -24,13 +24,7 @@ */ package com.iluwatar.lazy.loading; -import java.lang.reflect.Field; - -/** - * Date: 12/19/15 - 12:19 PM - * - * @author Jeroen Meulemeester - */ +/** HolderThreadSafeTest */ class HolderThreadSafeTest extends AbstractHolderTest { private final HolderThreadSafe holder = new HolderThreadSafe(); @@ -43,8 +37,7 @@ Heavy getInternalHeavyValue() throws Exception { } @Override - Heavy getHeavy() throws Exception { + Heavy getHeavy() { return this.holder.getHeavy(); } - -} \ No newline at end of file +} diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java index 81c2f84e4030..41c1d4886fe2 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java @@ -24,19 +24,13 @@ */ package com.iluwatar.lazy.loading; -import java.lang.reflect.Field; import java.util.function.Supplier; -/** - * Date: 12/19/15 - 12:27 PM - * - * @author Jeroen Meulemeester - */ +/** Java8HolderTest */ class Java8HolderTest extends AbstractHolderTest { private final Java8Holder holder = new Java8Holder(); - @Override Heavy getInternalHeavyValue() throws Exception { final var holderField = Java8Holder.class.getDeclaredField("heavy"); @@ -57,8 +51,7 @@ Heavy getInternalHeavyValue() throws Exception { } @Override - Heavy getHeavy() throws Exception { + Heavy getHeavy() { return holder.getHeavy(); } - -} \ No newline at end of file +} diff --git a/leader-election/README.md b/leader-election/README.md index ec57487a9c03..282ea9bac874 100644 --- a/leader-election/README.md +++ b/leader-election/README.md @@ -1,842 +1,183 @@ --- -title: Leader Election -category: Behavioral +title: "Leader Election Pattern in Java: Mastering Node Coordination and Consensus" +shortTitle: Leader Election +description: "Learn how the Leader Election design pattern helps Java applications maintain consistency, fault tolerance, and scalability in distributed systems. Discover examples, use cases, and implementation details." +category: Concurrency language: en tag: - - Cloud distributed + - API design + - Cloud distributed + - Fault tolerance + - Scalability + - Synchronization --- -## Intent -Leader Election pattern is commonly used in cloud system design. It can help to ensure that task -instances select the leader instance correctly and do not conflict with each other, cause -contention for shared resources, or inadvertently interfere with the work that other task -instances are performing. +## Also known as -## Explanation +* Coordinator Election +* Master Election -Real world example -> In a horizontally scaling cloud-based system, multiple instances of the same task could be -> running at the same time with each instance serving a different user. If these instances -> write to a shared resource, it's necessary to coordinate their actions to prevent each instance -> from overwriting the changes made by the others. In another scenario, if the tasks are performing -> individual elements of a complex calculation in parallel, the results need to be aggregated -> when they all complete. +## Intent of Leader Election Design Pattern -In plain words -> this pattern is used in distributed cloud-based system where leader must need to act as -> coordinator/aggregator among horizontal scaling instances for avoiding conflict on shared resources. - -Wikipedia says -> In distributed computing, leader election is the process of designating a single process as -> the organizer of some task distributed among several computers (nodes). - -**Programmatic Example** - -Ring algorithm and Bully algorithm implement leader election pattern. -Each algorithm require every participated instance to have unique ID. - -```java -public interface Instance { - - /** - * Check if the instance is alive or not. - * - * @return {@code true} if the instance is alive. - */ - boolean isAlive(); - - /** - * Set the health status of the certain instance. - * - * @param alive {@code true} for alive. - */ - void setAlive(boolean alive); - - /** - * Consume messages from other instances. - * - * @param message Message sent by other instances - */ - void onMessage(Message message); - -} -``` - -Abstract class who implements Instance and Runnable interfaces. -```java -public abstract class AbstractInstance implements Instance, Runnable { - - protected static final int HEARTBEAT_INTERVAL = 5000; - private static final String INSTANCE = "Instance "; - - protected MessageManager messageManager; - protected Queue messageQueue; - protected final int localId; - protected int leaderId; - protected boolean alive; +The Leader Election design pattern is crucial for enabling a system to elect a leader from a group of nodes, ensuring the leader is consistently recognized and able to coordinate tasks while other nodes remain followers. This pattern is fundamental in distributed systems, particularly for achieving fault tolerance and high availability. - /** - * Constructor of BullyInstance. - */ - public AbstractInstance(MessageManager messageManager, int localId, int leaderId) { - this.messageManager = messageManager; - this.messageQueue = new ConcurrentLinkedQueue<>(); - this.localId = localId; - this.leaderId = leaderId; - this.alive = true; - } - - /** - * The instance will execute the message in its message queue periodically once it is alive. - */ - @Override - @SuppressWarnings("squid:S2189") - public void run() { - while (true) { - if (!this.messageQueue.isEmpty()) { - this.processMessage(this.messageQueue.remove()); - } - } - } - - /** - * Once messages are sent to the certain instance, it will firstly be added to the queue and wait - * to be executed. - * - * @param message Message sent by other instances - */ - @Override - public void onMessage(Message message) { - messageQueue.offer(message); - } +## Detailed Explanation of Leader Election Pattern with Real-World Examples - /** - * Check if the instance is alive or not. - * - * @return {@code true} if the instance is alive. - */ - @Override - public boolean isAlive() { - return alive; - } - - /** - * Set the health status of the certain instance. - * - * @param alive {@code true} for alive. - */ - @Override - public void setAlive(boolean alive) { - this.alive = alive; - } +Real-world example - /** - * Process the message according to its type. - * - * @param message Message polled from queue. - */ - private void processMessage(Message message) { - switch (message.getType()) { - case ELECTION -> { - LOGGER.info(INSTANCE + localId + " - Election Message handling..."); - handleElectionMessage(message); - } - case LEADER -> { - LOGGER.info(INSTANCE + localId + " - Leader Message handling..."); - handleLeaderMessage(message); - } - case HEARTBEAT -> { - LOGGER.info(INSTANCE + localId + " - Heartbeat Message handling..."); - handleHeartbeatMessage(message); - } - case ELECTION_INVOKE -> { - LOGGER.info(INSTANCE + localId + " - Election Invoke Message handling..."); - handleElectionInvokeMessage(); - } - case LEADER_INVOKE -> { - LOGGER.info(INSTANCE + localId + " - Leader Invoke Message handling..."); - handleLeaderInvokeMessage(); - } - case HEARTBEAT_INVOKE -> { - LOGGER.info(INSTANCE + localId + " - Heartbeat Invoke Message handling..."); - handleHeartbeatInvokeMessage(); - } - default -> { - } - } - } +> A real-world analogy to the Leader Election pattern is the election of a team captain in sports. All team members (nodes) participate in the election process, following a set of agreed-upon rules (protocol). Once a captain (leader) is chosen, they assume responsibility for coordinating strategies, giving directions, and representing the team in discussions. If the captain is injured or unavailable, the team holds a new election or appoints a vice-captain (failover mechanism) to ensure that leadership and direction are maintained consistently. - /** - * Abstract methods to handle different types of message. These methods need to be implemented in - * concrete instance class to implement corresponding leader-selection pattern. - */ - protected abstract void handleElectionMessage(Message message); - - protected abstract void handleElectionInvokeMessage(); - - protected abstract void handleLeaderMessage(Message message); - - protected abstract void handleLeaderInvokeMessage(); +In plain words - protected abstract void handleHeartbeatMessage(Message message); +> The leader election pattern is a design approach that enables a distributed system to select one node as the coordinator or leader to manage tasks and maintain order, while other nodes operate as followers. - protected abstract void handleHeartbeatInvokeMessage(); +Wikipedia says -} -``` +> In distributed computing, leader election is the process of designating a single process as the organizer of some task distributed among several computers (nodes). Before the task has begun, all network nodes are either unaware which node will serve as the "leader" (or coordinator) of the task, or unable to communicate with the current coordinator. After a leader election algorithm has been run, however, each node throughout the network recognizes a particular, unique node as the task leader. -```java -/** - * Message used to transport data between instances. - */ -@Setter -@Getter -@EqualsAndHashCode -@AllArgsConstructor -@NoArgsConstructor -public class Message { - - private MessageType type; - private String content; +## Programmatic Example of Leader Election Pattern in Java -} -``` +The Leader Election pattern is a design approach that enables a distributed system to select one node as the coordinator or leader to manage tasks and maintain order, while other nodes operate as followers. This pattern is particularly useful in distributed systems where one node needs to act as a central coordinator for a specific function or decision-making process. -```java -public interface MessageManager { - - /** - * Send heartbeat message to leader instance to check whether the leader instance is alive. - * - * @param leaderId Instance ID of leader instance. - * @return {@code true} if leader instance is alive, or {@code false} if not. - */ - boolean sendHeartbeatMessage(int leaderId); - - /** - * Send election message to other instances. - * - * @param currentId Instance ID of which sends this message. - * @param content Election message content. - * @return {@code true} if the message is accepted by the target instances. - */ - boolean sendElectionMessage(int currentId, String content); - - /** - * Send new leader notification message to other instances. - * - * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. - * @return {@code true} if the message is accepted by the target instances. - */ - boolean sendLeaderMessage(int currentId, int leaderId); - - /** - * Send heartbeat invoke message. This will invoke heartbeat task in the target instance. - * - * @param currentId Instance ID of which sends this message. - */ - void sendHeartbeatInvokeMessage(int currentId); +In the provided code, we have an `AbstractMessageManager` class and `AbstractInstance` class. The `AbstractMessageManager` class is responsible for managing messages between instances and finding the next instance (potential leader) based on certain conditions. The `AbstractInstance` class represents a node in the distributed system. -} -``` -These type of messages are used to pass among instances. -```java -/** - * Message Type enum. - */ -public enum MessageType { - - /** - * Start the election. The content of the message stores ID(s) of the candidate instance(s). - */ - ELECTION, - - /** - * Nodify the new leader. The content of the message should be the leader ID. - */ - LEADER, - - /** - * Check health of current leader instance. - */ - HEARTBEAT, - - /** - * Inform target instance to start election. - */ - ELECTION_INVOKE, - - /** - * Inform target instance to notify all the other instance that it is the new leader. - */ - LEADER_INVOKE, - - /** - * Inform target instance to start heartbeat. - */ - HEARTBEAT_INVOKE +Let's break down the code and explain how it works: -} -``` -Abstract class who implements MessageManager interface. It helps to manage messages among instance. ```java -/** - * Abstract class of all the message manager classes. - */ public abstract class AbstractMessageManager implements MessageManager { - /** - * Contain all the instances in the system. Key is its ID, and value is the instance itself. - */ protected Map instanceMap; - /** - * Constructor of AbstractMessageManager. - */ public AbstractMessageManager(Map instanceMap) { this.instanceMap = instanceMap; } - /** - * Find the next instance with smallest ID. - * - * @return The next instance. - */ protected Instance findNextInstance(int currentId) { - Instance result = null; - var candidateList = instanceMap.keySet() - .stream() - .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) - .sorted() - .toList(); - if (candidateList.isEmpty()) { - var index = instanceMap.keySet() - .stream() - .filter((i) -> instanceMap.get(i).isAlive()) - .sorted() - .toList() - .get(0); - result = instanceMap.get(index); - } else { - var index = candidateList.get(0); - result = instanceMap.get(index); - } - return result; + // Implementation details... } } ``` -Here the implementation of Ring Algorithm -```java -/** - * Implementation with token ring algorithm. The instances in the system are organized as a ring. - * Each instance should have a sequential id and the instance with smallest (or largest) id should - * be the initial leader. All the other instances send heartbeat message to leader periodically to - * check its health. If one certain instance finds the server done, it will send an election message - * to the next alive instance in the ring, which contains its own ID. Then the next instance add its - * ID into the message and pass it to the next. After all the alive instances' ID are add to the - * message, the message is send back to the first instance and it will choose the instance with - * smallest ID to be the new leader, and then send a leader message to other instances to inform the - * result. - */ -@Slf4j -public class RingInstance extends AbstractInstance { - private static final String INSTANCE = "Instance "; - - /** - * Constructor of RingInstance. - */ - public RingInstance(MessageManager messageManager, int localId, int leaderId) { - super(messageManager, localId, leaderId); - } +The `AbstractMessageManager` class manages the instances in the system. It contains a map of instances, where the key is the instance ID and the value is the instance itself. The `findNextInstance` method is used to find the next instance with the smallest ID that is alive. This method can be used in the leader election process to determine the next leader if the current leader fails. - /** - * Process the heartbeat invoke message. After receiving the message, the instance will send a - * heartbeat to leader to check its health. If alive, it will inform the next instance to do the - * heartbeat. If not, it will start the election process. - */ - @Override - protected void handleHeartbeatInvokeMessage() { - try { - var isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); - if (isLeaderAlive) { - LOGGER.info(INSTANCE + localId + "- Leader is alive. Start next heartbeat in 5 second."); - Thread.sleep(HEARTBEAT_INTERVAL); - messageManager.sendHeartbeatInvokeMessage(this.localId); - } else { - LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election."); - messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId)); - } - } catch (InterruptedException e) { - LOGGER.info(INSTANCE + localId + "- Interrupted."); - } - } +```java +public abstract class AbstractInstance implements Instance { - /** - * Process election message. If the local ID is contained in the ID list, the instance will select - * the alive instance with smallest ID to be the new leader, and send the leader inform message. - * If not, it will add its local ID to the list and send the message to the next instance in the - * ring. - */ - @Override - protected void handleElectionMessage(Message message) { - var content = message.getContent(); - LOGGER.info(INSTANCE + localId + " - Election Message: " + content); - var candidateList = Arrays.stream(content.trim().split(",")) - .map(Integer::valueOf) - .sorted() - .toList(); - if (candidateList.contains(localId)) { - var newLeaderId = candidateList.get(0); - LOGGER.info(INSTANCE + localId + " - New leader should be " + newLeaderId + "."); - messageManager.sendLeaderMessage(localId, newLeaderId); - } else { - content += "," + localId; - messageManager.sendElectionMessage(localId, content); - } - } + protected int id; + protected boolean alive; + protected MessageManager messageManager; - /** - * Process leader Message. The instance will set the leader ID to be the new one and send the - * message to the next instance until all the alive instance in the ring is informed. - */ - @Override - protected void handleLeaderMessage(Message message) { - var newLeaderId = Integer.valueOf(message.getContent()); - if (this.leaderId != newLeaderId) { - LOGGER.info(INSTANCE + localId + " - Update leaderID"); - this.leaderId = newLeaderId; - messageManager.sendLeaderMessage(localId, newLeaderId); - } else { - LOGGER.info(INSTANCE + localId + " - Leader update done. Start heartbeat."); - messageManager.sendHeartbeatInvokeMessage(localId); - } + public AbstractInstance(MessageManager messageManager, int id) { + this.messageManager = messageManager; + this.id = id; + this.alive = true; } - /** - * Not used in Ring instance. - */ - @Override - protected void handleLeaderInvokeMessage() { - // Not used in Ring instance. + public boolean isAlive() { + return alive; } - @Override - protected void handleHeartbeatMessage(Message message) { - // Not used in Ring instance. + public void setAlive(boolean alive) { + this.alive = alive; } - @Override - protected void handleElectionInvokeMessage() { - // Not used in Ring instance. + public void onMessage(Message message) { + // Implementation details... } } ``` -```java -/** - * Implementation of RingMessageManager. - */ -public class RingMessageManager extends AbstractMessageManager { - - /** - * Constructor of RingMessageManager. - */ - public RingMessageManager(Map instanceMap) { - super(instanceMap); - } - - /** - * Send heartbeat message to current leader instance to check the health. - * - * @param leaderId leaderID - * @return {@code true} if the leader is alive. - */ - @Override - public boolean sendHeartbeatMessage(int leaderId) { - var leaderInstance = instanceMap.get(leaderId); - var alive = leaderInstance.isAlive(); - return alive; - } - - /** - * Send election message to the next instance. - * - * @param currentId currentID - * @param content list contains all the IDs of instances which have received this election - * message. - * @return {@code true} if the election message is accepted by the target instance. - */ - @Override - public boolean sendElectionMessage(int currentId, String content) { - var nextInstance = this.findNextInstance(currentId); - var electionMessage = new Message(MessageType.ELECTION, content); - nextInstance.onMessage(electionMessage); - return true; - } - - /** - * Send leader message to the next instance. - * - * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. - * @return {@code true} if the leader message is accepted by the target instance. - */ - @Override - public boolean sendLeaderMessage(int currentId, int leaderId) { - var nextInstance = this.findNextInstance(currentId); - var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); - nextInstance.onMessage(leaderMessage); - return true; - } +The `AbstractInstance` class represents a node in the distributed system. Each instance can check if it's alive, set its health status, and consume messages from other instances. - /** - * Send heartbeat invoke message to the next instance. - * - * @param currentId Instance ID of which sends this message. - */ - @Override - public void sendHeartbeatInvokeMessage(int currentId) { - var nextInstance = this.findNextInstance(currentId); - var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); - nextInstance.onMessage(heartbeatInvokeMessage); - } +Now, let's look at the `BullyApp` and `RingApp` classes, which implement two different leader election algorithms: -} -``` ```java -public static void main(String[] args) { +public class BullyApp { + + public static void main(String[] args) { Map instanceMap = new HashMap<>(); - var messageManager = new RingMessageManager(instanceMap); + var messageManager = new BullyMessageManager(instanceMap); - var instance1 = new RingInstance(messageManager, 1, 1); - var instance2 = new RingInstance(messageManager, 2, 1); - var instance3 = new RingInstance(messageManager, 3, 1); - var instance4 = new RingInstance(messageManager, 4, 1); - var instance5 = new RingInstance(messageManager, 5, 1); + var instance1 = new BullyInstance(messageManager, 1, 1); + // ... more instances ... instanceMap.put(1, instance1); - instanceMap.put(2, instance2); - instanceMap.put(3, instance3); - instanceMap.put(4, instance4); - instanceMap.put(5, instance5); - - instance2.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); - - final var thread1 = new Thread(instance1); - final var thread2 = new Thread(instance2); - final var thread3 = new Thread(instance3); - final var thread4 = new Thread(instance4); - final var thread5 = new Thread(instance5); - - thread1.start(); - thread2.start(); - thread3.start(); - thread4.start(); - thread5.start(); + // ... more instances ... instance1.setAlive(false); } +} ``` -The console output -``` -[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Heartbeat Invoke Message handling... -[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2- Leader is not alive. Start election. -[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Message handling... -[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Election Message: 2 -[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Election Message handling... -[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4 - Election Message: 2,3 -[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Election Message handling... -[Thread-4] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 5 - Election Message: 2,3,4 -[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Message handling... -[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - Election Message: 2,3,4,5 -[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - New leader should be 2. -[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling... -[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Update leaderID -[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Leader Message handling... -[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4 - Update leaderID -[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Leader Message handling... -[Thread-4] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 5 - Update leaderID -[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Leader Message handling... -[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - Update leaderID -[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling... -[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Leader update done. Start heartbeat. -[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Heartbeat Invoke Message handling... -[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4- Leader is alive. Start next heartbeat in 5 second. -``` -Here the implementation of Bully algorithm -```java -/** - * Impelemetation with bully algorithm. Each instance should have a sequential id and is able to - * communicate with other instances in the system. Initially the instance with smallest (or largest) - * ID is selected to be the leader. All the other instances send heartbeat message to leader - * periodically to check its health. If one certain instance finds the server done, it will send an - * election message to all the instances of which the ID is larger. If the target instance is alive, - * it will return an alive message (in this sample return true) and then send election message with - * its ID. If not, the original instance will send leader message to all the other instances. - */ -@Slf4j -public class BullyInstance extends AbstractInstance { - private static final String INSTANCE = "Instance "; - - /** - * Constructor of BullyInstance. - */ - public BullyInstance(MessageManager messageManager, int localId, int leaderId) { - super(messageManager, localId, leaderId); - } +The `BullyApp` class implements the Bully algorithm for leader election. In this algorithm, when a node notices the leader is down, it starts an election by sending an election message to all nodes with higher IDs. If it doesn't receive a response, it declares itself the leader and sends a victory message to all nodes with lower IDs. - /** - * Process the heartbeat invoke message. After receiving the message, the instance will send a - * heartbeat to leader to check its health. If alive, it will inform the next instance to do the - * heartbeat. If not, it will start the election process. - */ - @Override - protected void handleHeartbeatInvokeMessage() { - try { - boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); - if (isLeaderAlive) { - LOGGER.info(INSTANCE + localId + "- Leader is alive."); - Thread.sleep(HEARTBEAT_INTERVAL); - messageManager.sendHeartbeatInvokeMessage(localId); - } else { - LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election."); - boolean electionResult = - messageManager.sendElectionMessage(localId, String.valueOf(localId)); - if (electionResult) { - LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification."); - messageManager.sendLeaderMessage(localId, localId); - } - } - } catch (InterruptedException e) { - LOGGER.info(INSTANCE + localId + "- Interrupted."); - } - } +```java +public class RingApp { - /** - * Process election invoke message. Send election message to all the instances with smaller ID. If - * any one of them is alive, do nothing. If no instance alive, send leader message to all the - * alive instance and restart heartbeat. - */ - @Override - protected void handleElectionInvokeMessage() { - if (!isLeader()) { - LOGGER.info(INSTANCE + localId + "- Start election."); - boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); - if (electionResult) { - LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification."); - leaderId = localId; - messageManager.sendLeaderMessage(localId, localId); - messageManager.sendHeartbeatInvokeMessage(localId); - } - } - } + public static void main(String[] args) { - /** - * Process leader message. Update local leader information. - */ - @Override - protected void handleLeaderMessage(Message message) { - leaderId = Integer.valueOf(message.getContent()); - LOGGER.info(INSTANCE + localId + " - Leader update done."); - } - - private boolean isLeader() { - return localId == leaderId; - } + Map instanceMap = new HashMap<>(); + var messageManager = new RingMessageManager(instanceMap); - @Override - protected void handleLeaderInvokeMessage() { - // Not used in Bully Instance - } + var instance1 = new RingInstance(messageManager, 1, 1); + // ... more instances ... - @Override - protected void handleHeartbeatMessage(Message message) { - // Not used in Bully Instance - } + instanceMap.put(1, instance1); + // ... more instances ... - @Override - protected void handleElectionMessage(Message message) { - // Not used in Bully Instance + instance1.setAlive(false); } } ``` -```java -/** - * Implementation of BullyMessageManager. - */ -public class BullyMessageManager extends AbstractMessageManager { - - /** - * Constructor of BullyMessageManager. - */ - public BullyMessageManager(Map instanceMap) { - super(instanceMap); - } +The `RingApp` class implements the Ring algorithm for leader election. In this algorithm, each node sends an election message to its neighbor in a logical ring topology. When a node receives an election message, it passes it on if the ID in the message is higher than its own. The process continues until the message has made a full circle, at which point the node with the highest ID becomes the leader. - /** - * Send heartbeat message to current leader instance to check the health. - * - * @param leaderId leaderID - * @return {@code true} if the leader is alive. - */ - @Override - public boolean sendHeartbeatMessage(int leaderId) { - var leaderInstance = instanceMap.get(leaderId); - var alive = leaderInstance.isAlive(); - return alive; - } +These examples demonstrate how the Leader Election pattern can be implemented in different ways to suit the specific requirements of a distributed system. - /** - * Send election message to all the instances with smaller ID. - * - * @param currentId Instance ID of which sends this message. - * @param content Election message content. - * @return {@code true} if no alive instance has smaller ID, so that the election is accepted. - */ - @Override - public boolean sendElectionMessage(int currentId, String content) { - var candidateList = findElectionCandidateInstanceList(currentId); - if (candidateList.isEmpty()) { - return true; - } else { - var electionMessage = new Message(MessageType.ELECTION_INVOKE, ""); - candidateList.stream().forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); - return false; - } - } +## Detailed Explanation of Leader Election Pattern with Real-World Examples - /** - * Send leader message to all the instances to notify the new leader. - * - * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. - * @return {@code true} if the message is accepted. - */ - @Override - public boolean sendLeaderMessage(int currentId, int leaderId) { - var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); - instanceMap.keySet() - .stream() - .filter((i) -> i != currentId) - .forEach((i) -> instanceMap.get(i).onMessage(leaderMessage)); - return false; - } +![Leader Election](./etc/leader-election.urm.png "Leader Election pattern class diagram") - /** - * Send heartbeat invoke message to the next instance. - * - * @param currentId Instance ID of which sends this message. - */ - @Override - public void sendHeartbeatInvokeMessage(int currentId) { - var nextInstance = this.findNextInstance(currentId); - var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); - nextInstance.onMessage(heartbeatInvokeMessage); - } +## When to Use the Leader Election Pattern in Java - /** - * Find all the alive instances with smaller ID than current instance. - * - * @param currentId ID of current instance. - * @return ID list of all the candidate instance. - */ - private List findElectionCandidateInstanceList(int currentId) { - return instanceMap.keySet() - .stream() - .filter((i) -> i < currentId && instanceMap.get(i).isAlive()) - .toList(); - } +Use the Leader Election pattern in Java applications where: -} -``` +* A distributed system needs one node to act as the central coordinator for a specific function or decision-making process. +* High availability is essential, and the leader should be replaceable in case of failure. +* Coordination is required across different nodes in a cluster, particularly in cloud environments. -```java -public static void main(String[] args) { - - Map instanceMap = new HashMap<>(); - var messageManager = new BullyMessageManager(instanceMap); - - var instance1 = new BullyInstance(messageManager, 1, 1); - var instance2 = new BullyInstance(messageManager, 2, 1); - var instance3 = new BullyInstance(messageManager, 3, 1); - var instance4 = new BullyInstance(messageManager, 4, 1); - var instance5 = new BullyInstance(messageManager, 5, 1); - - instanceMap.put(1, instance1); - instanceMap.put(2, instance2); - instanceMap.put(3, instance3); - instanceMap.put(4, instance4); - instanceMap.put(5, instance5); +## Real-World Applications of Leader Election Pattern in Java - instance4.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); +* Apache ZooKeeper: Provides leader election for distributed services. +* Kubernetes: Elects a leader pod to manage stateful workloads. +* Hazelcast: Distributed data grid uses leader election for cluster management. - final var thread1 = new Thread(instance1); - final var thread2 = new Thread(instance2); - final var thread3 = new Thread(instance3); - final var thread4 = new Thread(instance4); - final var thread5 = new Thread(instance5); +## Benefits and Trade-offs of Leader Election Pattern - thread1.start(); - thread2.start(); - thread3.start(); - thread4.start(); - thread5.start(); - - instance1.setAlive(false); - } -``` - -The console output -``` -[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Heartbeat Invoke Message handling... -[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4- Leader is alive. -[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Heartbeat Invoke Message handling... -[Thread-4] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 5- Leader is not alive. Start election. -[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Election Invoke Message handling... -[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Invoke Message handling... -[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4- Start election. -[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Start election. -[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Invoke Message handling... -[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Start election. -[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... -[Thread-1] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 2- Start election. -[Thread-1] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 2- Succeed in election. Start leader notification. -[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... -[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Leader Message handling... -[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling... -[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... -[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Leader Message handling... -[Thread-0] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 1 - Leader Message handling... -[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... -[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4 - Leader update done. -[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3 - Leader update done. -[Thread-0] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 1 - Leader update done. -[Thread-4] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 5 - Leader update done. -[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Heartbeat Invoke Message handling... -[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Leader is alive. -``` +Benefits: -## Class diagram -![alt text](./etc/leader-election.urm.png "Leader Election pattern class diagram") +* Consistency: Ensures a single, consistent leader handles coordination tasks. +* Fault Tolerance: Allows for leader replacement if the current leader fails. +* Scalability: Works effectively in large, distributed systems where multiple nodes are present. -## Applicability -Use this pattern when +Trade-offs: -* the tasks in a distributed application, such as a cloud-hosted solution, require careful coordination and there is no natural leader. +* Complexity: Requires careful implementation to handle network partitions and latency. +* Overhead: Election processes may introduce performance overhead. +* Single Point of Failure: Even with redundancy, the leader can become a bottleneck if not carefully designed. -Do not use this pattern when +## Related Java Design Patterns -* there is a natural leader or dedicated process that can always act as the leader. For example, it may be possible to implement a singleton process that coordinates the task instances. If this process fails or becomes unhealthy, the system can shut it down and restart it. -* the coordination between tasks can be easily achieved by using a more lightweight mechanism. For example, if several task instances simply require coordinated access to a shared resource, a preferable solution might be to use optimistic or pessimistic locking to control access to that resource. +* [Observer](https://java-design-patterns.com/patterns/observer/): Followers can observe changes from the leader to stay updated. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): The leader functions as a single instance, acting as a unique decision-maker. +* [State](https://java-design-patterns.com/patterns/state/): Helps in managing state transitions, particularly in switching leadership roles. -## Credits +## References and Credits -* [Leader Election pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/leader-election) -* [Raft Leader Election](https://github.com/ronenhamias/raft-leader-election) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3y6yv1z) +* [Distributed Systems: Principles and Paradigms](https://amzn.to/3UN2vbH) +* [Site Reliability Engineering: How Google Runs Production Systems](https://amzn.to/4brjBRI) +* [Leader Election pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/leader-election) diff --git a/leader-election/pom.xml b/leader-election/pom.xml index 8cef075485cc..fbe1edbea0c6 100644 --- a/leader-election/pom.xml +++ b/leader-election/pom.xml @@ -34,6 +34,14 @@ leader-election + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java index 531c9869e3a9..398d0baf306c 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -28,9 +28,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import lombok.extern.slf4j.Slf4j; -/** - * Abstract class of all the instance implementation classes. - */ +/** Abstract class of all the instance implementation classes. */ @Slf4j public abstract class AbstractInstance implements Instance, Runnable { @@ -43,9 +41,7 @@ public abstract class AbstractInstance implements Instance, Runnable { protected int leaderId; protected boolean alive; - /** - * Constructor of BullyInstance. - */ + /** Constructor of BullyInstance. */ public AbstractInstance(MessageManager messageManager, int localId, int leaderId) { this.messageManager = messageManager; this.messageQueue = new ConcurrentLinkedQueue<>(); @@ -54,9 +50,7 @@ public AbstractInstance(MessageManager messageManager, int localId, int leaderId this.alive = true; } - /** - * The instance will execute the message in its message queue periodically once it is alive. - */ + /** The instance will execute the message in its message queue periodically once it is alive. */ @Override @SuppressWarnings("squid:S2189") public void run() { @@ -129,8 +123,7 @@ private void processMessage(Message message) { LOGGER.info(INSTANCE + localId + " - Heartbeat Invoke Message handling..."); handleHeartbeatInvokeMessage(); } - default -> { - } + default -> {} } } @@ -149,5 +142,4 @@ private void processMessage(Message message) { protected abstract void handleHeartbeatMessage(Message message); protected abstract void handleHeartbeatInvokeMessage(); - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java index 82f89f89c8fa..d613182e5aa9 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java @@ -26,42 +26,36 @@ import java.util.Map; -/** - * Abstract class of all the message manager classes. - */ +/** Abstract class of all the message manager classes. */ public abstract class AbstractMessageManager implements MessageManager { - /** - * Contain all the instances in the system. Key is its ID, and value is the instance itself. - */ + /** Contain all the instances in the system. Key is its ID, and value is the instance itself. */ protected Map instanceMap; - /** - * Constructor of AbstractMessageManager. - */ + /** Constructor of AbstractMessageManager. */ public AbstractMessageManager(Map instanceMap) { this.instanceMap = instanceMap; } /** - * Find the next instance with smallest ID. + * Find the next instance with the smallest ID. * * @return The next instance. */ protected Instance findNextInstance(int currentId) { Instance result = null; - var candidateList = instanceMap.keySet() - .stream() - .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) - .sorted() - .toList(); + var candidateList = + instanceMap.keySet().stream() + .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) + .sorted() + .toList(); if (candidateList.isEmpty()) { - var index = instanceMap.keySet() - .stream() - .filter((i) -> instanceMap.get(i).isAlive()) - .sorted() - .toList() - .get(0); + var index = + instanceMap.keySet().stream() + .filter((i) -> instanceMap.get(i).isAlive()) + .sorted() + .toList() + .get(0); result = instanceMap.get(index); } else { var index = candidateList.get(0); @@ -69,5 +63,4 @@ protected Instance findNextInstance(int currentId) { } return result; } - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java index 08f76dfea599..a2a16f0f62f9 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -24,9 +24,7 @@ */ package com.iluwatar.leaderelection; -/** - * Instance interface. - */ +/** Instance interface. */ public interface Instance { /** @@ -49,5 +47,4 @@ public interface Instance { * @param message Message sent by other instances */ void onMessage(Message message); - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java index 6984481a68fe..af3788e954fe 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; -/** - * Message used to transport data between instances. - */ +/** Message used to transport data between instances. */ @Setter @Getter @EqualsAndHashCode @@ -42,5 +40,4 @@ public class Message { private MessageType type; private String content; - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java index 58006dc00a24..96a030421c65 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -24,9 +24,7 @@ */ package com.iluwatar.leaderelection; -/** - * MessageManager interface. - */ +/** MessageManager interface. */ public interface MessageManager { /** @@ -41,7 +39,7 @@ public interface MessageManager { * Send election message to other instances. * * @param currentId Instance ID of which sends this message. - * @param content Election message content. + * @param content Election message content. * @return {@code true} if the message is accepted by the target instances. */ boolean sendElectionMessage(int currentId, String content); @@ -50,7 +48,7 @@ public interface MessageManager { * Send new leader notification message to other instances. * * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. + * @param leaderId Leader message content. * @return {@code true} if the message is accepted by the target instances. */ boolean sendLeaderMessage(int currentId, int leaderId); @@ -61,5 +59,4 @@ public interface MessageManager { * @param currentId Instance ID of which sends this message. */ void sendHeartbeatInvokeMessage(int currentId); - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java index 9f3d3ea86743..d2d06cc21e4f 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -24,40 +24,24 @@ */ package com.iluwatar.leaderelection; -/** - * Message Type enum. - */ +/** Message Type enum. */ public enum MessageType { - /** - * Start the election. The content of the message stores ID(s) of the candidate instance(s). - */ + /** Start the election. The content of the message stores ID(s) of the candidate instance(s). */ ELECTION, - /** - * Nodify the new leader. The content of the message should be the leader ID. - */ + /** Nodify the new leader. The content of the message should be the leader ID. */ LEADER, - /** - * Check health of current leader instance. - */ + /** Check health of current leader instance. */ HEARTBEAT, - /** - * Inform target instance to start election. - */ + /** Inform target instance to start election. */ ELECTION_INVOKE, - /** - * Inform target instance to notify all the other instance that it is the new leader. - */ + /** Inform target instance to notify all the other instance that it is the new leader. */ LEADER_INVOKE, - /** - * Inform target instance to start heartbeat. - */ + /** Inform target instance to start heartbeat. */ HEARTBEAT_INVOKE - } - diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java index 5d88fd60c8e9..700d8ac6e13c 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java @@ -26,7 +26,6 @@ import com.iluwatar.leaderelection.Instance; import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageManager; import com.iluwatar.leaderelection.MessageType; import java.util.HashMap; import java.util.Map; @@ -38,9 +37,7 @@ */ public class BullyApp { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { Map instanceMap = new HashMap<>(); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java index b8ed17a9067b..3ee9629d469d 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -42,9 +42,7 @@ public class BullyInstance extends AbstractInstance { private static final String INSTANCE = "Instance "; - /** - * Constructor of BullyInstance. - */ + /** Constructor of BullyInstance. */ public BullyInstance(MessageManager messageManager, int localId, int leaderId) { super(messageManager, localId, leaderId); } @@ -95,12 +93,10 @@ protected void handleElectionInvokeMessage() { } } - /** - * Process leader message. Update local leader information. - */ + /** Process leader message. Update local leader information. */ @Override protected void handleLeaderMessage(Message message) { - leaderId = Integer.valueOf(message.getContent()); + leaderId = Integer.parseInt(message.getContent()); LOGGER.info(INSTANCE + localId + " - Leader update done."); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java index 6f166dd358a4..e3bd158ed7db 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java @@ -31,14 +31,10 @@ import java.util.List; import java.util.Map; -/** - * Implementation of BullyMessageManager. - */ +/** Implementation of BullyMessageManager. */ public class BullyMessageManager extends AbstractMessageManager { - /** - * Constructor of BullyMessageManager. - */ + /** Constructor of BullyMessageManager. */ public BullyMessageManager(Map instanceMap) { super(instanceMap); } @@ -52,15 +48,14 @@ public BullyMessageManager(Map instanceMap) { @Override public boolean sendHeartbeatMessage(int leaderId) { var leaderInstance = instanceMap.get(leaderId); - var alive = leaderInstance.isAlive(); - return alive; + return leaderInstance.isAlive(); } /** * Send election message to all the instances with smaller ID. * * @param currentId Instance ID of which sends this message. - * @param content Election message content. + * @param content Election message content. * @return {@code true} if no alive instance has smaller ID, so that the election is accepted. */ @Override @@ -70,7 +65,7 @@ public boolean sendElectionMessage(int currentId, String content) { return true; } else { var electionMessage = new Message(MessageType.ELECTION_INVOKE, ""); - candidateList.stream().forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); + candidateList.forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); return false; } } @@ -79,14 +74,13 @@ public boolean sendElectionMessage(int currentId, String content) { * Send leader message to all the instances to notify the new leader. * * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. + * @param leaderId Leader message content. * @return {@code true} if the message is accepted. */ @Override public boolean sendLeaderMessage(int currentId, int leaderId) { var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); - instanceMap.keySet() - .stream() + instanceMap.keySet().stream() .filter((i) -> i != currentId) .forEach((i) -> instanceMap.get(i).onMessage(leaderMessage)); return false; @@ -111,10 +105,8 @@ public void sendHeartbeatInvokeMessage(int currentId) { * @return ID list of all the candidate instance. */ private List findElectionCandidateInstanceList(int currentId) { - return instanceMap.keySet() - .stream() + return instanceMap.keySet().stream() .filter((i) -> i < currentId && instanceMap.get(i).isAlive()) .toList(); } - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java index 811e9396e9a0..f72e37b01154 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java @@ -26,7 +26,6 @@ import com.iluwatar.leaderelection.Instance; import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageManager; import com.iluwatar.leaderelection.MessageType; import java.util.HashMap; import java.util.Map; @@ -38,9 +37,7 @@ */ public class RingApp { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { Map instanceMap = new HashMap<>(); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index d971217a3810..d4a8f0d38721 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -37,7 +37,7 @@ * check its health. If one certain instance finds the server done, it will send an election message * to the next alive instance in the ring, which contains its own ID. Then the next instance add its * ID into the message and pass it to the next. After all the alive instances' ID are add to the - * message, the message is send back to the first instance and it will choose the instance with + * message, the message is send back to the first instance, and it will choose the instance with the * smallest ID to be the new leader, and then send a leader message to other instances to inform the * result. */ @@ -45,9 +45,7 @@ public class RingInstance extends AbstractInstance { private static final String INSTANCE = "Instance "; - /** - * Constructor of RingInstance. - */ + /** Constructor of RingInstance. */ public RingInstance(MessageManager messageManager, int localId, int leaderId) { super(messageManager, localId, leaderId); } @@ -76,18 +74,16 @@ protected void handleHeartbeatInvokeMessage() { /** * Process election message. If the local ID is contained in the ID list, the instance will select - * the alive instance with smallest ID to be the new leader, and send the leader inform message. - * If not, it will add its local ID to the list and send the message to the next instance in the - * ring. + * the alive instance with the smallest ID to be the new leader, and send the leader inform + * message. If not, it will add its local ID to the list and send the message to the next instance + * in the ring. */ @Override protected void handleElectionMessage(Message message) { var content = message.getContent(); LOGGER.info(INSTANCE + localId + " - Election Message: " + content); - var candidateList = Arrays.stream(content.trim().split(",")) - .map(Integer::valueOf) - .sorted() - .toList(); + var candidateList = + Arrays.stream(content.trim().split(",")).map(Integer::valueOf).sorted().toList(); if (candidateList.contains(localId)) { var newLeaderId = candidateList.get(0); LOGGER.info(INSTANCE + localId + " - New leader should be " + newLeaderId + "."); @@ -115,9 +111,7 @@ protected void handleLeaderMessage(Message message) { } } - /** - * Not used in Ring instance. - */ + /** Not used in Ring instance. */ @Override protected void handleLeaderInvokeMessage() { // Not used in Ring instance. @@ -132,5 +126,4 @@ protected void handleHeartbeatMessage(Message message) { protected void handleElectionInvokeMessage() { // Not used in Ring instance. } - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index 30f966fdb547..7a055cf10331 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -30,14 +30,10 @@ import com.iluwatar.leaderelection.MessageType; import java.util.Map; -/** - * Implementation of RingMessageManager. - */ +/** Implementation of RingMessageManager. */ public class RingMessageManager extends AbstractMessageManager { - /** - * Constructor of RingMessageManager. - */ + /** Constructor of RingMessageManager. */ public RingMessageManager(Map instanceMap) { super(instanceMap); } @@ -51,16 +47,15 @@ public RingMessageManager(Map instanceMap) { @Override public boolean sendHeartbeatMessage(int leaderId) { var leaderInstance = instanceMap.get(leaderId); - var alive = leaderInstance.isAlive(); - return alive; + return leaderInstance.isAlive(); } /** * Send election message to the next instance. * * @param currentId currentID - * @param content list contains all the IDs of instances which have received this election - * message. + * @param content list contains all the IDs of instances which have received this election + * message. * @return {@code true} if the election message is accepted by the target instance. */ @Override @@ -75,7 +70,7 @@ public boolean sendElectionMessage(int currentId, String content) { * Send leader message to the next instance. * * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. + * @param leaderId Leader message content. * @return {@code true} if the leader message is accepted by the target instance. */ @Override @@ -97,5 +92,4 @@ public void sendHeartbeatInvokeMessage(int currentId) { var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); nextInstance.onMessage(heartbeatInvokeMessage); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java index d54dcabd77b8..f8a4d1869fbf 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Message test case. - */ +/** Message test case. */ class MessageTest { @Test @@ -45,5 +43,4 @@ void testGetContent() { var message = new Message(MessageType.HEARTBEAT, content); assertEquals(content, message.getContent()); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java index b4da68fee8ba..32ba40c38f17 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.leaderelection.bully; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * BullyApp unit test. - */ +import org.junit.jupiter.api.Test; + +/** BullyApp unit test. */ class BullyAppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> BullyApp.main(new String[]{})); + assertDoesNotThrow(() -> BullyApp.main(new String[] {})); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java index 11e4e85028c9..f1453202a39f 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java @@ -25,6 +25,7 @@ package com.iluwatar.leaderelection.bully; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -36,9 +37,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * BullyMessageManager unit test. - */ +/** BullyMessageManager unit test. */ class BullyMessageManagerTest { @Test @@ -56,7 +55,8 @@ void testSendElectionMessageNotAccepted() { var instance2 = new BullyInstance(null, 1, 2); var instance3 = new BullyInstance(null, 1, 3); var instance4 = new BullyInstance(null, 1, 4); - Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + Map instanceMap = + Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); instance1.setAlive(false); var messageManager = new BullyMessageManager(instanceMap); var result = messageManager.sendElectionMessage(3, "3"); @@ -68,7 +68,7 @@ void testSendElectionMessageNotAccepted() { var expectedMessage = new Message(MessageType.ELECTION_INVOKE, ""); assertEquals(message2, expectedMessage); assertEquals(instance4QueueSize, 0); - assertEquals(result, false); + assertFalse(result); } catch (IllegalAccessException | NoSuchFieldException e) { fail("Error to access private field."); } @@ -80,11 +80,12 @@ void testElectionMessageAccepted() { var instance2 = new BullyInstance(null, 1, 2); var instance3 = new BullyInstance(null, 1, 3); var instance4 = new BullyInstance(null, 1, 4); - Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + Map instanceMap = + Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); instance1.setAlive(false); var messageManager = new BullyMessageManager(instanceMap); var result = messageManager.sendElectionMessage(2, "2"); - assertEquals(result, true); + assertTrue(result); } @Test @@ -94,7 +95,8 @@ void testSendLeaderMessage() { var instance2 = new BullyInstance(null, 1, 2); var instance3 = new BullyInstance(null, 1, 3); var instance4 = new BullyInstance(null, 1, 4); - Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + Map instanceMap = + Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); instance1.setAlive(false); var messageManager = new BullyMessageManager(instanceMap); messageManager.sendLeaderMessage(2, 2); @@ -131,6 +133,4 @@ void testSendHeartbeatInvokeMessage() { fail("Error to access private field."); } } - - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java index e036b05a5a67..31dbad14b8ab 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java @@ -34,9 +34,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * BullyInstance unit test. - */ +/** BullyInstance unit test. */ class BullyinstanceTest { @Test @@ -52,7 +50,6 @@ void testOnMessage() { } catch (IllegalAccessException | NoSuchFieldException e) { fail("fail to access messasge queue."); } - } @Test @@ -75,5 +72,4 @@ void testSetAlive() { bullyInstance.setAlive(false); assertFalse(bullyInstance.isAlive()); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java index 207b6ff240cd..839e481f4c81 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.leaderelection.ring; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * RingApp unit test. - */ +import org.junit.jupiter.api.Test; + +/** RingApp unit test. */ class RingAppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> RingApp.main(new String[]{})); + assertDoesNotThrow(() -> RingApp.main(new String[] {})); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java index 2c0634d71350..140432e54e1a 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -34,9 +34,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * RingInstance unit test. - */ +/** RingInstance unit test. */ class RingInstanceTest { @Test diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java index 4dc8426581b4..ed13e4fb45f4 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -36,9 +36,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * RingMessageManager unit test. - */ +/** RingMessageManager unit test. */ class RingMessageManagerTest { @Test @@ -112,5 +110,4 @@ void testSendHeartbeatInvokeMessage() { fail("Error to access private field."); } } - } diff --git a/leader-followers/README.md b/leader-followers/README.md index 5198097883c1..0479ae232df5 100644 --- a/leader-followers/README.md +++ b/leader-followers/README.md @@ -1,105 +1,171 @@ --- -title: Leader/Followers +title: "Leader-Followers Pattern in Java: Enhancing Efficiency with Dynamic Worker Allocation" +shortTitle: Leader-Followers +description: "Discover the Leader-Followers design pattern for efficient thread management and synchronization. Learn how to optimize resource usage and improve system performance with detailed examples and applications." category: Concurrency language: en tag: -- Performance + - Decoupling + - Performance + - Synchronization + - Thread management --- -## Intent -The Leader/Followers design pattern is a pattern used to coordinate a selection of 'workers'. It allows tasks to execute concurrently -with the Leader delegating tasks to the Follower threads for execution. It is a very common design pattern used in multithreaded -situations such as servers, and works to help prevent ambiguity around delegation of tasks. +## Intent of Leader/Followers Design Pattern + +To efficiently manage a set of worker threads, the Leader-Followers pattern enables multiple threads to take turns sharing a set of event sources, optimizing resource utilization and improving performance compared to a one-thread-per-source approach. + +## Detailed Explanation of Leader/Followers Pattern with Real-World Examples -## Explanation Real-world Example -> The best real world example of Leader/Followers is a web server. Web servers have to be able to handle a multitude of incoming -> connections all at once. In a web server, the Leader/Followers pattern works by using the Leader to listen to incoming requests -> and accept connections. Once a connection is made to a client the Leader can then find a Follower thread to delegate the task -> to for execution and return to the client. This means that the Leader does not have to wait to finish execution before it can -> accept another incoming connection, and can focus on delegating tasks. This pattern is created to aid in concurrency of applicaitons, -> allowing for many connections to work simultaneously. + +> Imagine managing a busy restaurant with multiple servers and a single host. The host acts as the "leader" and is responsible for greeting guests, managing the waitlist, and seating guests. Once the guests are seated, the host returns to the entrance to manage new arrivals. The servers, or "followers," wait for the host to assign them tables. This assignment is done on a rotational basis where the next available server takes the next group of guests. This system ensures that the host efficiently handles the incoming flow of guests while servers focus on providing service, similar to how the Leader and Followers pattern manages threads and tasks in a software system. This approach optimizes resource utilization (in this case, staff) and ensures smooth operations during peak times, much like it optimizes thread usage in computing environments. In plain words -> You can picture the Leader as a traffic controller that has to direct traffic from one lane into 25 lanes. As a car comes in, -> the Leader sends it down a road that isn't full or busy. This car can then have its request filled, or reach its destination. -> If the Leader had to drive each car down the lane itself, the line would pile up and progress would be slow. But as the Leader -> has Followers that can also drive the cars, the Leader can focus on making the line move quickly and ensuring traffic doesn't -> back up. - -Wikipedia says -> A concurrency pattern are those types of design patterns that deal with the multi-threaded programming paradigm. - -## Programmatic Example -This example shows Java code that sets up a Leader that listens for client requests on port 8080. Once a request is sent, -the leader will accept it and delegate it to a new Follower to execute. This means that the Leader can keep delegating, -and ensure requests are fulfilled timely. This is only pseudocode and the working code would require a more concrete implementation -of Leader and Followers. -```java -public class LeaderFollowerWebServer { - public static void main(String[] args) throws IOException { - int port = 8080; // the port that clients can reach the leader on - int numFollowers = 5; // the amount of followers we can delegate tasks to +> The Leader-Followers design pattern utilizes a single leader thread to distribute work among multiple follower threads, effectively managing task delegation, thread synchronization, and improving resource efficiency in concurrent programming. - ServerSocket serverSocket = new ServerSocket(port); // pseudocode for creating a socket to the server - ExecutorService executorService = Executors.newFixedThreadPool(numFollowers); // pseudocode to start execution for Followers +[martinfowler.com](https://martinfowler.com/articles/patterns-of-distributed-systems/leader-follower.html) says - System.out.println("Web server started. Listening on port " + port); +> Select one server in the cluster as a leader. The leader is responsible for taking decisions on behalf of the entire cluster and propagating the decisions to all the other servers. - while (true) { - Socket clientSocket = serverSocket.accept(); - // Accept a new connection and assign it to a follower thread for processing - executorService.execute(new Follower(clientSocket)); - } +## Programmatic Example of Leader-Followers Pattern in Java + +The Leader-Followers pattern is a concurrency design pattern where one thread (the leader) waits for work to arrive, de-multiplexes, dispatches, and processes the work, thereby enhancing CPU cache affinity and reducing event dispatching latency. Once the leader finishes processing the work, it promotes one of the follower threads to be the new leader. This pattern is useful for enhancing CPU cache affinity, minimizing locking overhead, and reducing event dispatching latency. + +In the provided code, we have a `WorkCenter` class that manages a group of `Worker` threads. One of these workers is designated as the leader and is responsible for receiving and processing tasks. Once a task is processed, the leader promotes a new leader from the remaining workers. + +```java +// WorkCenter class +public class WorkCenter { + + @Getter + private Worker leader; + private final List workers = new CopyOnWriteArrayList<>(); + + // Method to create workers and set the initial leader + public void createWorkers(int numberOfWorkers, TaskSet taskSet, TaskHandler taskHandler) { + for (var id = 1; id <= numberOfWorkers; id++) { + var worker = new Worker(id, this, taskSet, taskHandler); + workers.add(worker); + } + promoteLeader(); + } + + // Method to promote a new leader + public void promoteLeader() { + Worker leader = null; + if (!workers.isEmpty()) { + leader = workers.get(0); } + this.leader = leader; + } } +``` -class Follower implements Runnable { - private final Socket clientSocket; +In the `Worker` class, each worker is a thread that waits for tasks to process. If the worker is the leader, it processes the task and then promotes a new leader. - public Follower(Socket clientSocket) { - this.clientSocket = clientSocket; +```java +// Worker class +public class Worker implements Runnable { + + private final long id; + private final WorkCenter workCenter; + private final TaskSet taskSet; + private final TaskHandler taskHandler; + + @Override + public void run() { + while (!Thread.interrupted()) { + try { + if (workCenter.getLeader() != null && !workCenter.getLeader().equals(this)) { + synchronized (workCenter) { + if (workCenter.getLeader() != null && !workCenter.getLeader().equals(this)) { + workCenter.wait(); + continue; + } + } + } + final Task task = taskSet.getTask(); + synchronized (workCenter) { + workCenter.removeWorker(this); + workCenter.promoteLeader(); + workCenter.notifyAll(); + } + taskHandler.handleTask(task); + workCenter.addWorker(this); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } } + } +} +``` - @Override - public void run() { - try { - // handle the client request, e.g., read and write data - // this is where you would implement your request processing logic. - // we will just close the socket - clientSocket.close(); - } catch (IOException e) { - e.printStackTrace(); - } +In the `App` class, we create a `WorkCenter`, add tasks to a `TaskSet`, and then start the workers. The leader worker will start processing the tasks, and once it finishes a task, it will promote a new leader. + +```java +// App class +public class App { + + public static void main(String[] args) throws InterruptedException { + var taskSet = new TaskSet(); + var taskHandler = new TaskHandler(); + var workCenter = new WorkCenter(); + workCenter.createWorkers(4, taskSet, taskHandler); + addTasks(taskSet); + startWorkers(workCenter); + } + + private static void addTasks(TaskSet taskSet) throws InterruptedException { + var rand = new SecureRandom(); + for (var i = 0; i < 5; i++) { + var time = Math.abs(rand.nextInt(1000)); + taskSet.addTask(new Task(time)); } + } + + private static void startWorkers(WorkCenter workCenter) throws InterruptedException { + var workers = workCenter.getWorkers(); + var exec = Executors.newFixedThreadPool(workers.size()); + workers.forEach(exec::submit); + exec.awaitTermination(2, TimeUnit.SECONDS); + exec.shutdownNow(); + } } ``` -## Class diagram -![Leader/Followers class diagram](./etc/leader-followers.png) -## Applicability -Use Leader-Followers pattern when +This is a basic example of the Leader/Followers pattern. The leader worker processes tasks and promotes a new leader once it finishes a task. The new leader then starts processing the next task, and the cycle continues. + +## When to Use the Leader/Followers Pattern in Java + +* The Leader-Followers pattern is useful in scenarios requiring efficient handling of multiple services on a single thread, avoiding resource thrashing, and improving scalability in concurrent programming environments. +* Applicable in server environments where multiple client requests must be handled concurrently with minimal resource consumption. + +## Real-World Applications of Leader-Followers Pattern in Java + +* Network servers handling multiple incoming connections. +* Event-driven applications that manage a large number of input/output sources. + +## Benefits and Trade-offs of Leader/Followers Pattern + +Benefits: -* You want to establish a concurrent application -* You want faster response times on heavy load -* You want an easily scalable program -* You want to load balance a program +* Reduces the number of threads and context switching, leading to better performance and lower resource utilization. +* Improves system scalability and responsiveness. -## Consequences -Consequences involved with using the Leader/Followers pattern +Trade-offs: -* Implementing this pattern will increase complexity of the code -* If the leader is too slow at delegating processes, some Followers may not get to execute tasks leading to a waste of resources -* There is overhead with organising and maintaining a thread pool -* Debugging is more complex +* Increased complexity in managing the synchronization between leader and followers. +* Potential for underutilization of resources if not correctly implemented. -## Real world examples +## Related Java Design Patterns -* ACE Thread Pool Reactor framework -* JAWS -* Real-time CORBA +* [Half-Sync/Half-Async](https://java-design-patterns.com/patterns/half-sync-half-async/): Leader and Followers can be seen as a variation where the synchronization aspect is divided between the leader (synchronous handling) and followers (waiting asynchronously). +* [Thread Pool](https://java-design-patterns.com/patterns/thread-pool/): Both patterns manage a pool of worker threads, but Thread Pool assigns tasks to any available thread rather than using a leader to distribute work. -## Credits +## References and Credits -* Douglas C. Schmidt and Carlos O’Ryan - Leader/Followers \ No newline at end of file +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) diff --git a/leader-followers/pom.xml b/leader-followers/pom.xml index d77cd803529f..12152cd3fc26 100644 --- a/leader-followers/pom.xml +++ b/leader-followers/pom.xml @@ -34,10 +34,37 @@ leader-followers + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.leaderfollowers.App + + + + + + + + diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java index ffd17bf246b1..826a7bbb1d83 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java @@ -39,27 +39,23 @@ * work. {@link TaskSet} basically acts as the source of input events for the {@link Worker}, who * are spawned and controlled by the {@link WorkCenter} . When {@link Task} arrives then the leader * takes the work and calls the {@link TaskHandler}. It also calls the {@link WorkCenter} to - * promotes one of the followers to be the new leader, who can then process the next work and so - * on. + * promotes one of the followers to be the new leader, who can then process the next work and so on. * - *

The pros for this pattern are: - * It enhances CPU cache affinity and eliminates unbound allocation and data buffer sharing between - * threads by reading the request into buffer space allocated on the stack of the leader or by using - * the Thread-Specific Storage pattern [22] to allocate memory. It minimizes locking overhead by not - * exchanging data between threads, thereby reducing thread synchronization. In bound handle/thread - * associations, the leader thread dispatches the event based on the I/O handle. It can minimize - * priority inversion because no extra queuing is introduced in the server. It does not require a - * context switch to handle each event, reducing the event dispatching latency. Note that promoting - * a follower thread to fulfill the leader role requires a context switch. Programming simplicity: - * The Leader/Followers pattern simplifies the programming of concurrency models where multiple - * threads can receive requests, process responses, and de-multiplex connections using a shared - * handle set. + *

The pros for this pattern are: It enhances CPU cache affinity and eliminates unbound + * allocation and data buffer sharing between threads by reading the request into buffer space + * allocated on the stack of the leader or by using the Thread-Specific Storage pattern [22] to + * allocate memory. It minimizes locking overhead by not exchanging data between threads, thereby + * reducing thread synchronization. In bound handle/thread associations, the leader thread + * dispatches the event based on the I/O handle. It can minimize priority inversion because no extra + * queuing is introduced in the server. It does not require a context switch to handle each event, + * reducing the event dispatching latency. Note that promoting a follower thread to fulfill the + * leader role requires a context switch. Programming simplicity: The Leader/Followers pattern + * simplifies the programming of concurrency models where multiple threads can receive requests, + * process responses, and de-multiplex connections using a shared handle set. */ public class App { - /** - * The main method for the leader followers pattern. - */ + /** The main method for the leader followers pattern. */ public static void main(String[] args) throws InterruptedException { var taskSet = new TaskSet(); var taskHandler = new TaskHandler(); @@ -68,9 +64,7 @@ public static void main(String[] args) throws InterruptedException { execute(workCenter, taskSet); } - /** - * Start the work, dispatch tasks and stop the thread pool at last. - */ + /** Start the work, dispatch tasks and stop the thread pool at last. */ private static void execute(WorkCenter workCenter, TaskSet taskSet) throws InterruptedException { var workers = workCenter.getWorkers(); var exec = Executors.newFixedThreadPool(workers.size()); @@ -81,9 +75,7 @@ private static void execute(WorkCenter workCenter, TaskSet taskSet) throws Inter exec.shutdownNow(); } - /** - * Add tasks. - */ + /** Add tasks. */ private static void addTasks(TaskSet taskSet) throws InterruptedException { var rand = new SecureRandom(); for (var i = 0; i < 5; i++) { diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java index 58b7a2ba3b2d..29374b931cc5 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java @@ -24,29 +24,17 @@ */ package com.iluwatar.leaderfollowers; -/** - * A unit of work to be processed by the Workers. - */ +import lombok.Getter; +import lombok.Setter; + +/** A unit of work to be processed by the Workers. */ public class Task { - private final int time; + @Getter private final int time; - private boolean finished; + @Getter @Setter private boolean finished; public Task(int time) { this.time = time; } - - public int getTime() { - return time; - } - - public void setFinished() { - this.finished = true; - } - - public boolean isFinished() { - return this.finished; - } - } diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java index 9b38b971bba1..8689ce4421dd 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java @@ -26,20 +26,15 @@ import lombok.extern.slf4j.Slf4j; -/** - * The TaskHandler is used by the {@link Worker} to process the newly arrived task. - */ +/** The TaskHandler is used by the {@link Worker} to process the newly arrived task. */ @Slf4j public class TaskHandler { - /** - * This interface handles one task at a time. - */ + /** This interface handles one task at a time. */ public void handleTask(Task task) throws InterruptedException { var time = task.getTime(); Thread.sleep(time); LOGGER.info("It takes " + time + " milliseconds to finish the task"); - task.setFinished(); + task.setFinished(true); } - } diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java index 0c0ce4f30529..c54037024490 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java @@ -27,9 +27,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -/** - * A TaskSet is a collection of the tasks, the leader receives task from here. - */ +/** A TaskSet is a collection of the tasks, the leader receives task from here. */ public class TaskSet { private final BlockingQueue queue = new ArrayBlockingQueue<>(100); diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java index 5976487fa667..c2fd32f3afef 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import lombok.Getter; /** * A WorkCenter contains a leader and a list of idle workers. The leader is responsible for @@ -34,12 +35,10 @@ */ public class WorkCenter { - private Worker leader; + @Getter private Worker leader; private final List workers = new CopyOnWriteArrayList<>(); - /** - * Create workers and set leader. - */ + /** Create workers and set leader. */ public void createWorkers(int numberOfWorkers, TaskSet taskSet, TaskHandler taskHandler) { for (var id = 1; id <= numberOfWorkers; id++) { var worker = new Worker(id, this, taskSet, taskHandler); @@ -56,16 +55,10 @@ public void removeWorker(Worker worker) { workers.remove(worker); } - public Worker getLeader() { - return leader; - } - - /** - * Promote a leader. - */ + /** Promote a leader. */ public void promoteLeader() { Worker leader = null; - if (workers.size() > 0) { + if (!workers.isEmpty()) { leader = workers.get(0); } this.leader = leader; diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java index 5ad463a4a216..b079bc616da7 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java @@ -27,22 +27,17 @@ import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -/** - * Worker class that takes work from work center. - */ +/** Worker class that takes work from work center. */ @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Slf4j public class Worker implements Runnable { - @EqualsAndHashCode.Include - private final long id; + @EqualsAndHashCode.Include private final long id; private final WorkCenter workCenter; private final TaskSet taskSet; private final TaskHandler taskHandler; - /** - * Constructor to create a worker which will take work from the work center. - */ + /** Constructor to create a worker which will take work from the work center. */ public Worker(long id, WorkCenter workCenter, TaskSet taskSet, TaskHandler taskHandler) { super(); this.id = id; @@ -83,5 +78,4 @@ public void run() { } } } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java index acd39e735396..dd8779141c01 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java index 2d92e2e6bf29..a52aa9f79bbe 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for TaskHandler - */ +/** Tests for TaskHandler */ class TaskHandlerTest { @Test @@ -40,5 +38,4 @@ void testHandleTask() throws InterruptedException { taskHandler.handleTask(handle); assertTrue(handle.isFinished()); } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java index 63559a938929..8b55ba41855d 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for TaskSet - */ +/** Tests for TaskSet */ class TaskSetTest { @Test @@ -48,5 +46,4 @@ void testGetTask() throws InterruptedException { assertEquals(100, task.getTime()); assertEquals(0, taskSet.getSize()); } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java index f37d7526e14c..425edc57632c 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for WorkCenter - */ +/** Tests for WorkCenter */ class WorkCenterTest { @Test diff --git a/localization/ar/abstract-document/README.md b/localization/ar/abstract-document/README.md new file mode 100644 index 000000000000..bf1599124f26 --- /dev/null +++ b/localization/ar/abstract-document/README.md @@ -0,0 +1,188 @@ +--- +title: Abstract Document +shortTitle: Abstract Document +category: Structural +language: ar +tag: + - Extensibility +--- + + +## الهدف + +استخدام الخصائص الديناميكية والحصول على مرونة اللغات غير المتغيرة مع الحفاظ على أمان الأنواع. + +## التوضيح + +يتيح استخدام نمط الوثيقة المجردة إدارة الخصائص غير الثابتة الإضافية. يستخدم هذا النمط مفهوم +السمات لتمكين أمان الأنواع وخصائص مفصولة من فئات مختلفة في مجموعة من الواجهات. + +مثال من العالم الحقيقي + +> خذ على سبيل المثال سيارة مكونة من العديد من الأجزاء. ومع ذلك، لا نعرف إذا كانت السيارة تحتوي على جميع الأجزاء أو جزء منها فقط. سياراتنا ديناميكية ومرنة للغاية. + +بصيغة أخرى + +> يسمح نمط الوثيقة المجردة بإضافة خصائص إلى الكائنات دون أن تكون هذه الكائنات على دراية بذلك. + +حسب ويكيبيديا + +> نمط تصميم هيكلي موجه للكائنات لتنظيم الكائنات في حاويات من نوع مفتاح-قيمة بشكل فضفاض مع نوعية غير محددة، وكشف البيانات باستخدام طرق عرض مهيكلة. الهدف من هذا النمط هو تحقيق درجة عالية من المرونة بين المكونات في لغة قوية النوع حيث يمكن إضافة خصائص جديدة إلى شجرة الكائنات أثناء العمل دون فقدان دعم أمان الأنواع. يستخدم النمط السمات لفصل خصائص مختلفة للفئة إلى واجهات متعددة. + +**مثال برمجي** + +أولاً، دعونا نعرف الفئات الأساسية `Document` و `AbstractDocument`. في الأساس، تجعل الكائن يحتوي على خريطة من الخصائص وأي عدد من الكائنات الفرعية. + + +```java +public interface Document { + + Void put(String key, Object value); + + Object get(String key); + + Stream children(String key, Function, T> constructor); +} + +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map 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 Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + ... +} +``` + +بعد ذلك، نعرف `enum` لـ `Property` ومجموعة من الواجهات للنمط، السعر، النموذج، والأجزاء. هذا يتيح لنا إنشاء واجهات تظهر بشكل ثابت لفئة `Car`. + + +```java +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} + +public interface HasType extends Document { + + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } +} + +public interface HasPrice extends Document { + + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } +} +public interface HasModel extends Document { + + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } +} + +public interface HasParts extends Document { + + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} +``` + +Ahora estamos listos para introducir el Coche `Car`. + +```java +public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { + + public Car(Map properties) { + super(properties); + } +} +``` + +وأخيرًا، هكذا نبني ونستخدم السيارة `Car` في مثال كامل. + + +```java + LOGGER.info("Constructing parts and car"); + + 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 carProperties = Map.of( + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); + + 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)) + ); + + // Constructing parts and car + // Here is our car: + // model: 300SL + // price: 10000 + // parts: + // wheel/15C/100 + // door/Lambo/300 +``` + +## Diagrama de clases + +![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain") + +## التطبيق + +استخدم نمط الوثيقة المجردة عندما: + +* يوجد حاجة لإضافة خصائص أثناء العمل. +* ترغب في طريقة مرنة لتنظيم النطاق في هيكل مشابه لشجرة. +* ترغب في نظام أقل ترابطًا. + +## الحقوق + + +* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) +* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) +* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492) \ No newline at end of file diff --git a/localization/ar/abstract-document/etc/abstract-document.png b/localization/ar/abstract-document/etc/abstract-document.png new file mode 100644 index 000000000000..6bc0b29a4e77 Binary files /dev/null and b/localization/ar/abstract-document/etc/abstract-document.png differ diff --git a/localization/ar/abstract-factory/README.md b/localization/ar/abstract-factory/README.md new file mode 100644 index 000000000000..230c9ae12b44 --- /dev/null +++ b/localization/ar/abstract-factory/README.md @@ -0,0 +1,234 @@ +--- +title: Abstract Factory +shortTitle: Abstract Factory +category: Creational +language: ar +tag: + - Gang of Four +--- +## يُعرف أيضًا باسم + +Kit + +## الهدف + +توفير واجهة لإنشاء عائلات من الكائنات المرتبطة المعتمدة دون تحديد فئتها المحددة. + +## التوضيح + +مثال من العالم الحقيقي + +> لإنشاء مملكة نحتاج إلى كائنات بموضوع مشترك. المملكة الإلفية تحتاج إلى ملك إلفي، وقلعة إلفية، وجيش إلفي، بينما المملكة الأوركية تحتاج إلى ملك أوركي، وقلعة أوركية، وجيش أوركي. هناك اعتماد بين كائنات المملكة. + +بصيغة أخرى + +> مصنع للمصانع؛ مصنع يجمع بين مصانع فردية ولكنها مرتبطة/معتمدة دون تحديد فئتها المحددة. + +حسب ويكيبيديا + +> يوفر نمط المصنع المجرد طريقة لتغليف مجموعة من المصانع الفردية التي لها موضوع مشترك دون تحديد فئاتها المحددة. + +**مثال برمجي** + +ترجمة المثال السابق حول الممالك. أولاً لدينا بعض الواجهات والتنفيذات لكائنات `Castle`. + + +```java +public interface Castle { + String getDescription(); +} + +public interface King { + String getDescription(); +} + +public interface Army { + String getDescription(); +} + +// Elven implementations -> +public class ElfCastle implements Castle { + static final String DESCRIPTION = "This is the elven castle!"; + @Override + public String getDescription() { + return DESCRIPTION; + } +} +public class ElfKing implements King { + static final String DESCRIPTION = "This is the elven king!"; + @Override + public String getDescription() { + return DESCRIPTION; + } +} +public class ElfArmy implements Army { + static final String DESCRIPTION = "This is the elven Army!"; + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +// التنفيذات Orcish بطريقة مشابهة +-> ... + +``` + +ثم لدينا التجريد والتنفيذ لمصنع المملكة `KingdomFactory`. + + +```java +public interface KingdomFactory { + Castle createCastle(); + King createKing(); + Army createArmy(); +} + +public class ElfKingdomFactory implements KingdomFactory { + + @Override + public Castle createCastle() { + return new ElfCastle(); + } + + @Override + public King createKing() { + return new ElfKing(); + } + + @Override + public Army createArmy() { + return new ElfArmy(); + } +} + +public class OrcKingdomFactory implements KingdomFactory { + + @Override + public Castle createCastle() { + return new OrcCastle(); + } + + @Override + public King createKing() { + return new OrcKing(); + } + + @Override + public Army createArmy() { + return new OrcArmy(); + } +} +``` + +الآن لدينا المصنع المجرد الذي يسمح لنا بإنشاء عائلات من الكائنات المرتبطة. على سبيل المثال، مصنع المملكة الإلفية `ElfKingdomFactory` يقوم بإنشاء القلعة `castle`، الملك `king`، والجيش `army`، إلخ. + + + +```java +var factory = new ElfKingdomFactory(); +var castle = factory.createCastle(); +var king = factory.createKing(); +var army = factory.createArmy(); + +castle.getDescription(); +king.getDescription(); +army.getDescription(); +``` + +ناتج البرنامج: + + +```java +This is the elven castle! +This is the elven king! +This is the elven Army! +``` + +الآن يمكننا تصميم مصنع لمصانع الممالك الخاصة بنا. في هذا المثال، قمنا بإنشاء `FactoryMaker`، المسؤول عن إعادة نسخة من `ElfKingdomFactory` أو `OrcKingdomFactory`. +يمكن للعميل استخدام `FactoryMaker` لإنشاء مصنع محدد، والذي بدوره سينتج كائنات محددة مختلفة (مشتقة من `Army` و `King` و `Castle`). +في هذا المثال نستخدم أيضًا `enum` لتمرير نوع مصنع المملكة الذي سيطلبه العميل. + + +```java +public static class FactoryMaker { + + public enum KingdomType { + ELF, ORC + } + + public static KingdomFactory makeFactory(KingdomType type) { + return switch (type) { + case ELF -> new ElfKingdomFactory(); + case ORC -> new OrcKingdomFactory(); + default -> throw new IllegalArgumentException("KingdomType not supported."); + }; + } +} + + 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("Orc Kingdom"); + app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC)); + --similar use of the orc factory + } +``` + +## مخطط الفئات + +![alt text](./etc/abstract-factory.urm.png "Diagrama de Clases de Abstract Factory") + + +## التطبيق + +استخدم نمط المصنع المجرد عندما: + +* يجب أن يكون النظام غير متحيز حول كيفية إنشاء وتركيب وتمثيل كائناته. +* يجب تكوين النظام مع إحدى عائلات المنتجات المتعددة. +* تم تصميم عائلة الكائنات المرتبطة لتستخدم معًا وتحتاج إلى فرض هذا الافتراض. +* ترغب في توفير مكتبة من المنتجات ولا تريد الكشف عن تنفيذاتها، بل واجهاتها فقط. +* العمر الافتراضي للاعتماد هو مفهومًا أقصر من عمر العميل. +* تحتاج إلى قيمة في وقت التشغيل لبناء الاعتماد. +* تريد تحديد أي منتج من العائلة يتم استدعاؤه في وقت التشغيل. +* تحتاج إلى توفير واحد أو أكثر من المعلمات المعروفة فقط في وقت التشغيل قبل أن تتمكن من حل الاعتماد. +* تحتاج إلى الاتساق بين المنتجات. +* لا تريد تغيير الكود الموجود عند إضافة منتجات أو عائلات جديدة من المنتجات إلى البرنامج. + +أمثلة على حالات الاستخدام + +* اختيار استدعاء التنفيذ الصحيح لـ FileSystemAcmeService أو DatabaseAcmeService أو NetworkAcmeService في وقت التشغيل. +* كتابة الاختبارات الوحدوية تصبح أسهل بكثير. +* أدوات واجهة المستخدم (UI) لأنظمة تشغيل مختلفة (SO). + +## العواقب + +* إخفاء حقن الاعتمادات في جافا داخل كائنات الخدمة قد يؤدي إلى أخطاء في وقت التشغيل كان يمكن تجنبها في وقت الترجمة. +* بينما يكون النمط جيدًا في إنشاء كائنات محددة مسبقًا، قد يكون من الصعب إضافة الجديدة. +* الكود يصبح أكثر تعقيدًا مما ينبغي لأنه يتم إضافة العديد من الواجهات والفئات الجديدة جنبًا إلى جنب مع النمط. + +## الدروس التعليمية + +* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java) + +## الاستخدامات المعروفة + +* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) +* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) +* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) + +## الأنماط المتعلقة + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) + +## الحقوق + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/ar/abstract-factory/etc/abstract-factory.urm.png b/localization/ar/abstract-factory/etc/abstract-factory.urm.png new file mode 100644 index 000000000000..836858a2c652 Binary files /dev/null and b/localization/ar/abstract-factory/etc/abstract-factory.urm.png differ diff --git a/localization/ar/active-object/README.md b/localization/ar/active-object/README.md new file mode 100644 index 000000000000..842570a2604a --- /dev/null +++ b/localization/ar/active-object/README.md @@ -0,0 +1,126 @@ +--- +title: Active Object +shortTitle: Active Object +category: Concurrency +language: ar +tag: + - Performance +--- + + +## الهدف +يفصل نمط التصميم الكائن النشط بين تنفيذ الطريقة واستدعائها للكائنات التي تعمل في خيط تحكم خاص بها. الهدف هو إدخال التزامن باستخدام استدعاءات الطرق غير المتزامنة وجدولة لإدارة الطلبات. + +## التوضيح + +ستحتوي الفئة التي تنفذ نمط التصميم الكائن النشط على آلية مزامنة ذاتية دون استخدام الطرق المتزامنة (synchronized). + +مثال من العالم الحقيقي + +> الأورك معروفون بوحشيتهم وفلسفتهم في عدم العمل الجماعي. بناءً على هذا السلوك، يمكن القول إن لديهم خيط تحكم خاص بهم. + +يمكننا استخدام نمط الكائن النشط لتنفيذ مخلوق لديه خيط تحكم خاص به ويعرض واجهة برمجة التطبيقات (API) الخاصة به، ولكن ليس التنفيذ نفسه. + +**مثال برمجي** + + +```java +public abstract class ActiveCreature{ + private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); + + private BlockingQueue requests; + + private String name; + + private Thread thread; + + public ActiveCreature(String name) { + this.name = name; + this.requests = new LinkedBlockingQueue(); + thread = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + requests.take().run(); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + } + } + ); + 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 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; + } +} +``` +يمكننا أن نرى أن أي فئة تمتد من ActiveCreature سيكون لديها خيط تحكم خاص بها لاستدعاء وتنفيذ الطرق. + +على سبيل المثال، الفئة Orc: + +```java +public class Orc extends ActiveCreature { + + public Orc(String name) { + super(name); + } + +} +``` +الآن يمكننا إنشاء مخلوقات متعددة مثل الأورك، نطلب منهم الأكل والتجول، وسيفعلون ذلك في خيط التحكم الخاص بهم: + +```java + public static void main(String[] args) { + 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); + } +``` + +## مخطط الفئات + +![alt text](./etc/active-object.urm.png "Active Object class diagram") + +## الدروس التعليمية + +* [Android and Java Concurrency: The Active Object Pattern](https://www.youtube.com/watch?v=Cd8t2u5Qmvc) \ No newline at end of file diff --git a/localization/ar/active-object/etc/active-object.urm.png b/localization/ar/active-object/etc/active-object.urm.png new file mode 100644 index 000000000000..c14f66144ee2 Binary files /dev/null and b/localization/ar/active-object/etc/active-object.urm.png differ diff --git a/localization/ar/acyclic-visitor/README.md b/localization/ar/acyclic-visitor/README.md new file mode 100644 index 000000000000..8bb8e01a154f --- /dev/null +++ b/localization/ar/acyclic-visitor/README.md @@ -0,0 +1,167 @@ +--- +title: Acyclic Visitor +shortTitle: Acyclic Visitor +category: Behavioral +language: ar +tag: + - Extensibility +--- +## الهدف + +السماح بإضافة وظائف جديدة إلى تسلسلات الفئات الموجودة دون التأثير عليها، ودون إنشاء الدوائر المعتمدة المزعجة التي هي جزء من نمط GoF (Gang of Four) للزائر (Visitor). + +## التوضيح + +مثال من العالم الحقيقي + +> لدينا تسلسل فئات مودم. يجب أن تتم زيارة المودمات في هذه التسلسلات بواسطة خوارزمية خارجية بناءً على بعض الفلاتر (هل المودم متوافق مع Unix أو DOS؟). + +بصيغة أخرى + +> يتيح نمط Acyclic Visitor إضافة وظائف إلى تسلسلات الفئات الموجودة دون تعديلها. + +[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) يقول + +> يسمح نمط Acyclic Visitor بإضافة وظائف جديدة إلى تسلسلات الفئات الموجودة دون التأثير عليها، ودون إنشاء الدوائر المعتمدة التي هي جزء من نمط الزائر (Visitor Pattern) في GangOfFour. + +**مثال برمجي** + +هنا لدينا تسلسل `Modem`. + + +```java +public abstract class Modem { + 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"); + } + } +} + +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"); + } + } +} +``` + +بعد ذلك، لدينا تسلسل `ModemVisitor`. + + +```java +public interface ModemVisitor { +} + +public interface HayesVisitor extends ModemVisitor { + void visit(Hayes hayes); +} + +public interface ZoomVisitor extends ModemVisitor { + void visit(Zoom zoom); +} + +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(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."); + } +} +``` + +وأخيرًا، هنا "الزوار" في العمل. + + +```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); +``` + +ناتج البرنامج: + + +``` + // Hayes modem used with Dos configurator. + // Zoom modem used with Dos configurator. + // Only HayesVisitor is allowed to visit Hayes modem + // Zoom modem used with Unix configurator. +``` + + +## مخطط الفئات + +![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") + +## التطبيق + +يمكن استخدام هذا النمط في الحالات التالية: + +* عندما تحتاج إلى إضافة وظيفة جديدة إلى تسلسل فئات دون التأثير عليها أو تعديلها. +* عندما توجد وظائف تعمل على التسلسل ولكنها لا تنتمي إلى التسلسل بحد ذاته. على سبيل المثال، الفئات `ConfigureForDOS` و `ConfigureForUnix` و `ConfigureForX`. +* عندما تحتاج إلى تنفيذ عمليات مختلفة تمامًا على كائن اعتمادًا على نوعه. +* عندما يكون من المتوقع توسيع التسلسل الزائر بشكل متكرر باستخدام مشتقات من فئة العنصر. +* عندما يكون عملية إعادة التجميع، الربط، الاختبار أو توزيع المشتقات من فئة العنصر مرهقة جدًا. + +## الدروس التعليمية + +* [Acyclic Visitor Pattern Example](https://codecrafter.blogspot.com/2012/12/the-acyclic-visitor-pattern.html) + +## العواقب + +الجوانب الجيدة: + +* لا توجد دوائر اعتماد بين التسلسلات. +* لا حاجة لإعادة تجميع جميع الزوار إذا تم إضافة زائر جديد. +* لا يسبب أخطاء في الترجمة في الزوار الموجودين إذا كانت التسلسل تحتوي على عضو جديد. + +الجوانب السيئة: + + +* ينتهك [مبدأ الاستبدال في ليسكوف](https://java-design-patterns.com/principles/#liskov-substitution-principle) من خلال إظهار أنه يمكن قبول جميع الزوار مع الاهتمام بزائر واحد فقط. +* يجب إنشاء تسلسل زوار موازٍ لجميع الأعضاء في التسلسل الذي يمكن زيارته. + +## الأنماط المتعلقة + +* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/) + + +## الحقوق + + +* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) +* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) diff --git a/localization/ar/acyclic-visitor/etc/acyclic-visitor.png b/localization/ar/acyclic-visitor/etc/acyclic-visitor.png new file mode 100644 index 000000000000..7b4df13d80f8 Binary files /dev/null and b/localization/ar/acyclic-visitor/etc/acyclic-visitor.png differ diff --git a/localization/ar/adapter/README.md b/localization/ar/adapter/README.md new file mode 100644 index 000000000000..06cb0a14ae6e --- /dev/null +++ b/localization/ar/adapter/README.md @@ -0,0 +1,141 @@ +--- +title: Adapter +shortTitle: Adapter +category: Structural +language: ar +tag: + - Gang of Four +--- + +## أيضًا معروف بـ +الغطاء (Wrapper) + +## الهدف +تحويل واجهة فئة إلى واجهة أخرى يتوقعها العميل. يتيح نمط المحول (Adapter) للفئات العمل مع فئات أخرى التي لا يمكنها العمل معها في الظروف العادية بسبب مشاكل التوافق. + +## التوضيح + +مثال من العالم الحقيقي + +> تخيل أنك تمتلك بعض الصور في بطاقة ذاكرة وتريد نقلها إلى جهاز الكمبيوتر الخاص بك. لنقل الصور، تحتاج إلى نوع من المحول الذي يتوافق مع منافذ جهاز الكمبيوتر الخاص بك ويسمح لك بإدخال البطاقة. في هذه الحالة، قارئ البطاقات هو محول (Adapter). +> مثال آخر هو محول التيار الكهربائي؛ إذا كان هناك قابس بثلاثة دبابيس ولا يمكن توصيله بمنفذ كهربائي به ثقبين، فإنه يحتاج إلى محول لجعله متوافقًا مع المنفذ. +> مثال آخر هو مترجم يترجم كلمات من شخص لآخر. + +بصيغة أخرى + +> يتيح نمط المحول (Adapter) تغليف كائن داخل محول لجعله متوافقًا مع فئة سيكون غير متوافق معها بطريقة أخرى. + +حسب ويكيبيديا + +> في هندسة البرمجيات، نمط المحول هو نمط تصميم برمجي يسمح باستخدام واجهة فئة موجودة كواجهة مختلفة. وغالبًا ما يُستخدم لجعل الفئات الموجودة تعمل مع فئات أخرى دون الحاجة إلى تعديل كود المصدر الخاص بها. + +**مثال برمجي** + +خذ على سبيل المثال قبطان يمكنه فقط استخدام القوارب التي تعمل بالمجاديف ولا يمكنه الإبحار على الإطلاق. + +أولاً، لدينا الواجهات `RowingBoat` (قارب المجاديف) و `FishingBoat` (قارب الصيد). + +```java +public interface RowingBoat { + void row(); +} + +@Slf4j +public class FishingBoat { + public void sail() { + LOGGER.info("The fishing boat is sailing"); + } +} +``` + +ويتوقع القبطان تنفيذ واجهة `RowingBoat` (قارب المجاديف) ليتمكن من التحرك. + + +```java +public class Captain { + + private final RowingBoat rowingBoat; + // default constructor and setter for rowingBoat + public Captain(RowingBoat rowingBoat) { + this.rowingBoat = rowingBoat; + } + + public void row() { + rowingBoat.row(); + } +} +``` + +الآن لنفترض أن مجموعة من القراصنة قد جاءت ويجب على قائدنا الهروب، ولكن هناك فقط قارب صيد. نحتاج إلى إنشاء محول يسمح للقائد باستخدام قارب الصيد مع مهاراته لاستخدام القوارب التي تعمل بالمجاديف. + + +```java +@Slf4j +public class FishingBoatAdapter implements RowingBoat { + + private final FishingBoat boat; + + public FishingBoatAdapter() { + boat = new FishingBoat(); + } + + @Override + public void row() { + boat.sail(); + } +} +``` + +والآن يمكن لـ `Captain` (القائد) استخدام `FishingBoat` (قارب الصيد) للهروب من القراصنة. + + +```java +var captain = new Captain(new FishingBoatAdapter()); +captain.row(); +``` + +## مخطط الفئات +![alt text](./etc/adapter.urm.png "Adapter class diagram") + +## التطبيق +استخدم نمط المحول (Adapter) عندما: + +* تريد استخدام فئة موجودة وواجهتها لا تتناسب مع ما تحتاجه. +* تريد إنشاء فئة قابلة لإعادة الاستخدام تتعاون مع فئات غير مرتبطة أو لم يكن من المخطط تعاونها معًا، أي فئات ليس لديها بالضرورة واجهات متوافقة. +* تحتاج إلى استخدام عدة فئات فرعية موجودة، ولكن من غير العملي تعديل واجهتها عن طريق إنشاء فئات فرعية لكل واحدة. يمكن للمحول تعديل واجهة الفئة الأصلية. +* العديد من التطبيقات التي تستخدم مكتبات طرف ثالث تستخدم المحولات كطبقات وسيطة بين التطبيق والمكتبة لفصل التطبيق عن المكتبة. إذا كان من الضروري استخدام مكتبة أخرى، يكفي إنشاء محول للمكتبة الجديدة دون الحاجة إلى تعديل كود التطبيق. + +## الدروس + + +* [Dzone](https://dzone.com/articles/adapter-design-pattern-in-java) +* [Refactoring Guru](https://refactoring.guru/design-patterns/adapter/java/example) +* [Baeldung](https://www.baeldung.com/java-adapter-pattern) + +## العواقب +المحولات بين الفئات والكائنات لها خصائص مختلفة. محول الفئات + +* يقوم بإجراء التكيف ويرتبط بفئة محددة. كنتيجة لذلك، لن يعمل محول الفئات عندما نريد تكيف فئة وفئاتها الفرعية. +* يسمح للمحول بتعديل سلوك الفئة المتكيفة لأن المحول هو فئة فرعية من الفئة المتكيفة. +* يستخدم كائنًا واحدًا ولا يحتاج إلى استخدام مؤشرات إضافية للإشارة إلى الفئة المتكيفة. + +محوّل الكائنات + +* يسمح للمحول الواحد بالعمل مع العديد من الفئات، أي مع الفئة المتكيفة وكل الفئات الفرعية لها (إذا كانت موجودة). يمكن للمحول أيضًا إضافة وظائف إلى جميع الفئات المتكيفة في نفس الوقت. +* يجعل من الصعب تعديل سلوك الفئة المتكيفة. سيكون من الضروري إنشاء فئة فرعية للفئة المتكيفة وجعل المحول يشير إلى الفئة الفرعية بدلاً من الفئة الأصلية. + +## أمثلة من العالم الواقعي + + +* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) +* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) +* [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-) +* [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-) + + +## الشكر + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/ar/adapter/etc/adapter.urm.png b/localization/ar/adapter/etc/adapter.urm.png new file mode 100644 index 000000000000..341ad67699d9 Binary files /dev/null and b/localization/ar/adapter/etc/adapter.urm.png differ diff --git a/localization/ar/aggregator-microservices/README.md b/localization/ar/aggregator-microservices/README.md new file mode 100644 index 000000000000..40d3ff26f7a8 --- /dev/null +++ b/localization/ar/aggregator-microservices/README.md @@ -0,0 +1,111 @@ +--- +title: Aggregator Microservices +shortTitle: Aggregator Microservices +category: Architectural +language: ar +tag: +- Cloud distributed +- Decoupling +- Microservices +--- + +## الهدف + +يقوم المستخدم بإجراء مكالمة واحدة إلى خدمة المجمع، ومن ثم يقوم المجمع بالاتصال بكل خدمة ميكروسيرفيس ذات صلة. + +## الشرح + +مثال من العالم الواقعي + +> يحتاج سوقنا الإلكتروني إلى معلومات حول المنتجات والمخزون الحالي. يقوم بإجراء مكالمة إلى خدمة المجمع التي بدورها تتصل بخدمة ميكروسيرفيس المعلومات الخاصة بالمنتج وخدمة ميكروسيرفيس المخزون للمنتج التي تعيد المعلومات مجمعة. + +ببساطة + +> يقوم ميكروسيرفيس المجمع بجمع البيانات من عدة ميكروسيرفيسات ويعيد النتيجة مجمعة لمعالجتها. + +يقول StackOverflow + +> يقوم ميكروسيرفيس المجمع بالاتصال بعدة خدمات لتحقيق الوظيفة المطلوبة من التطبيق. + +**مثال برمجي** + +لنبدأ بنموذج البيانات. هنا هو `Product`. + + +```java +public class Product { + private String title; + private int productInventories; + // getters and setters -> + ... +} +``` + +بعد ذلك، يمكننا تقديم ميكروسيرفيسنا `Aggregator` (مجمع الميكروسيرفيسات). يحتوي على `ProductInformationClient` (عميل معلومات المنتج) و +`ProductInventoryClient` (عميل مخزون المنتج) للاتصال بالميكروسيرفيسات المعنية. + + +```java +@RestController +public class Aggregator { + + @Resource + private ProductInformationClient informationClient; + + @Resource + private ProductInventoryClient inventoryClient; + + @RequestMapping(path = "/product", method = RequestMethod.GET) + public Product getProduct() { + + 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 default error inventory + product.setProductInventories(requireNonNullElse(productInventory, -1)); + + return product; + } +} +``` + +هذه هي جوهر تنفيذ الميكروسيرفيسات للمعلومات. ميكروسيرفيس المخزون مشابه، ببساطة يعيد +عدد المخزون. + + +```java +@RestController +public class InformationController { + @RequestMapping(value = "/information", method = RequestMethod.GET) + public String getProductTitle() { + return "The Product Title."; + } +} +``` + +الآن عند استدعاء واجهة برمجة التطبيقات REST الخاصة بنا `Aggregator` تُرجع معلومات المنتج. + + +```bash +curl http://localhost:50004/product +{"title":"The Product Title.","productInventories":5} +``` + +## مخطط الفئة + +![alt text](./aggregator-service/etc/aggregator-service.png "Aggregator Microservice") + +## قابلية التطبيق + +استخدم نمط تجميع الميكروسيرفيسات (Aggregator Microservices) عندما تحتاج إلى واجهة برمجة تطبيقات موحدة لعدة ميكروسيرفيسات، بغض النظر عن جهاز العميل. + + +## الشكر + +* [أنماط تصميم الميكروسيرفيس](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/) +* [أنماط الميكروسيرفيس: مع أمثلة في Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60) +* [أنماط العمارة: اكتشاف الأنماط الأساسية في أكثر المجالات ضرورة في العمارة المؤسسية](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4) diff --git a/aggregator-microservices/aggregator-service/etc/aggregator-service.png b/localization/ar/aggregator-microservices/aggregator-service/etc/aggregator-service.png similarity index 100% rename from aggregator-microservices/aggregator-service/etc/aggregator-service.png rename to localization/ar/aggregator-microservices/aggregator-service/etc/aggregator-service.png diff --git a/localization/ar/ambassador/README.md b/localization/ar/ambassador/README.md new file mode 100644 index 000000000000..b70fe071eb31 --- /dev/null +++ b/localization/ar/ambassador/README.md @@ -0,0 +1,210 @@ +--- +title: Ambassador +shortTitle: Ambassador +category: Structural +language: ar +tag: + - Decoupling + - Cloud distributed +--- + +## الهدف + +توفير مثيل من الخدمة المساعدة للعميل وتفويض الوظائف المشتركة لها من مصدر مشترك. + +## الشرح + +مثال واقعي + +> خدمة بعيدة تحتوي على العديد من العملاء الذين يصلون إلى وظيفة تقدمها هذه الخدمة. الخدمة هي تطبيق قديم ومن +> المستحيل تحديثها. عدد كبير من الطلبات من قبل المستخدمين تسبب مشاكل في الاتصال. يجب تنفيذ قواعد جديدة +> بشأن تكرار الطلبات جنبًا إلى جنب مع فحوصات التأخير والسجلات من جانب العميل. + +بكلمات أخرى + +> باستخدام نمط Ambassador، يمكننا تنفيذ تقليل تكرار الطلبات من العملاء جنبًا إلى جنب مع فحوصات التأخير +> والسجلات. + +وفقًا لوثائق مايكروسوفت + +> يمكن اعتبار خدمة Ambassador وكأنها وكيل خارج العملية يتواجد جنبًا إلى جنب مع العميل. +> +> يمكن أن يكون هذا النمط مفيدًا لتفريغ المهام المشتركة في الاتصال من العميل مثل المراقبة، السجلات، التوجيه، +> الأمان (مثل TLS) وأنماط المقاومة (*) بطريقة مستقلة عن اللغة. غالبًا ما يُستخدم مع التطبيقات القديمة، +> أو التطبيقات الأخرى التي يصعب تعديلها، بهدف توسيع قدراتها في الشبكات. يمكنه أيضًا +> تمكين فريق متخصص لتنفيذ هذه الميزات. + +**مثال على الكود** + +مع المقدمة السابقة في الاعتبار، سنقوم بمحاكاة وظيفتها في المثال التالي. لدينا واجهة يتم تنفيذها +من قبل الخدمة البعيدة وكذلك خدمة Ambassador: + +```java +interface RemoteServiceInterface { + long doRemoteFunction(int value) throws Exception; +} +``` + +## خدمة بعيدة ممثلة كـ Singleton (مثيل واحد). + +```java +@Slf4j +public class RemoteService implements RemoteServiceInterface { + 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); + } + + return waitTime >= 200 ? value * 10 : -1; + } +} +``` + +## خدمة السفير مع إضافة وظائف إضافية مثل السجلات والتحقق من الكمون + +```java +@Slf4j +public class ServiceAmbassador implements RemoteServiceInterface { + 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; + } + } + return result; + } +} +``` + +العميل لديه خدمة سفير محلية تستخدم للتفاعل مع الخدمة البعيدة: + + +```java +@Slf4j +public class Client { + private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador(); + + long useService(int value) { + var result = serviceAmbassador.doRemoteFunction(value); + LOGGER.info("Service result: " + result); + return result; + } +} +``` + +بعد ذلك، لدينا عميلان يستخدمان الخدمة: + + +```java +public class App { + public static void main(String[] args) { + var host1 = new Client(); + var host2 = new Client(); + host1.useService(12); + host2.useService(73); + } +} +``` + +هذه هي النتيجة التي سنحصل عليها بعد تنفيذ المثال: + + +```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 +``` + +## مخطط الفئات + +![alt text](./etc/ambassador.urm.png "مخطط فئات Ambassador") + +## التطبيقات + +يتم تطبيق نمط Ambassador عندما نعمل مع خدمة بعيدة قديمة لا يمكن تعديلها أو أنه سيكون من الصعب للغاية تعديلها. يمكن تنفيذ خصائص الاتصال في العميل دون الحاجة إلى إجراء تغييرات على الخدمة البعيدة. + +* يوفر Ambassador واجهة محلية لخدمة بعيدة. +* يوفر Ambassador سجلات، انقطاع الدائرة، إعادة المحاولات، والأمان في العميل. + +## حالات الاستخدام النموذجية + +* التحكم في الوصول إلى كائن آخر +* تنفيذ السجلات أو السجلات +* تنفيذ انقطاع الدائرة +* تفويض مهام الخدمات البعيدة +* تسهيل الاتصال بالشبكة + +## الاستخدامات المعروفة + +* [بوابة API المدمجة مع Kubernetes للخدمات الصغيرة](https://github.com/datawire/ambassador) + +## الأنماط ذات الصلة + +* [الوكيل (Proxy)](https://java-design-patterns.com/patterns/proxy/) + +## الشكر + +* [نمط Ambassador (وثائق Microsoft باللغة الإنجليزية)](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) +* [تصميم الأنظمة الموزعة: الأنماط والمفاهيم للخدمات القابلة للتوسع وموثوقة](https://www.amazon.com/s?k=designing+distributed+systems&sprefix=designing+distri%2Caps%2C156&linkCode=ll2&tag=javadesignpat-20&linkId=a12581e625462f9038557b01794e5341&language=en_US&ref_=as_li_ss_tl) + +## ملاحظات المترجم +(*) تشير النسخة الأصلية بالإنجليزية من وثائق Microsoft إلى مصطلح المرونة +وفي الترجمة الإسبانية يتم ترجمته إلى المقاومة، على الرغم من ربطه بالقسم الخاص بنمط الموثوقية. انظر: +* [نسخة الوثائق الخاصة بنمط Ambassador من Microsoft باللغة الإسبانية.](https://learn.microsoft.com/es-es/azure/architecture/patterns/ambassador) diff --git a/localization/ar/ambassador/etc/ambassador.urm.png b/localization/ar/ambassador/etc/ambassador.urm.png new file mode 100644 index 000000000000..9b50a02ad356 Binary files /dev/null and b/localization/ar/ambassador/etc/ambassador.urm.png differ diff --git a/localization/ar/api-gateway/README.md b/localization/ar/api-gateway/README.md new file mode 100644 index 000000000000..a3f33a1c40eb --- /dev/null +++ b/localization/ar/api-gateway/README.md @@ -0,0 +1,170 @@ +--- +title: API Gateway +shortTitle: API Gateway +category: Architectural +language: ar +tag: + - Cloud distributed + - Decoupling + - Microservices +--- + +## الهدف + +إضافة استدعاءات إلى الخدمات الصغيرة في نفس المكان، بوابة API (API Gateway). يقوم المستخدم +بإجراء استدعاء بسيط إلى بوابة API، وتقوم بوابة API بدورها باستدعاء كل خدمة صغيرة ذات صلة. + +## الشرح + +مع نمط الخدمات الصغيرة، قد يحتاج العميل إلى بيانات من عدة خدمات صغيرة. إذا كان +العميل سيستدعي كل خدمة صغيرة بشكل مباشر، فقد يؤدي ذلك إلى أوقات تحميل طويلة، حيث +يجب على العميل إجراء طلب شبكة لكل خدمة صغيرة يتم استدعاؤها. بالإضافة إلى ذلك، وجود +استدعاء العميل لكل خدمة صغيرة يربط العميل مباشرة بتلك الخدمة الصغيرة - إذا حدث تغيير داخلي +في الخدمات الصغيرة (على سبيل المثال، إذا تم دمج خدمتين صغيرتين في وقت ما في المستقبل) أو إذا تغير الموقع (الخادم والمنفذ) لأحد الخدمات الصغيرة، فإن كل +عميل يستخدم تلك الخدمات الصغيرة يجب تحديثه. + +الهدف من نمط بوابة API هو تخفيف بعض هذه المشاكل. في نمط بوابة API، يتم وضع كيان إضافي +(بوابة API) بين العميل والخدمات الصغيرة. +مهمة بوابة API هي إضافة استدعاءات إلى الخدمات الصغيرة. بدلاً من أن يقوم العميل +بإجراء استدعاء لكل خدمة صغيرة على حدة، يقوم العميل باستدعاء بوابة API مرة واحدة فقط. ثم تقوم بوابة +API باستدعاء كل واحدة من الخدمات الصغيرة التي يحتاجها العميل. + +مثال واقعي + +> نحن نطبق نظام خدمات صغيرة وبوابة API لموقع تجارة إلكترونية. في هذا +> النظام تقوم بوابة API بإجراء استدعاءات إلى خدمات Image و Price. (الصورة والسعر) + +بمعنى آخر + +> في نظام يتم تنفيذه باستخدام بنية خدمات صغيرة، تعتبر بوابة API هي النقطة +> الوحيدة للوصول التي تجمع استدعاءات الخدمات الصغيرة الفردية. + +تقول ويكيبيديا + +> بوابة API هي خادم يعمل كواجهة أمامية لـ API، يستقبل طلبات API، ويطبق +> حدودًا وسياسات الأمان، ويرسل الطلبات إلى الخدمة الخلفية ثم يعيد +> الاستجابة إلى الطالب. غالبًا ما تشمل بوابة API محركًا للتحويل لتنظيم +> وتعديل الطلبات والاستجابات أثناء سير العملية. يمكن أن توفر بوابة +> API أيضًا وظائف مثل جمع تحليلات البيانات والتخزين المؤقت. قد +> توفر بوابة API وظائف لدعم المصادقة، والتفويض، والأمان، +> والتدقيق، والامتثال. + +**كود المثال** + +يوضح هذا التنفيذ كيف قد يبدو نمط بوابة API لموقع تجارة إلكترونية. تقوم +`ApiGateway` بإجراء استدعاءات إلى خدمات Image و Price باستخدام +`ImageClientImpl` و `PriceClientImpl` على التوالي. العملاء الذين يشاهدون الموقع +على جهاز مكتبي يمكنهم مشاهدة معلومات الأسعار وصورة المنتج، لذلك تقوم `ApiGateway` +بإجراء استدعاء إلى الخدمات الصغيرة وجمع البيانات في نموذج `DesktopProduct`. +ومع ذلك، فإن مستخدمي الأجهزة المحمولة يرون فقط معلومات الأسعار، ولا يشاهدون صورة المنتج. +بالنسبة لمستخدمي الأجهزة المحمولة، تقوم `ApiGateway` فقط +بالحصول على معلومات الأسعار، والتي تستخدم لإكمال `MobileProduct`. + +إليك تنفيذ خدمة الصورة (Image). + + +```java +public interface ImageClient { + 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(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +إليك تنفيذ خدمة المصغرة للسعر (Price). + +```java +public interface PriceClient { + 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(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +هنا يمكننا أن نرى كيف تقوم بوابة الـ API بتوجيه الطلبات إلى الخدمات المصغرة. + +```java +public class ApiGateway { + + @Resource + private ImageClient imageClient; + + @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 = "/mobile", method = RequestMethod.GET) + public MobileProduct getProductMobile() { + var mobileProduct = new MobileProduct(); + mobileProduct.setPrice(priceClient.getPrice()); + return mobileProduct; + } +} +``` + +## مخطط الفئة + +![alt text](./etc/api-gateway.png "API Gateway") + +## التطبيقات + +استخدم نمط API Gateway عندما + +* تكون تستخدم بنية ميكروسيرفيسز وتحتاج إلى نقطة تجميع واحدة لاستدعاءات الميكروسيرفيسز. + +## الدروس التعليمية + +* [Exploring the New Spring Cloud Gateway](https://www.baeldung.com/spring-cloud-gateway) +* [Spring Cloud - Gateway](https://www.tutorialspoint.com/spring_cloud/spring_cloud_gateway.htm) +* [Getting Started With Spring Cloud Gateway](https://dzone.com/articles/getting-started-with-spring-cloud-gateway) + +## الشكر + +* [microservices.io - API Gateway](http://microservices.io/patterns/apigateway.html) +* [NGINX - Building Microservices: Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) +* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=ac7b6a57f866ac006a309d9086e8cfbd) +* [Building Microservices: Designing Fine-Grained Systems](https://www.amazon.com/gp/product/1491950358/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1491950358&linkId=4c95ca9831e05e3f0dadb08841d77bf1) diff --git a/api-gateway/etc/api-gateway.png b/localization/ar/api-gateway/etc/api-gateway.png similarity index 100% rename from api-gateway/etc/api-gateway.png rename to localization/ar/api-gateway/etc/api-gateway.png diff --git a/localization/ar/arrange-act-assert/README.md b/localization/ar/arrange-act-assert/README.md new file mode 100644 index 000000000000..ed99bb686a7f --- /dev/null +++ b/localization/ar/arrange-act-assert/README.md @@ -0,0 +1,142 @@ +--- +title: Arrange/Act/Assert +shortTitle: Arrange/Act/Assert +category: Idiom +language: ar +tag: + - Testing +--- + +## أيضا معروف بـ + +Dado/Cuando/Entonces + +## الغرض + +Arrange/Act/Assert (AAA) هو نمط لتنظيم اختبارات الوحدة. +يقسم اختبارات الوحدة إلى ثلاث خطوات واضحة ومميزة: + +1. Arrange(تنظيم): قم بإعداد التهيئة والتهيئة اللازمة للاختبار. +2. Act(العمل): اتخذ التدابير اللازمة للاختبار. +3. Assert(التأكيد): تحقق من نتائج الاختبار. + +## الشرح + +لهذا النمط العديد من الفوائد الكبيرة. فهو يخلق فصلًا واضحًا بين إعدادات الاختبار، العمليات والنتائج. هذا الهيكل يجعل الكود أسهل في القراءة والفهم. إذا +وضعت الخطوات بالترتيب وصيغت الكود بطريقة تفصلها، يمكنك مسح الاختبار وفهمه بسرعة. + +كما أنه يفرض درجة معينة من الانضباط عند كتابة اختبارات الوحدة. يجب أن تكون قادرًا على تصور +بوضوح الخطوات الثلاث التي سيقوم بها اختبارك. هذا يجعل الاختبارات أكثر بديهية للكتابة مع الحفاظ على هيكل واضح. + +مثال يومي + +> نحتاج إلى كتابة مجموعة من اختبارات الوحدة كاملة وواضحة لفئة. + +بعبارة أخرى + +> Arrange/Act/Assert هو نمط اختبار ينظم الاختبارات إلى ثلاث خطوات واضحة لتسهيل +> الصيانة. + +WikiWikiWeb يقول + +> Arrange/Act/Assert هو نمط لتنظيم وتنسيق الكود في طرق اختبار الوحدة. + +**كود المثال** + +لنلقِ نظرة أولاً على فئتنا `Cash` التي سيتم اختبارها. + + +```java +public class Cash { + + 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; + } + } + + int count() { + return amount; + } +} +``` + +ثم نكتب اختبارات الوحدة الخاصة بنا بناءً على نمط Arrange/Act/Assert. لاحظ بوضوح الفصل بين الخطوات لكل اختبار وحدة. + + +```java +class CashAAATest { + + @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 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()); + } +} +``` + +## القابلية للتطبيق + +استخدم نمط Arrange/Act/Assert عندما + +* تحتاج إلى تنظيم اختبارات الوحدة الخاصة بك لتكون أسهل في القراءة والصيانة والتحسين. + +## الشكر + +* [Arrange, Act, Assert: ¿Qué son las pruebas AAA?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) +* [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/) +* [Martin Fowler: DadoCuandoEntonces](https://martinfowler.com/bliki/GivenWhenThen.html) +* [Patrones de prueba xUnit: Refactorizando Código de prueba](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f63d0bcf50) +* [Principios, prácticas y patrones UnitTesting](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cfae3a5aaccae3a5a) +* [Desarrollo basado en pruebas: Ejemplo](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b47caef50875) diff --git a/localization/ar/async-method-invocation/README.md b/localization/ar/async-method-invocation/README.md new file mode 100644 index 000000000000..05173d7d6104 --- /dev/null +++ b/localization/ar/async-method-invocation/README.md @@ -0,0 +1,178 @@ +--- +title: Async Method Invocation +shortTitle: Async Method Invocation +category: Concurrency +language: ar +tag: + - Reactive +--- + +## الغرض + +الاستدعاء غير المتزامن للطرق (invocación de método asincrónico) هو نمط حيث لا يتم حظر الخيط أو العملية التي تستدعي الطريقة أثناء انتظار النتائج. يوفر النمط معالجة متوازية للمهام المستقلة المتعددة ويسترجع النتائج عبر +التعريفات الراجعة (callbacks) أو بالانتظار حتى ينتهي الإجراء. + +## الشرح + +مثال يومي + +> إطلاق الصواريخ الفضائية هو عمل مثير. يعطي قائد المهمة أمر الإطلاق و +> بعد فترة غير محددة من الوقت، يتم إطلاق الصاروخ بنجاح أو يفشل بشكل كارثي. + +بعبارة أخرى + +> يستدعي الاستدعاء غير المتزامن للطرق الإجراء ويعود فورًا قبل أن تنتهي المهمة +> يتم إرجاع نتائج الإجراء لاحقًا إلى الاستدعاء (callback). + +حسب ويكيبيديا + +> في البرمجة متعددة العمليات، يعتبر استدعاء الطريقة غير المتزامن (AMI)، المعروف أيضًا بـ +> الاستدعاءات غير المتزامنة أو النمط غير المتزامن هو نمط تصميم حيث لا يتم حظر مكان الاستدعاء +> أثناء انتظار انتهاء الكود المستدعى. بدلاً من ذلك، يتم إخطار خيط الاستدعاء عندما تصل الاستجابة. الاستطلاع للحصول على إجابة هو خيار غير مرغوب فيه. + +**مثال برمجي** + +في هذا المثال، نحن نطلق صواريخ فضائية وننشر مركبات قمرية. + +تُظهر التطبيق ما يفعله نمط الاستدعاء غير المتزامن للطريقة. الأجزاء الرئيسية للنمط هي +`AsyncResult` الذي يعد حاوية وسيطة لقيمة تم تقييمها بشكل غير متزامن، +`AsyncCallback` الذي يمكن توفيره ليتم تنفيذه عند اكتمال المهمة و `AsyncExecutor` الذي +يدير تنفيذ المهام غير المتزامنة. + + +```java +public interface AsyncResult { + boolean isCompleted(); + T getValue() throws ExecutionException; + void await() throws InterruptedException; +} +``` + +```java +public interface AsyncCallback { + void onComplete(T value, Optional ex); +} +``` + +```java +public interface AsyncExecutor { + AsyncResult startProcess(Callable task); + AsyncResult startProcess(Callable task, AsyncCallback callback); + T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; +} +``` + +`ThreadAsyncExecutor` هو تنفيذ لـ `AsyncExecutor`. يتم تسليط الضوء على بعض من أجزائه الرئيسية أدناه. + + +```java +public class ThreadAsyncExecutor implements AsyncExecutor { + + @Override + public AsyncResult startProcess(Callable task) { + return startProcess(task, null); + } + + @Override + public AsyncResult startProcess(Callable task, AsyncCallback 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 endProcess(AsyncResult asyncResult) + throws ExecutionException, InterruptedException { + if (!asyncResult.isCompleted()) { + asyncResult.await(); + } + return asyncResult.getValue(); + } +} +``` + +الآن كل شيء جاهز لإطلاق بعض الصواريخ لرؤية كيف يعمل كل شيء. + + +```java +public static void main(String[] args) throws Exception { + // الآن كل شيء جاهز لإطلاق بعض الصواريخ لرؤية كيف يعمل كل شيء. + + var executor = new ThreadAsyncExecutor(); + + // يبدأ بعض المهام غير المتزامنة بأوقات معالجة مختلفة، اثنان من الأخيرين مع محركات استرجاع (callback handlers) + + final var asyncResult1 = executor.startProcess(lazyval(10, 500)); + 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("Desplegando el rover lunar")); + final var asyncResult5 = + executor.startProcess(lazyval("devolución de llamada callback", 600), callback("Desplegando el rover lunar")); + + // يحاكي المعالجة في الخيط أو العملية الحالية بينما يتم تنفيذ المهام غير المتزامنة في خيوط أو عملياتها الخاصة + + Subproceso.dormir(350); // هيه، نحن نعمل بجد هنا + + log("قائد المهمة يشرب القهوة +"); + + // ينتظر حتى اكتمال المهام + + final var result1 = executor.endProcess(asyncResult1); + final var result2 = executor.endProcess(asyncResult2); + final var result3 = executor.endProcess(asyncResult3); + asyncResult4.await(); + asyncResult5.await(); + +// يسجل نتائج المهام، يتم تسجيل الاسترجاعات فور اكتمالها +log("الصاروخ الفضائي <" + resultado1 + "> أكمل إطلاقه"); +log("الصاروخ الفضائي <" + resultado2 + "> أكمل إطلاقه"); +log("الصاروخ الفضائي <" + result3 + "> أكمل إطلاقه"); + +} +``` + +Here is the output from the program console. + +```java +21:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي تم إطلاقه بنجاح +21:47:08.269 [main] INFO com.iluwatar.async.method.invocation.App - قائد المهمة يشرب القهوة +21:47:08.318 [executor-4] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي <20> تم إطلاقه بنجاح +21:47:08.335 [executor-4] INFO com.iluwatar.async.method.invocation.App - نشر الروفر القمري <20> +21:47:08.414 [executor-1] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي <10> تم إطلاقه بنجاح +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي تم إطلاقه بنجاح +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - تنفيذ المركبة القمرية +21:47:08.616 [executor-3] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي <50> تم إطلاقه بنجاح +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - إطلاق الصاروخ الفضائي <10> تم +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - إطلاق الصاروخ الفضائي تم +21:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - إطلاق الصاروخ الفضائي <50> تم + +``` + +# مخطط الفئة + +![texto alternativo](./etc/async-method-invocation.png "Invocación de método asíncrono") + +## القابلية للتطبيق + +استخدم نمط استدعاء الطريقة غير المتزامنة عندما + +* يكون لديك مهام مستقلة متعددة يمكن تنفيذها بشكل متوازٍ +* تحتاج إلى تحسين أداء مجموعة من المهام المتسلسلة +* لديك قدرة معالجة محدودة أو مهام تنفيذ طويلة الأمد ولا ينبغي أن تنتظر الدعوة حتى تصبح المهام جاهزة + +## أمثلة يومية + +* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html) +* [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) +* [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) +* [Patrón asíncrono basado en tareas](https://msdn.microsoft.com/en-us/library/hh873175.aspx) diff --git a/localization/ar/async-method-invocation/etc/async-method-invocation.png b/localization/ar/async-method-invocation/etc/async-method-invocation.png new file mode 100644 index 000000000000..764895d7a217 Binary files /dev/null and b/localization/ar/async-method-invocation/etc/async-method-invocation.png differ diff --git a/localization/ar/balking/README.md b/localization/ar/balking/README.md new file mode 100644 index 000000000000..c43480d580bd --- /dev/null +++ b/localization/ar/balking/README.md @@ -0,0 +1,135 @@ +--- +title: Balking +shortTitle: Balking +category: Concurrency +language: ar +tag: + - Decoupling +--- + +## الغرض + +يتم استخدام نمط _Balking_ لمنع كائن من تنفيذ كود معين إذا كان في حالة غير مكتملة أو غير مناسبة. + +## الشرح + +مثال من الحياة الواقعية + +> في غسالة الملابس هناك زر بدء لتشغيل غسل الملابس. عندما تكون الغسالة غير نشطة، يعمل الزر كما هو متوقع، ولكن إذا كانت الغسالة تغسل بالفعل، فإن الزر لا يفعل شيئًا. + +بمعنى آخر + +> باستخدام نمط _Balking_، يتم تنفيذ كود معين فقط إذا كان الكائن في حالة معينة. + +تقول ويكيبيديا + +> نمط _Balking_ هو نمط تصميم برمجي ينفذ إجراء على كائن فقط عندما يكون الكائن في حالة معينة. على سبيل المثال، إذا كان الكائن يقرأ ملفات ZIP واستدعى أسلوب _get_ على الكائن عندما لا يكون الملف ZIP مفتوحًا، فإن الكائن "يرفض" (_balk_) الطلب. + +**مثال برمجي** + +في هذا المثال من التنفيذ، `WashingMachine` هو كائن له حالتان يمكن أن يكونا: _ENABLED_ و _WASHING_ (مفعل و يغسل على التوالي). إذا كانت الغسالة في حالة _ENABLED_، فإن الحالة تتغير إلى _WASHING_ باستخدام طريقة آمنة ضد الخيوط (thread-safe). من ناحية أخرى، إذا كانت الغسالة بالفعل تغسل وأي خيط آخر ينفذ `wash()`، فلن يتم تغيير الحالة وتنتهي تنفيذ الطريقة دون القيام بأي شيء. + +إليك الأجزاء ذات الصلة من فئة `WashingMachine`. + + +```java +@Slf4j +public class WashingMachine { + + 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; + } + 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()); + } +} +``` + +هنا الواجهة البسيطة `DelayProvider` المستخدمة من قبل `WashingMachine`. + + +```java +public interface DelayProvider { + void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task); +} +``` + +الآن نقدم التطبيق باستخدام `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(); + } +} +``` + +إليك مخرجات التطبيق في وحدة التحكم. + + +``` +14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED +14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing +14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING +14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! +14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING +14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! +14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed. +``` + +## مخطط الفئات + + +![alt text](./etc/balking.png "Balking") + +## القابلية للتطبيق + +استخدم نمط _Balking_ عندما + +* يجب على كائن تنفيذ كود معين فقط عندما يكون في حالة معينة. +* الكائنات في حالة معرضة للتوقف مؤقتًا، ولكن لفترة زمنية غير محددة. + +## الأنماط ذات الصلة + + +* [Guarded Suspension Pattern](https://java-design-patterns.com/patterns/guarded-suspension/) +* [Double Checked Locking Pattern](https://java-design-patterns.com/patterns/double-checked-locking/) + +## المراجع + + +* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, 2nd Edition, Volume 1](https://www.amazon.com/gp/product/0471227293/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0471227293&linkId=0e39a59ffaab93fb476036fecb637b99) diff --git a/localization/ar/balking/etc/balking.png b/localization/ar/balking/etc/balking.png new file mode 100644 index 000000000000..f409eaacbb95 Binary files /dev/null and b/localization/ar/balking/etc/balking.png differ diff --git a/localization/ar/bridge/README.md b/localization/ar/bridge/README.md new file mode 100644 index 000000000000..2c47f65ea747 --- /dev/null +++ b/localization/ar/bridge/README.md @@ -0,0 +1,216 @@ +--- +title: Bridge +shortTitle: Bridge +category: Structural +language: ar +tag: + - Gang of Four +--- + +## أيضًا معروف باسم + +Handle/Body + +## الهدف + +فصل التجريد عن تنفيذه بحيث يمكن لكل منهما التغيير بشكل مستقل. + +## الشرح + +مثال من الحياة الواقعية + +> تخيل أن لديك سلاحًا مع تعاويذ مختلفة، ومن المفترض أن تسمح بخلط أسلحة مختلفة مع تعاويذ مختلفة. ماذا ستفعل؟ هل ستقوم بإنشاء نسخ متعددة من كل سلاح لكل تعويذة من التعاويذ أو ببساطة تقوم بإنشاء تعويذة منفصلة وتحددها للسلاح حسب الحاجة؟ نمط Bridge يتيح لك القيام بالأمر الثاني. + +ببساطة + +> يتعلق نمط Bridge بتفضيل التركيب على الوراثة. يتم دفع تفاصيل التنفيذ من هرم إلى كائن آخر مع هرم منفصل. + +تقول ويكيبيديا + +> نمط Bridge هو نمط تصميم يستخدم في هندسة البرمجيات يهدف إلى "فصل التجريد عن تنفيذه بحيث يمكن لكل منهما التغيير بشكل مستقل." + +**مثال برمجي** + +نقلًا لمثال السلاح المذكور أعلاه. هنا لدينا واجهة السلاح `Weapon`: + + +```java +public interface Weapon { + void wield(); + void swing(); + void unwield(); + Enchantment getEnchantment(); +} + +public class Sword implements Weapon { + + private final Enchantment enchantment; + + public Sword(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The sword is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The sword is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The sword is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} + +public class Hammer implements Weapon { + + private final Enchantment enchantment; + + public Hammer(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The hammer is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The hammer is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The hammer is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} +``` + +إليك واجهة التعاويذ `Enchantment` المنفصلة: + + +```java +public interface Enchantment { + void onActivate(); + void apply(); + void onDeactivate(); +} + +public class FlyingEnchantment implements Enchantment { + + @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 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 apply() { + LOGGER.info("The item eats the soul of enemies."); + } + + @Override + public void onDeactivate() { + LOGGER.info("Bloodlust slowly disappears."); + } +} +``` + +إليك كلا الواجهتين في العمل: + + +```java +LOGGER.info("The knight receives an enchanted sword."); +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(); +``` + +إليك مخرجات التطبيق في وحدة التحكم. + + +``` +The knight receives an enchanted sword. +The sword is wielded. +The item spreads bloodlust. +The sword is swung. +The item eats the soul of enemies. +The sword is unwielded. +Bloodlust slowly disappears. +The valkyrie receives an enchanted hammer. +The hammer is wielded. +The item begins to glow faintly. +The hammer is swung. +The item flies and strikes the enemies finally returning to owner's hand. +The hammer is unwielded. +The item's glow fades. +``` + +## مخطط الفئات + + +![alt text](./etc/bridge.urm.png "Bridge diagrama de clases") + +## القابلية للتطبيق + +استخدم نمط Bridge عندما + +* ترغب في تجنب الربط الدائم بين التجريد وتنفيذه. قد يكون هذا هو الحال، على سبيل المثال، عندما يجب اختيار أو تغيير التنفيذ في وقت التشغيل. +* يجب أن تكون كل من التجريدات وتنفيذاتها قابلة للتوسيع عبر الوراثة. في هذه الحالة، يتيح لك نمط Bridge دمج التجريدات والتنفيذات المختلفة وتوسيعها بشكل مستقل. +* يجب ألا تؤثر التغييرات في تنفيذ التجريد على العملاء؛ أي أنه لا يجب أن يحتاج الكود الخاص بهم إلى إعادة تجميع. +* لديك تكاثر في الفئات. تشير مثل هذه الهرميات إلى الحاجة إلى تقسيم كائن إلى جزئين. يستخدم Rumbaugh مصطلح "التعميمات المتداخلة" للإشارة إلى مثل هذه الهرميات من الفئات. +* ترغب في مشاركة تنفيذ بين عدة كائنات (ربما باستخدام عد مرجعي)، ويجب إخفاء هذه الحقيقة عن العميل. مثال بسيط هو فئة String لـ Coplien، حيث يمكن لعدة كائنات مشاركة نفس تمثيل السلسلة. + +## الدروس التعليمية + +* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java) + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/ar/bridge/etc/bridge.urm.png b/localization/ar/bridge/etc/bridge.urm.png new file mode 100644 index 000000000000..785585bf8163 Binary files /dev/null and b/localization/ar/bridge/etc/bridge.urm.png differ diff --git a/localization/ar/builder/README.md b/localization/ar/builder/README.md new file mode 100644 index 000000000000..1edfb4b826db --- /dev/null +++ b/localization/ar/builder/README.md @@ -0,0 +1,147 @@ +--- +title: Builder +shortTitle: Builder +category: Creational +language: ar +tag: + - Gang of Four +--- + +## الهدف + +فصل بناء كائن معقد عن تمثيله بحيث يمكن لنفس عملية البناء إنشاء تمثيلات مختلفة. + +## الشرح + +مثال من الحياة الواقعية + +> تخيل مولد شخصيات للعبة تقمص أدوار. الخيار الأسهل هو السماح للكمبيوتر بإنشاء الشخصية نيابة عنك. إذا أردت تحديد تفاصيل الشخصية يدويًا مثل المهنة، الجنس، لون الشعر، إلخ، فإن إنشاء الشخصية يصبح عملية خطوة بخطوة تكتمل عندما تكون جميع الاختيارات جاهزة. + +ببساطة + +> يسمح بإنشاء نكهات مختلفة من كائن دون تلويث الباني. مفيد عندما يمكن أن يكون هناك عدة نكهات لكائن ما، أو عندما تكون هناك العديد من الخطوات المعنية في إنشاء الكائن. + +تقول ويكيبيديا + +> نمط البناء هو نمط تصميم برمجي لإنشاء الكائنات بهدف إيجاد حل لمضاد النمط الخاص بالباني المنطقي. + +مع ذلك، دعني أضيف بعض المعلومات حول ما هو مضاد النمط للباني المنطقي. في وقت ما أو آخر، رأينا جميعًا بانيًا مثل التالي: + + +```java +public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { +} +``` + +كما ترى، قد يخرج عدد معلمات الباني عن السيطرة بسرعة، وقد يصبح من الصعب فهم ترتيب المعلمات. بالإضافة إلى ذلك، قد تستمر هذه القائمة في النمو إذا أردت إضافة المزيد من الخيارات في المستقبل. يسمى هذا مضاد النمط للباني المنطقي. + +**مثال برمجي** + +البديل الحكيم هو استخدام نمط Builder. أولاً، لدينا بطلنا `Hero` الذي نريد إنشائه: + + +```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 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; + } +} +``` + +ثم لدينا الباني: + + +```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; + + 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; + } + + public Builder withHairType(HairType hairType) { + this.hairType = hairType; + return this; + } + + public Builder withHairColor(HairColor hairColor) { + this.hairColor = hairColor; + return this; + } + + public Builder withArmor(Armor armor) { + this.armor = armor; + return this; + } + + public Builder withWeapon(Weapon weapon) { + this.weapon = weapon; + return this; + } + + public Hero build() { + return new Hero(this); + } + } +``` + +إذن يمكن استخدامه كما يلي: + + +```java +var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); +``` + +## مخطط الفئات + +![alt text](./etc/builder.urm.png "مخطط فئات Builder") + +## القابلية للتطبيق + +استخدم نمط Builder عندما + +* يجب أن يكون الخوارزمية لإنشاء كائن معقدة مستقلة عن الأجزاء التي تتكون منها الكائن وكيفية تجميعها. +* يجب أن تسمح عملية البناء بتمثيلات مختلفة للكائن الذي يتم بناؤه. + +## الدروس التعليمية + +* [Refactoring Guru](https://refactoring.guru/design-patterns/builder) +* [مدونة Oracle](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) +* [Journal Dev](https://www.journaldev.com/1425/builder-design-pattern-in-java) + +## الاستخدامات في العالم الواقعي + +* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) +* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) بالإضافة إلى غيرها من المخازن المؤقتة مثل FloatBuffer و IntBuffer، إلخ. +* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) +* جميع التطبيقات من [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) +* [بناة Apache Camel](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/ar/builder/etc/builder.urm.png b/localization/ar/builder/etc/builder.urm.png new file mode 100644 index 000000000000..d77808d36097 Binary files /dev/null and b/localization/ar/builder/etc/builder.urm.png differ diff --git a/localization/ar/business-delegate/README.md b/localization/ar/business-delegate/README.md new file mode 100644 index 000000000000..13fe05b32db3 --- /dev/null +++ b/localization/ar/business-delegate/README.md @@ -0,0 +1,194 @@ +--- +title: Business Delegate +shortTitle: Business Delegate +category: Structural +language: ar +tag: + - Decoupling +--- + +## الغرض + +نمط **Business Delegate** يضيف طبقة من التجريد بين مستويات العرض والأعمال. باستخدام هذا النمط، نحقق ارتباطًا مرنًا بين المستويات ونعزل المعرفة حول كيفية تحديد المواقع والاتصال والتفاعل مع الكائنات التجارية التي تشكل التطبيق. + +## أيضًا معروف باسم + +مُمثل الخدمة + +## الشرح + +مثال من العالم الحقيقي + +> تطبيق للهواتف المحمولة يعد ببث أي فيلم موجود إلى جهازك. يقوم التطبيق بالتقاط سلسلة البحث من المستخدم ويمررها إلى **Business Delegate**. يقوم **Business Delegate** باختيار خدمة البث الأكثر ملاءمة ويبدأ في تشغيل الفيديو. + +بكلمات أبسط + +> يضيف **Business Delegate** طبقة من التجريد بين مستويات العرض والأعمال. + +تقول ويكيبيديا + +> **Business Delegate** هو نمط تصميم في Java EE. هذا النمط يهدف إلى تقليل الترابط بين خدمات الأعمال ومستوى العرض المتصل، وإخفاء تفاصيل التنفيذ الخاصة بالخدمات (بما في ذلك البحث والوصول إلى بنية EJB). يعمل **Business Delegate** كـ **مهايئ** لاستدعاء كائنات الأعمال من طبقة العرض. + +**مثال برمجي** + +أولاً، لدينا تجريد لخدمات البث عبر الفيديو `VideoStreamingService` مع زوج من التطبيقات `NetflixService` و `YouTubeService`. + + +```java +public interface VideoStreamingService { + void doProcessing(); +} + +@Slf4j +public class NetflixService implements VideoStreamingService { + @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"); + } +} +``` + +التالي، لدينا خدمة البحث `BusinessLookup` التي تقرر أي خدمة بث الفيديو يجب استخدامها. + + +```java + +@Setter +public class BusinessLookup { + + private NetflixService netflixService; + private YouTubeService youTubeService; + + public VideoStreamingService getBusinessService(String movie) { + if (movie.toLowerCase(Locale.ROOT).contains("die hard")) { + return netflixService; + } else { + return youTubeService; + } + } +} +``` + +يستخدم **Delegado de Negocio** `BusinessDelegate` بحث الأعمال لتوجيه طلبات تشغيل الأفلام إلى خدمة بث الفيديو المناسبة. + + +```java + +@Setter +public class BusinessDelegate { + + private BusinessLookup lookupService; + + public void playbackMovie(String movie) { + VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie); + videoStreamingService.doProcessing(); + } +} +``` + +العميل المحمول `MobileClient` يستخدم **Business Delegate** لاستدعاء مستوى الأعمال. + + +```java +public class MobileClient { + + private final BusinessDelegate businessDelegate; + + public MobileClient(BusinessDelegate businessDelegate) { + this.businessDelegate = businessDelegate; + } + + public void playbackMovie(String movie) { + businessDelegate.playbackMovie(movie); + } +} +``` + +أخيرًا، يمكننا عرض المثال الكامل أثناء التنفيذ. + + +```java + public static void main(String[]args){ + + // preparar los objetos + var businessDelegate=new BusinessDelegate(); + var businessLookup=new BusinessLookup(); + businessLookup.setNetflixService(new NetflixService()); + businessLookup.setYouTubeService(new YouTubeService()); + businessDelegate.setLookupService(businessLookup); + + // crear el cliente y utilizar el Business Delegate + var client=new MobileClient(businessDelegate); + client.playbackMovie("Die Hard 2"); + client.playbackMovie("Maradona: The Greatest Ever"); + } +``` + +إليك مخرجات وحدة التحكم. + + +``` +21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing +21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing +``` + +## مخطط الفئات + +![مخطط الفئات](./etc/business-delegate.urm.png "Business Delegate") + +## الأنماط ذات الصلة + +* [نمط تحديد الموقع للخدمات](https://java-design-patterns.com/patterns/service-locator/) + +## القابلية للتطبيق + +استخدم نمط Business Delegate عندما + +* ترغب في تقليل الترابط بين مستويات العرض والأعمال. +* ترغب في تنسيق المكالمات إلى خدمات أعمال متعددة. +* ترغب في تجميع عمليات البحث والمكالمات إلى الخدمات. +* من الضروري تجريد وتغليف الاتصال بين طبقة العميل وخدمات الأعمال. + +## دروس + +* [نمط Business Delegate في TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) + +## الاستخدامات المعروفة + +* التطبيقات المؤسسية التي تستخدم Java EE (Java Platform, Enterprise Edition) +* التطبيقات التي تتطلب الوصول عن بُعد إلى خدمات الأعمال + +## العواقب + +الفوائد: + +* فك الترابط بين مستويات العرض والأعمال: يسمح بمواصلة تطور مستوى العميل والخدمات المؤسسية بشكل مستقل. +* شفافية الموقع: لا يتأثر العملاء بتغييرات الموقع أو التهيئة لخدمات الأعمال. +* إعادة الاستخدام وقابلية التوسع: يمكن إعادة استخدام كائنات Business Delegate بواسطة عملاء متعددين، ويدعم النمط التوازن في الحمل وقابلية التوسع. + +العيوب: + +* التعقيد: يضيف طبقات وتجريدات إضافية قد تزيد من التعقيد. +* تحميل الأداء: قد يؤدي الإشارة الإضافية إلى خفض طفيف في الأداء. + +## الأنماط ذات الصلة + +* [محدد خدمات](https://java-design-patterns.com/patterns/service-locator/): يستخدم Delegado de Negocio ( + Business Delegate) محدد خدمات (Service Locator) للعثور على خدمات الأعمال. +* [واجهة الجلسة](https://java-design-patterns.com/patterns/session-facade/): يمكن لـ Delegado de Negocio (Business + Delegate) استخدام واجهة الجلسة (Session Facade) لتوفير واجهة موحدة لمجموعة من خدمات الأعمال. +* [كائن مركب](https://java-design-patterns.com/patterns/composite-entity/): يمكن لـ Delegado de Negocio (Business Delegate) + استخدام الكائن المركب (Composite Entity) لإدارة حالة خدمات الأعمال. + +## الشكر + +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947) diff --git a/localization/ar/business-delegate/etc/business-delegate.urm.png b/localization/ar/business-delegate/etc/business-delegate.urm.png new file mode 100644 index 000000000000..4dca6c263b99 Binary files /dev/null and b/localization/ar/business-delegate/etc/business-delegate.urm.png differ diff --git a/localization/ar/bytecode/README.md b/localization/ar/bytecode/README.md new file mode 100644 index 000000000000..d5c046b4fccf --- /dev/null +++ b/localization/ar/bytecode/README.md @@ -0,0 +1,264 @@ +--- +title: Bytecode +shortTitle: Bytecode +category: Behavioral +language: ar +tag: + - Game programming +--- + +## الغرض + +يسمح بترميز السلوك كتعليمات لجهاز افتراضي. + +## الشرح + +مثال من العالم الواقعي + +> فريق يعمل على لعبة جديدة حيث يتقاتل السحرة مع بعضهم البعض. يحتاج سلوك السحرة إلى تعديل دقيق وتجربة مئات المرات من خلال اختبارات اللعبة. ليس من المثالي أن يطلب من المبرمج إجراء تغييرات في كل مرة يريد فيها مصمم اللعبة تعديل السلوك، لذلك يتم تنفيذ سلوك الساحر كجهاز افتراضي يعتمد على البيانات. + +بكلمات بسيطة + +> نمط Bytecode يسمح بسلوك موجه بالبيانات بدلاً من الكود. + +[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) يوضح الوثائق: + +> مجموعة من التعليمات تحدد العمليات منخفضة المستوى التي يمكن تنفيذها. يتم ترميز سلسلة من التعليمات كدورة من البايتات. يقوم الجهاز الافتراضي بتنفيذ هذه التعليمات واحدة تلو الأخرى، باستخدام مكدس للقيم الوسيطة. يسمح الجمع بين التعليمات بتعريف سلوكيات معقدة وعالية المستوى. + +**مثال برمجي** + +أحد الكائنات الأكثر أهمية في اللعبة هو فئة ماغو `Wizard`. + + +```java + +@AllArgsConstructor +@Setter +@Getter +@Slf4j +public class Wizard { + + 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 spawnParticles() { + LOGGER.info("Spawning particles"); + numberOfSpawnedParticles++; + } +} +``` + +بعد ذلك، نعرض التعليمات المتاحة لجهازنا الافتراضي. لكل تعليمات دلالتها الخاصة حول كيفية التعامل مع بيانات المكدس. على سبيل المثال، تقوم التعليمة ADD بأخذ العنصرين العلويين من المكدس، وتجمعهما، ثم تضع النتيجة في المكدس. + + +```java + +@AllArgsConstructor +@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 + // ... +} +``` + +في قلب مثالنا توجد فئة `VirtualMachine`. تأخذ التعليمات كمدخلات وتنفذها لتوفير سلوك كائن اللعبة. + + +```java + +@Getter +@Slf4j +public class VirtualMachine { + + private final Stack stack = new Stack<>(); + + 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 void setHealth(int wizard, int amount) { + wizards[wizard].setHealth(amount); + } + // other setters -> + // ... +} +``` + +الآن يمكننا عرض المثال الكامل باستخدام الآلة الافتراضية. + +```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)); + + 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")); + } +``` + +إليك مخرجات وحدة التحكم. + + +``` +16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0] +16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains [] +``` + +## مخطط الفئات + + +![alt text](./etc/bytecode.urm.png "Bytecode class diagram") + +## القابلية للتطبيق + + +## القابلية للتطبيق + +استخدم نمط Bytecode عندما تحتاج إلى تعريف العديد من السلوكيات ولغة تنفيذ لعبتك ليست مناسبة لأن: + +* إنها منخفضة المستوى جدًا، مما يجعل البرمجة مملة أو عرضة للأخطاء. +* التكرار فيها يستغرق وقتًا طويلًا بسبب بطء وقت الترجمة أو مشاكل أخرى في الأدوات. +* إنها تحتوي على ثقة مفرطة. إذا كنت ترغب في التأكد من أن السلوك المحدد لا يمكن أن يتسبب في كسر اللعبة، يجب أن تفصله عن باقي قاعدة الكود. + +## الاستخدامات المعروفة + +* تستخدم Java Virtual Machine (JVM) bytecode لتمكين برامج Java من العمل على أي جهاز يحتوي على JVM. +* تقوم Python بترجمة سكربتاتها إلى bytecode، الذي يتم تفسيره بعد ذلك بواسطة آلة افتراضية Python. +* يستخدم .NET Framework نوعًا من bytecode يسمى Microsoft Intermediate Language (MSIL). + +## العواقب + +### المزايا: + +* القابلية للنقل: يمكن تنفيذ البرامج على أي منصة تحتوي على آلة افتراضية متوافقة. +* الأمان: يمكن للآلة الافتراضية تطبيق ضوابط أمان على كود البايت. +* الأداء: يمكن للمترجمات JIT تحسين كود البايت في وقت التشغيل، مما يحسن الأداء المحتمل مقارنة بالكود المفسر. + +### العيوب: + +* الحمل الزائد: تنفيذ bytecode يتضمن عادةً مزيدًا من الحمل الزائد مقارنةً بتنفيذ الكود الأصلي، مما قد يؤثر على الأداء. +* التعقيد: تنفيذ وصيانة آلة افتراضية يضيف تعقيدًا للنظام. + +## الأنماط المرتبطة + +* [مترجم](https://java-design-patterns.com/patterns/interpreter/) يستخدم غالبًا داخل تنفيذ آلات افتراضية لتفسير تعليمات bytecode. +* [أمر](https://java-design-patterns.com/patterns/command/): يمكن اعتبار تعليمات bytecode أوامر يتم تنفيذها بواسطة الآلة الافتراضية. +* [طريقة المصنع](https://java-design-patterns.com/patterns/factory-method/): قد تستخدم الآلات الافتراضية طرق المصنع لإنشاء العمليات أو التعليمات المحددة في bytecode. + +## الشكر + +* [أنماط برمجة الألعاب](http://gameprogrammingpatterns.com/bytecode.html) +* [برمجة لغات البرمجة](https://amzn.to/49Tusnn) diff --git a/localization/ar/bytecode/etc/bytecode.urm.png b/localization/ar/bytecode/etc/bytecode.urm.png new file mode 100644 index 000000000000..51335fa0a4b4 Binary files /dev/null and b/localization/ar/bytecode/etc/bytecode.urm.png differ diff --git a/localization/ar/chain-of-responsibility/README.md b/localization/ar/chain-of-responsibility/README.md new file mode 100644 index 000000000000..f3cc1a2f3b20 --- /dev/null +++ b/localization/ar/chain-of-responsibility/README.md @@ -0,0 +1,207 @@ +--- +title: Chain of responsibility +shortTitle: Chain of responsibility +category: Behavioral +language: ar +tag: + - Gang of Four + - Decoupling +--- + +## أيضًا معروف بـ + +* سلسلة الأوامر +* سلسلة الكائنات +* سلسلة المسؤولية + +## الغرض + +يمنع ربط مُرسل الطلب بمستقبله من خلال إعطاء أكثر من كائن الفرصة لإدارة الطلب. يربط الكائنات المستقبلية معًا ويمرر الطلب عبر السلسلة حتى يتمكن أحد الكائنات من معالجته. + +## الشرح + +مثال من الحياة الواقعية + +> الملك الأورك يعطي أوامر بصوت عالٍ لجيشه. أقرب شخص للرد هو القائد، ثم الضابط، ثم الجندي. القائد، الضابط، والجندي يشكلون سلسلة من المسؤولية. + +بكلمات بسيطة + +> يساعد في بناء سلسلة من الكائنات. يدخل الطلب من طرف ويتنقل عبر كائنات متعددة حتى يجد مديرًا مناسبًا. + +تقول ويكيبيديا + +> في التصميم الموجه للكائنات، نمط سلسلة المسؤولية هو نمط تصميم يتكون من مصدر لأوامر الكائنات وسلسلة من كائنات المعالجة. يحتوي كل كائن معالجة على منطق يحدد أنواع أوامر الكائنات التي يمكنه التعامل معها؛ يتم تمرير البقية إلى كائن المعالجة التالي في السلسلة. + +**مثال برمجي** + +ترجمة لمثالنا مع الأورك أعلاه. أولًا، لدينا الكلاس `Request`: + + +```java +import lombok.Getter; + +@Getter +public class Request { + + 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 void markHandled() { + this.handled = true; + } + + @Override + public String toString() { + return getRequestDescription(); + } +} + +public enum RequestType { + DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX +} +``` + +أدناه، نعرض تسلسل هرم مدير الطلبات. + + +```java +public interface RequestHandler { + + boolean canHandleRequest(Request req); + + int getPriority(); + + void handle(Request req); + + String name(); +} + +@Slf4j +public class OrcCommander implements RequestHandler { + @Override + public boolean canHandleRequest(Request req) { + return req.getRequestType() == RequestType.DEFEND_CASTLE; + } + + @Override + public int getPriority() { + return 2; + } + + @Override + public void handle(Request req) { + req.markHandled(); + LOGGER.info("{} handling request \"{}\"", name(), req); + } + + @Override + public String name() { + return "Orc commander"; + } +} + +// يتم تعريف OrcOfficer و OrcSoldier بطريقة مشابهة لـ OrcCommander + + +``` + +الملك أورك يعطي الأوامر ويشكل السلسلة. + + +```java +public class OrcKing { + + private List handlers; + + public OrcKing() { + buildChain(); + } + + 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)); + } +} +``` + +سلسلة المسؤولية في العمل. + + +```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")); +``` + +إخراج وحدة التحكم. + + +``` +Orc commander handling request "defend castle" +Orc officer handling request "torture prisoner" +Orc soldier handling request "collect tax" +``` + +## مخطط الفئات + +![alt text](./etc/chain-of-responsibility.urm.png "مخطط الفئات لسلسلة المسؤولية") + +## التطبيقية + +استخدم سلسلة المسؤولية عندما + +* يمكن لعدة كائنات معالجة الطلب، ولا يتم التعرف على المعالج مسبقًا. يجب تحديد المعالج تلقائيًا. +* ترغب في إرسال طلب إلى أحد الكائنات دون تحديد المستقبل بشكل صريح. +* يجب تحديد مجموعة الكائنات التي يمكنها معالجة الطلب ديناميكيًا. + +## الاستخدامات المعروفة + +* التفاعل مع الأحداث في إطارات واجهات المستخدم الرسومية حيث يمكن معالجة الحدث في عدة مستويات من تسلسل مكونات واجهة المستخدم. +* إطارات عمل الوسطاء حيث يمر الطلب عبر سلسلة من كائنات المعالجة. +* أنظمة السجلات حيث يمكن أن تمر الرسائل عبر سلسلة من المسجلين، مع إمكانية معالجتها بطرق مختلفة. +* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) +* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) +* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) + +## العواقب + +المزايا: + +* تقليل الترابط. لا يحتاج مُرسل الطلب إلى معرفة المعالج المحدد الذي سيعالج الطلب. +* مرونة أكبر في تخصيص المسؤوليات للكائنات. يمكن إضافة أو تغيير المسؤوليات لإدارة الطلب عن طريق تغيير أعضاء وترتيب السلسلة. +* يتيح تعيين معالج افتراضي إذا لم يكن هناك معالج محدد يمكنه معالجة الطلب. + +العيوب: + +* قد يكون من الصعب تصحيح الأخطاء وفهم التدفق، خاصة إذا كانت السلسلة طويلة ومعقدة. +* قد يبقى الطلب دون معالجة إذا كانت السلسلة لا تحتوي على معالج "التقاط الكل". +* قد تنشأ مشكلات في الأداء بسبب إمكانية مرور الطلب عبر عدة معالجات قبل العثور على المعالج المناسب، أو عدم العثور عليه على الإطلاق. + +## الأنماط ذات الصلة + +* [الأمر](https://java-design-patterns.com/patterns/command/): يمكن استخدامه لتغليف طلب ككائن، يمكن تمريره عبر السلسلة. +* [التركيب](https://java-design-patterns.com/patterns/composite/): غالبًا ما يتم تطبيق نمط سلسلة المسؤولية مع نمط التركيب. +* [الزخرفة](https://java-design-patterns.com/patterns/decorator/): يمكن ربط الزخارف بشكل مشابه للمسؤوليات في نمط سلسلة المسؤولية. + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) +* [Pattern languages of program design 3](https://amzn.to/4a4NxTH) diff --git a/localization/ar/chain-of-responsibility/etc/chain-of-responsibility.urm.png b/localization/ar/chain-of-responsibility/etc/chain-of-responsibility.urm.png new file mode 100644 index 000000000000..af1bd105455b Binary files /dev/null and b/localization/ar/chain-of-responsibility/etc/chain-of-responsibility.urm.png differ diff --git a/localization/ar/client-session/README.md b/localization/ar/client-session/README.md new file mode 100644 index 000000000000..a6892e80b239 --- /dev/null +++ b/localization/ar/client-session/README.md @@ -0,0 +1,118 @@ +--- +title: Client Session +shortTitle: Client Session +category: Behavioral +language: ar +tags: + - Session management + - Web development +--- + +## أيضًا معروف باسم + +* جلسة المستخدم + +## الهدف + +يهدف نمط التصميم "جلسة العميل" إلى الحفاظ على حالة وبيانات المستخدم عبر طلبات متعددة ضمن تطبيق ويب، مما يضمن تجربة مستخدم مستمرة وشخصية. + +## الشرح + +مثال واقعي + +> ترغب في إنشاء تطبيق لإدارة البيانات يسمح للمستخدمين بإرسال طلبات إلى الخادم لتعديل وإجراء تغييرات على البيانات المخزنة على أجهزتهم. هذه الطلبات صغيرة والبيانات فردية لكل مستخدم، مما يلغي الحاجة إلى تنفيذ قاعدة بيانات واسعة النطاق. باستخدام نمط جلسة العميل، يمكن إدارة عدة طلبات في نفس الوقت، مع تحقيق توازن في تحميل العملاء عبر خوادم مختلفة بسهولة لأن الخوادم تظل بدون حالة. كما يتم القضاء على الحاجة إلى تخزين معرفات الجلسة على الخادم لأن العملاء يقدمون كل المعلومات التي يحتاجها الخادم لمعالجة طلباتهم. + +بإيجاز + +> بدلاً من تخزين معلومات عن العميل الحالي والمعلومات التي يتم الوصول إليها على الخادم، يتم الاحتفاظ بها فقط على جانب العميل. يجب على العميل إرسال بيانات الجلسة مع كل طلب إلى الخادم ويجب عليه إرسال حالة محدثة مرة أخرى إلى العميل، التي يتم تخزينها على جهاز العميل. لا يتعين على الخادم تخزين معلومات العميل. ([مرجع](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client)) + +**مثال برمجي** + +إليك كود مثال لوصف نمط جلسة العميل. في الكود التالي، نقوم أولاً بإنشاء مثيل للخادم. سيتم استخدام هذا المثيل بعد ذلك للحصول على كائنات الجلسة لعميلين. كما ترى في الكود التالي، يمكن استخدام كائن الجلسة لتخزين أي معلومات ذات صلة يحتاجها الخادم لمعالجة طلب العميل. سيتم تمرير هذه الكائنات مع كل طلب إلى الخادم. سيتضمن الطلب كائن الجلسة الذي يخزن التفاصيل ذات الصلة بالعميل مع البيانات المطلوبة لمعالجة الطلب. تساعد معلومات الجلسة في كل طلب الخادم في التعرف على العميل ومعالجة الطلب بناءً على ذلك. + + +```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); + } +} + +@Data +@AllArgsConstructor +public class Session { + + /** + * Session id. + */ + private String id; + + /** + * Client name. + */ + private String clientName; + +} + +@Data +@AllArgsConstructor +public class Request { + + private String data; + + private Session session; + +} +``` + +## مخطط الهيكلية + +![alt text](./etc/session_state_pattern.png "نمط حالة الجلسة") + +## قابلية التطبيق + +استخدم نمط حالة الجلسة عندما: + +* التطبيقات الويب التي تتطلب مصادقة وتفويض المستخدم. +* التطبيقات التي تحتاج إلى تتبع أنشطة وتفضيلات المستخدم عبر طلبات أو زيارات متعددة. +* الأنظمة التي يحتاج فيها موارد الخادم إلى التحسين عن طريق تحميل إدارة الحالة إلى جانب العميل. + +## الاستخدامات المعروفة + +* مواقع التجارة الإلكترونية لتتبع محتويات سلة التسوق عبر الجلسات. +* المنصات عبر الإنترنت التي تقدم محتوى مخصص بناءً على تفضيلات وسجل المستخدم. +* تطبيقات الويب التي تتطلب تسجيل دخول المستخدم للوصول إلى المحتوى المخصص أو الآمن. + +## العواقب + +الفوائد: + +* تحسين أداء الخادم من خلال تقليل الحاجة لتخزين حالة المستخدم على الخادم. +* تحسين تجربة المستخدم من خلال المحتوى المخصص والتنقل السلس عبر أجزاء التطبيق المختلفة. +* مرونة في إدارة الجلسات من خلال عدة آليات تخزين على جانب العميل (مثل الكوكيز، Web Storage API). + +العيوب: + +* مخاطر محتملة للأمان إذا تم تخزين معلومات حساسة في جلسات العميل دون التشفير والتحقق المناسب. +* الاعتماد على قدرات وضبط العميل، مثل سياسات الكوكيز التي قد تختلف حسب المتصفح وإعدادات المستخدم. +* زيادة التعقيد في منطق إدارة الجلسات، خاصة في إدارة انتهاء الصلاحية، التجديد ومزامنة الجلسات عبر الأجهزة أو النوافذ المتعددة. + +## الأنماط ذات الصلة + +* جلسة الخادم: غالباً ما يُستخدم جنباً إلى جنب مع نمط جلسة العميل لتوفير توازن بين كفاءة جانب العميل والتحكم في جانب الخادم. +* [سينجلتون](https://java-design-patterns.com/patterns/singleton/): ضمان وجود مثيل واحد فقط من جلسة المستخدم في التطبيق بأكمله. +* [حالة](https://java-design-patterns.com/patterns/state/): إدارة تحولات الحالة في الجلسة مثل الحالات المصادق عليها، الضيف أو المنتهية. + +## الفضل + +* [DZone - Practical PHP patterns](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client) +* [نمط حالة جلسة العميل - Ram N Java](https://www.youtube.com/watch?v=ycOSj9g41pc) +* [Java الاحترافي لتطبيقات الويب](https://amzn.to/4aazY59) +* [تأمين تطبيقات الويب باستخدام Spring Security](https://amzn.to/3PCCEA1) diff --git a/client-session/etc/session_state_pattern.png b/localization/ar/client-session/etc/session_state_pattern.png similarity index 100% rename from client-session/etc/session_state_pattern.png rename to localization/ar/client-session/etc/session_state_pattern.png diff --git a/localization/ar/collecting-parameter/README.md b/localization/ar/collecting-parameter/README.md new file mode 100644 index 000000000000..c799bb0223de --- /dev/null +++ b/localization/ar/collecting-parameter/README.md @@ -0,0 +1,222 @@ +--- +title: Collecting Parameter +shortTitle: Collecting Parameter +category: Behavioral +language: ar +tag: + - Accumulation + - Generic +--- + +## أيضًا يُعرف بـ + +* جامع +* مُجمّع + +## الهدف + +يهدف إلى تبسيط الطرق التي تجمع المعلومات من خلال تمرير كائن جمع واحد عبر عدة استدعاءات للطرق، مما يسمح لها بإضافة النتائج إلى هذه المجموعة بدلاً من أن يقوم كل طريقة بإنشاء مجموعة خاصة بها. + +## الشرح + +### مثال من العالم الحقيقي + +داخل مبنى تجاري كبير، توجد طابعات مُشتركة تعد مجموعة من جميع وظائف الطباعة المعلقة حاليًا. تحتوي الطوابق المختلفة على طرز مختلفة من الطابعات، ولكل منها سياسة طباعة مختلفة. يجب علينا بناء برنامج يمكنه إضافة مهام الطباعة المناسبة بشكل مستمر إلى مجموعة تسمى *معامل الجمع*. + +### ببساطة + +بدلاً من وجود دالة ضخمة تحتوي على العديد من السياسات لجمع المعلومات في متغير، يمكننا إنشاء العديد من الدوال الصغيرة التي تأخذ كل معلم، وتضيف معلومات جديدة. يمكننا تمرير المعامل إلى كل من هذه الدوال الصغيرة وفي النهاية، سيكون لدينا ما أردناه في الأصل. في هذه المرة، يكون الكود أنظف وأسهل في الفهم. نظرًا لأن الدالة الكبيرة قد تم تقسيمها، فإن الكود أيضًا أسهل في التعديل حيث يتم تحديد التغييرات في الدوال الصغيرة. + +### تقول ويكيبيديا + +في اصطلاح المعاملات الجامعية، يتم تمرير مجموعة (قائمة، خريطة، إلخ) بشكل متكرر كمعامل إلى دالة تضيف العناصر إلى المجموعة. + +### مثال برمجي + +بتشفير مثالنا السابق، يمكننا استخدام مجموعة `النتيجة` كمعامل جامع. يتم تنفيذ القيود التالية: + +- إذا كان ورق A4 ملونًا، يجب أن يكون أيضًا من جهة واحدة. يتم قبول جميع الأوراق غير الملونة الأخرى. +- لا يجب أن يكون ورق A3 ملونًا ويجب أن يكون من جهة واحدة. +- يجب أن يكون ورق A2 من صفحة واحدة، من جهة واحدة وغير ملون. + + +```java +package com.iluwatar.collectingparameter; + +import java.util.LinkedList; +import java.util.Queue; + +public class App { + static PrinterQueue printerQueue = PrinterQueue.getInstance(); + + /** + * 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)); + + /* + This variable is the collecting parameter. + */ + var result = new LinkedList(); + + /* + * Using numerous sub-methods to collaboratively add information to the result collecting parameter + */ + addA4Papers(result); + addA3Papers(result); + addA2Papers(result); + } +} +``` + +نستخدم الطرق `addA4Paper` و `addA3Paper` و `addA2Paper` لملء معامل الجمع `النتيجة` بالوظائف المناسبة للطباعة وفقًا للسياسة الموصوفة سابقًا. يتم ترميز السياسات الثلاث كما يلي: + + +```java +public class App { + 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 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); + } + } + } + } + + /** + * 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 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); + } + } + } + } + + /** + * 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 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); + } + } + } + } +} +``` + +كل طريقة تأخذ كمعامل معلمة جمع. بعد ذلك، تضيف العناصر، المأخوذة من متغير عالمي، إلى هذه المعلمة إذا كانت كل عنصر يفي بمعيار معين. يمكن أن تحتوي هذه الطرق على السياسة التي يرغب فيها العميل. + +في هذا المثال البرمجي، يتم إضافة ثلاث مهام طباعة إلى الطابور. فقط أول مهمتين للطباعة يجب إضافتهما إلى معلمة الجمع وفقًا للسياسة. العناصر في متغير `النتيجة` بعد التنفيذ هي: + +| حجم الورق | عدد الصفحات | مزدوج الوجه | ملون | +|-----------|-------------|-------------|------| +| A4 | 5 | false | false | +| A3 | 2 | false | false | + +وهذا هو ما توقعناه. + +## مخطط الفئات + +![alt text](./etc/collecting-parameter.urm.png "معامل الجمع") + +## قابلية التطبيق + +استخدم نمط التصميم جمع المعاملات عندما: + +- عندما تنتج عدة طرق مجموعة من النتائج وتريد إضافة هذه النتائج بطريقة موحدة. +- في السيناريوهات حيث يمكن أن يحسن تقليل عدد المجموعات التي يتم إنشاؤها بواسطة الطرق من كفاءة الذاكرة والأداء. +- عند إعادة هيكلة الطرق الكبيرة التي تقوم بعدة مهام، بما في ذلك جمع النتائج من عمليات متعددة. + +## الدروس التعليمية + +الدروس التعليمية لهذه الطريقة موجودة في: + +- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) بواسطة Joshua Kerivsky +- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) بواسطة Kent Beck + +## الاستخدامات المعروفة + +يوضح Joshua Kerivsky مثالًا واقعيًا في كتابه 'Refactoring to Patterns'. يقدم مثالًا لاستخدام نمط التصميم "جمع المعاملات" لإنشاء طريقة `toString()` لشجرة XML. بدون استخدام هذا النمط، سيحتاج ذلك إلى وظيفة ضخمة تحتوي على شروط ودمج النصوص مما سيزيد من صعوبة قراءة الشيفرة. يمكن تقسيم مثل هذه الطريقة إلى طرق أصغر، حيث يضيف كل منها مجموعة خاصة من المعلومات إلى معلمة الجمع. انظر إلى هذا في [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf). + +أمثلة أخرى هي: + +- إضافة رسائل الخطأ أو فشل التحقق في عملية تحقق معقدة. +- جمع العناصر أو المعلومات أثناء التنقل في هيكل بيانات معقد. +- إعادة هيكلة الوظائف المعقدة للتقارير حيث يتم إنشاء أجزاء متعددة من التقرير باستخدام طرق مختلفة. + +## العواقب + +المزايا: + +- يقلل من تكرار الشيفرة من خلال تجميع معالجة المجموعات في مكان واحد. +- يحسن الوضوح وقابلية الصيانة من خلال توضيح مكان وكيفية جمع النتائج. +- يحسن الأداء عن طريق تقليل إنشاء وإدارة كائنات جمع متعددة. + +العيوب: + +- يزيد من الترابط بين المنادي والطرق المنادى عليها، حيث يجب أن يتفقوا على المجموعة المستخدمة. +- قد يقدم آثار جانبية في الطرق إذا لم تتم إدارتها بعناية، حيث لم تعد الطرق مستقلة في إدارة النتائج. + +## الأنماط ذات الصلة + +- [Composite](https://java-design-patterns.com/patterns/composite/): يمكن استخدامه مع جمع المعاملات عند العمل مع الهياكل الهرمية، مما يسمح بجمع النتائج عبر هيكل مركب. +- [Visitor](https://java-design-patterns.com/patterns/visitor/): يستخدم غالبًا معًا، حيث يتولى Visitor المرور وإجراء العمليات في هيكل، بينما يقوم جمع المعاملات بتراكم النتائج. +- [Command](https://java-design-patterns.com/patterns/command/): يمكن للأوامر استخدام معلمة الجمع لإضافة نتائج عدة عمليات يتم تنفيذها بواسطة كائنات الأمر. + +## الشكر + +- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) بواسطة Joshua Kerivsky +- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) بواسطة Kent Beck +- [Wiki](https://wiki.c2.com/?CollectingParameter) +- [Refactoring: Improving the Design of Existing Code](https://amzn.to/3TVEgaB) +- [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/4aApLP0) diff --git a/localization/ar/collecting-parameter/etc/collecting-parameter.urm.png b/localization/ar/collecting-parameter/etc/collecting-parameter.urm.png new file mode 100644 index 000000000000..785d6ecc2da1 Binary files /dev/null and b/localization/ar/collecting-parameter/etc/collecting-parameter.urm.png differ diff --git a/localization/ar/command/README.md b/localization/ar/command/README.md new file mode 100644 index 000000000000..af20644ab502 --- /dev/null +++ b/localization/ar/command/README.md @@ -0,0 +1,245 @@ +--- +title: Command +shortTitle: Command +category: Behavioral +language: ar +tag: + - Gang of Four +--- + +## أيضًا يعرف بـ + +* إجراء +* معاملة + +## الهدف + +يُغلف نمط التصميم Command الطلب ككائن، مما يسمح بتمرير العملاء مع قوائم الانتظار، الطلبات، والعمليات. كما يدعم أيضًا التراجع عن العمليات. + +## الشرح + +### مثال واقعي + +> يوجد ساحر يلقي تعويذات على عفريت. يتم تنفيذ التعويذات على العفريت واحدة تلو الأخرى. التعويذة الأولى تصغر العفريت والتعويذة الثانية تجعله غير مرئي. بعد ذلك، يقوم الساحر بالتراجع عن التعويذات واحدة تلو الأخرى. كل تعويذة هي كائن أمر يمكن التراجع عنها. + +### بكلمات بسيطة: + +> تخزين الطلبات ككائنات أمر يسمح بتنفيذ الإجراء أو التراجع عنه في وقت لاحق. + +### تقول ويكيبيديا: + +> في البرمجة الكائنية التوجه، نمط الأمر هو نمط تصميم سلوكي حيث يتم استخدام كائن لتغليف كافة المعلومات اللازمة لتنفيذ إجراء أو تحفيز حدث في وقت لاحق. + +### مثال برمجي + +إليك الكود البرمجي مع الساحر `Wizard` والعفريت `Goblin`. دعونا نبدأ بفئة الساحر `Wizard`. + + +```java + +@Slf4j +public class Wizard { + + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); + + public Wizard() { + } + + public void castSpell(Runnable runnable) { + runnable.run(); + undoStack.offerLast(runnable); + } + + public void undoLastSpell() { + if (!undoStack.isEmpty()) { + var previousSpell = undoStack.pollLast(); + redoStack.offerLast(previousSpell); + previousSpell.run(); + } + } + + public void redoLastSpell() { + if (!redoStack.isEmpty()) { + var previousSpell = redoStack.pollLast(); + undoStack.offerLast(previousSpell); + previousSpell.run(); + } + } + + @Override + public String toString() { + return "Wizard"; + } +} +``` + +### التالي، لدينا العفريت `Goblin` الذي هو الهدف `Target` للتعويذات. + + +```java + +@Slf4j +public abstract class Target { + + private Size size; + + private Visibility visibility; + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public Visibility getVisibility() { + return visibility; + } + + public void setVisibility(Visibility visibility) { + this.visibility = visibility; + } + + @Override + public abstract String toString(); + + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } +} + +public class Goblin extends Target { + + public Goblin() { + setSize(Size.NORMAL); + setVisibility(Visibility.VISIBLE); + } + + @Override + public String toString() { + return "Goblin"; + } + + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } + + public void changeVisibility() { + var visible = getVisibility() == Visibility.INVISIBLE + ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } +} +``` + +### أخيرًا، لدينا الساحر في الدالة الرئيسية وهو يلقي التعويذات. + + +```java +public static void main(String[]args){ + var wizard=new Wizard(); + var goblin=new Goblin(); + + // casts shrink/unshrink spell + wizard.castSpell(goblin::changeSize); + + // casts visible/invisible spell + wizard.castSpell(goblin::changeVisibility); + + // undo and redo casts + wizard.undoLastSpell(); + wizard.redoLastSpell(); +``` + +### هذا هو المثال قيد التنفيذ. + + +```java +var wizard=new Wizard(); + var goblin=new Goblin(); + + goblin.printStatus(); + wizard.castSpell(goblin::changeSize); + goblin.printStatus(); + + wizard.castSpell(goblin::changeVisibility); + goblin.printStatus(); + + wizard.undoLastSpell(); + goblin.printStatus(); + + wizard.undoLastSpell(); + goblin.printStatus(); + + wizard.redoLastSpell(); + goblin.printStatus(); + + wizard.redoLastSpell(); + goblin.printStatus(); +``` + +### إليك مخرجات البرنامج: + + +```java +Goblin,[size=normal][visibility=visible] + Goblin,[size=small][visibility=visible] + Goblin,[size=small][visibility=invisible] + Goblin,[size=small][visibility=visible] + Goblin,[size=normal][visibility=visible] + Goblin,[size=small][visibility=visible] + Goblin,[size=small][visibility=invisible] +``` + +## تطبيق + +استخدم نمط الأمر (Command) في الحالات التالية: + +* لتحديد كائنات باستخدام إجراء لتنفيذه. يمكنك التعبير عن هذه التحديدات باستخدام لغة إجراء مع دالة رد اتصال، أي دالة يتم تسجيلها في مكان ما ليتم استدعاؤها في وقت لاحق. الأوامر هي بديل موجه للكائنات لردود الاتصال. +* لتحديد، وضع في طابور وتنفيذ الطلبات في أوقات مختلفة. يمكن أن يكون لكائن الأمر حياة مستقلة عن الطلب الأصلي. إذا كان يمكن تمثيل مستلم الطلب بطريقة مستقلة عن مساحة العناوين، فيمكنك نقل كائن الأمر للطلب إلى عملية مختلفة وتنفيذ الطلب هناك. +* دعم الإلغاء. يمكن لعملية تنفيذ الأمر تخزين الحالة لإلغاء تأثيراتها في نفس الأمر. يجب أن تحتوي واجهة الأمر على عملية إضافية لإلغاء التنفيذ التي تعيد تأثيرات استدعاء سابق لتنفيذ. يتم تخزين الأوامر التي تم تنفيذها في قائمة تاريخ. يمكن تحقيق وظيفة التراجع وإعادة التنفيذ بشكل غير محدود من خلال استعراض هذه القائمة للأمام والخلف عن طريق استدعاء إلغاء التنفيذ والتنفيذ، على التوالي. +* دعم تسجيل التغييرات بحيث يمكن تطبيقها مرة أخرى في حال حدوث عطل في النظام. من خلال إضافة عمليات تحميل وتخزين إلى واجهة الأوامر، يمكنك الاحتفاظ بسجل مستمر للتغييرات. يتطلب استعادة العطل إعادة تحميل الأوامر المسجلة من القرص وتنفيذها مرة أخرى باستخدام عملية التنفيذ. +* هيكلة النظام حول عمليات عالية المستوى مبنية على عمليات بدائية. هذه الهيكلة شائعة في أنظمة المعلومات التي تدعم المعاملات. المعاملة تحتوي على مجموعة من التغييرات في البيانات. يوفر نمط الأمر طريقة لنمذجة المعاملات. تحتوي الأوامر على واجهة مشتركة تسمح باستدعاء جميع المعاملات بنفس الطريقة. كما يسهل النمط توسيع النظام مع معاملات جديدة. +* الحفاظ على سجل من الطلبات. +* تنفيذ وظيفة رد الاتصال. +* تنفيذ وظيفة التراجع. + +## الاستخدامات المعروفة + +* الأزرار في واجهة المستخدم الرسومية وعناصر القائمة في تطبيقات سطح المكتب. +* العمليات في أنظمة قواعد البيانات والأنظمة المعاملاتية التي تدعم التراجع (rollback). +* تسجيل الماكرو في التطبيقات مثل محرري النصوص وجداول البيانات. +* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) +* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java) +* [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) +* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) + +## العواقب + +المزايا: + +* يفصل الكائن الذي يستدعي العملية عن الكائن الذي يعرف كيفية تنفيذها. +* من السهل إضافة أوامر جديدة، لأنه لا يتعين عليك تغيير الفئات الموجودة. +* يمكنك تجميع مجموعة من الأوامر في أمر مركب. + +العيوب: + +* يزيد عدد الفئات لكل أمر فردي. +* قد يعقد التصميم عند إضافة طبقات متعددة بين المرسلين والمستلمين. + +## الأنماط ذات الصلة + +* [Composite](https://java-design-patterns.com/patterns/composite/): يمكن تجميع الأوامر باستخدام نمط المركب لإنشاء أوامر كبيرة. +* [Memento](https://java-design-patterns.com/patterns/memento/): يمكن استخدامه لتنفيذ آليات التراجع. +* [Observer](https://java-design-patterns.com/patterns/observer/): يمكن ملاحظة النمط لتغييرات التي تفعّل الأوامر. + +## المصادر + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PFUqSY) diff --git a/localization/ar/command/etc/command.png b/localization/ar/command/etc/command.png new file mode 100644 index 000000000000..0f026464ecc4 Binary files /dev/null and b/localization/ar/command/etc/command.png differ diff --git a/localization/ar/commander/README.md b/localization/ar/commander/README.md new file mode 100644 index 000000000000..2cb0b72e17f1 --- /dev/null +++ b/localization/ar/commander/README.md @@ -0,0 +1,136 @@ +--- +title: Commander +shortTitle: Commander +category: Behavioral +language: ar +tag: + - Cloud distributed + - Microservices + - Transactions +--- + +## أيضا يُعرف باسم + +* منسق المعاملات الموزعة +* منسق المعاملات + +## الهدف + +الهدف من نمط "المنسق" في سياق المعاملات الموزعة هو إدارة وتنسيق المعاملات المعقدة عبر العديد من المكونات أو الخدمات الموزعة، مما يضمن التناسق والنزاهة للمعاملة العالمية. يقوم بتغليف أوامر المعاملات ومنطق التنسيق، مما يسهل تنفيذ بروتوكولات المعاملات الموزعة مثل الالتزام ذو المرحلتين أو ساغا. + +## الشرح + +مثال حقيقي + +> تخيل أنك تنظم مهرجان موسيقي دولي كبير، حيث من المقرر أن تؤدي عدة فرق من جميع أنحاء العالم. وصول كل فرقة، واختبار الصوت، والأداء، كل منها يعد معاملة فردية في نظام موزع. يتصرف منظم المهرجان كـ "منسق"، منسقًا هذه المعاملات لضمان أنه إذا تأخر طيران إحدى الفرق (مثل فشل المعاملة)، فهناك خطة احتياطية مثل إعادة جدولة أو تبادل الفترات الزمنية مع فرقة أخرى (إجراءات تعويضية)، للحفاظ على سير البرنامج العام. تعكس هذه الإعدادات نمط المنسق في المعاملات الموزعة، حيث يجب تنسيق العديد من المكونات لتحقيق نتيجة مرضية رغم الفشل الفردي. + +بكلمات بسيطة + +> يقوم نمط "المنسق" بتحويل الطلب إلى كائن مستقل، مما يسمح بمعلمة الأوامر، ووضع الإجراءات في طابور، وتنفيذ عمليات التراجع. + +**مثال برمجي** + +إدارة المعاملات عبر خدمات مختلفة في نظام موزع، مثل منصة للتجارة الإلكترونية تحتوي على خدمات منفصلة للدفع والشحن، تتطلب تنسيقًا دقيقًا لتجنب المشكلات. عندما يقدم المستخدم طلبًا ولكن خدمة واحدة (مثل الدفع) غير متوفرة بينما الخدمة الأخرى (مثل الشحن) جاهزة، نحتاج إلى حل قوي للتعامل مع هذا التفاوت. + +إحدى الاستراتيجيات لحل هذه المشكلة هي استخدام مكون منسق لتنظيم العملية. في البداية، تتم معالجة الطلب من قبل الخدمة المتاحة (الشحن في هذه الحالة). ثم يحاول المنسق مزامنة الطلب مع الخدمة غير المتوفرة في ذلك الوقت (الدفع) من خلال تخزين تفاصيل الطلب في قاعدة بيانات أو وضعه في طابور للمعالجة في المستقبل. يجب أن يأخذ هذا النظام في الحسبان الفشل المحتمل عند إضافة الطلبات إلى الطابور. + +يحاول المنسق بشكل متكرر معالجة الطلبات في الطابور لضمان أن تعكس جميع الخدمات أخيرًا نفس بيانات المعاملة. يتضمن هذا العملية ضمان التكرارية، مما يعني أنه حتى إذا تم إجراء نفس طلب مزامنة الطلبات عدة مرات، فسيتم تنفيذه مرة واحدة فقط، مما يمنع المعاملات المكررة. الهدف هو تحقيق التناسق النهائي بين الخدمات، حيث تتزامن جميع الأنظمة بمرور الوقت على الرغم من الفشل أو التأخير الأولي. + +في الكود المقدم، يُستخدم نمط المنسق لإدارة المعاملات الموزعة عبر العديد من الخدمات (خدمة الدفع، خدمة الشحن، خدمة الرسائل، إدارة الموظفين). كل خدمة تحتوي على قاعدة بيانات خاصة بها ويمكن أن تُطلق استثناءات لمحاكاة الفشل. + +فئة المنسق هي الجزء المركزي من هذا النمط. تأخذ الفئة المنسق مثيلات لجميع الخدمات وقواعد بياناتها، جنبًا إلى جنب مع بعض معلمات التكوين. تُستخدم دالة placeOrder في فئة المنسق لتنفيذ الطلب، مما يتطلب التفاعل مع جميع الخدمات. + + +```java +public class Commander { + // ... constructor and other methods ... + + public void placeOrder(Order order) { + // ... implementation ... + } +} +``` + +تمثل الفئات "المستخدم" و "الطلب" مستخدمًا وطلبًا على التوالي. يتم إجراء الطلب بواسطة المستخدم. + + +```java +public class User { + // ... constructor and other methods ... +} + +public class Order { + // ... constructor and other methods ... +} +``` + +كل خدمة (على سبيل المثال، خدمة الدفع، خدمة الشحن، خدمة الرسائل، إدارة الموظفين) لديها قاعدة بيانات خاصة بها ويمكن أن تُطلق استثناءات لمحاكاة الأعطال. على سبيل المثال، قد تقوم خدمة الدفع بإطلاق استثناء DatabaseUnavailableException إذا كانت قاعدة بياناتها غير متاحة. + + +```java +public class PaymentService { + // ... constructor and other methods ... +} +``` + +تمثل الفئات DatabaseUnavailableException و ItemUnavailableException و ShippingNotPossibleException أنواعًا مختلفة من الاستثناءات التي قد تحدث. + + +```java +public class DatabaseUnavailableException extends Exception { + // ... constructor and other methods ... +} + +public class ItemUnavailableException extends Exception { + // ... constructor and other methods ... +} + +public class ShippingNotPossibleException extends Exception { + // ... constructor and other methods ... +} +``` + +في الطريقة الرئيسية لكل فئة (AppQueueFailCases و AppShippingFailCases)، يتم محاكاة سيناريوهات مختلفة عن طريق إنشاء مثيلات من فئة Commander مع تكوينات مختلفة واستدعاء طريقة placeOrder. + +## مخطط الفئات + +![alt text](./etc/commander.urm.png "مخطط فئة Commander") + +## قابلية التطبيق + +استخدم نمط Commander للمعاملات الموزعة عندما: + +* تحتاج إلى ضمان اتساق البيانات بين الخدمات الموزعة في حالة حدوث فشل جزئي في النظام. +* تشمل المعاملات عدة خدمات ميكروسيرفيس أو مكونات موزعة تتطلب commit أو rollback منسق. +* تقوم بتنفيذ معاملات طويلة الأجل تتطلب إجراءات تعويضية للإلغاء. + +## الاستخدامات المعروفة + +* بروتوكولات Two-Phase Commit (2PC): التنسيق بين commit أو rollback عبر قواعد البيانات أو الخدمات الموزعة. +* تنفيذات نمط Saga: إدارة عمليات الأعمال طويلة الأجل التي تشمل العديد من الميكروسيرفيس، مع وجود إجراء تعويضي لكل خطوة للإلغاء. +* المعاملات الموزعة في بنية الميكروسيرفيس: تنسيق العمليات المعقدة بين الميكروسيرفيس مع الحفاظ على تكامل البيانات واتساقها. + +## العواقب + +الفوائد: + +* يوفر آلية واضحة لإدارة المعاملات الموزعة المعقدة، مما يحسن موثوقية النظام. +* يسمح بتنفيذ المعاملات التعويضية، وهي ضرورية للحفاظ على التناسق في المعاملات طويلة الأجل. +* يسهل دمج الأنظمة المتجانسة في سياق المعاملات. + +العيوب: + +* يزيد من التعقيد، خاصة في حالات الفشل، بسبب الحاجة إلى آليات التراجع المنسقة. +* قد يؤثر على الأداء بسبب الحمل الزائد للتنسيق وفحوصات التناسق. +* قد تؤدي التنفيذات المعتمدة على Saga إلى زيادة التعقيد في فهم سير العملية التجارية العامة. + +## الأنماط المرتبطة + +[Nمط Saga](https://java-design-patterns.com/patterns/saga/): غالبًا ما يتم مناقشته مع نمط Commander للمعاملات الموزعة، مع التركيز على المعاملات طويلة الأجل مع إجراءات تعويضية. + +## الشكر + +* [المعاملات الموزعة: جبال الجليد في الميكروسيرفيس](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/) +* [أنماط الميكروسيرفيس: مع أمثلة في جافا](https://amzn.to/4axjnYW) +* [تصميم التطبيقات المعتمدة على البيانات: الأفكار الكبيرة وراء الأنظمة القابلة للاعتماد، القابلة للتوسع، والقابلة للصيانة](https://amzn.to/4axHwOV) +* [أنماط تكامل المؤسسات: تصميم وبناء ونشر حلول الرسائل](https://amzn.to/4aATcRe) diff --git a/localization/ar/commander/etc/commander.urm.png b/localization/ar/commander/etc/commander.urm.png new file mode 100644 index 000000000000..6b5ebba75bd6 Binary files /dev/null and b/localization/ar/commander/etc/commander.urm.png differ diff --git a/localization/ar/composite-entity/README.md b/localization/ar/composite-entity/README.md new file mode 100644 index 000000000000..0e04ae3efe03 --- /dev/null +++ b/localization/ar/composite-entity/README.md @@ -0,0 +1,159 @@ +--- +title: Composite Entity +shortTitle: Composite Entity +category: Structural +language: ar +tag: + - Client-server + - Data access + - Enterprise patterns +--- + +## أيضا يُعرف بـ + +* الكيان ذو الحبيبات الخشنة + +## الهدف + +هدف نمط التصميم **الكيان المركب** هو إدارة مجموعة من الكائنات المستمرة المترابطة كما لو كانت كيانًا واحدًا. يتم استخدامه عادة في سياق **Enterprise JavaBeans (EJB)** وأطر العمل التجارية المماثلة لتمثيل الهياكل البيانية للبيانات ضمن نماذج الأعمال، مما يتيح للعملاء التعامل معها كوحدة واحدة. + +## الشرح + +مثال واقعي + +> في وحدة التحكم، قد يكون هناك العديد من الواجهات التي تحتاج إلى إدارة ومراقبة. باستخدام نمط الكيان المركب، يمكن دمج الكائنات المعتمدة مثل الرسائل والإشارات والسيطرة عليها باستخدام كائن واحد. + +بكلمات بسيطة + +> نمط الكيان المركب يسمح بتمثيل وإدارة مجموعة من الكائنات المرتبطة من خلال كائن موحد. + +**مثال برمجي** + +نحتاج إلى حل عام للمشكلة. لذلك، سنقدم نمطًا عامًا للكيان المركب. + + +```java +public abstract class DependentObject { + + T data; + + public void setData(T message) { + this.data = message; + } + + public T getData() { + return data; + } +} + +public abstract class CoarseGrainedObject { + + DependentObject[] dependentObjects; + + public void setData(T... data) { + IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i])); + } + + public T[] getData() { + return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray(); + } +} + +``` + +الكائن المركب المتخصص `consola` يرث من هذه الفئة الأساسية بالطريقة التالية. + + +```java +public class MessageDependentObject extends DependentObject { + +} + +public class SignalDependentObject extends DependentObject { + +} + +public class ConsoleCoarseGrainedObject extends CoarseGrainedObject { + + @Override + public String[] getData() { + super.getData(); + return new String[] { + dependentObjects[0].getData(), dependentObjects[1].getData() + }; + } + + public void init() { + dependentObjects = new DependentObject[] { + new MessageDependentObject(), new SignalDependentObject()}; + } +} + +public class CompositeEntity { + + private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); + + public void setData(String message, String signal) { + console.setData(message, signal); + } + + public String[] getData() { + return console.getData(); + } +} +``` + +إدارة الآن تخصيص كائنات الرسالة والإشارة مع الكائن المركب `consola`. + + +```java +var console=new CompositeEntity(); + console.init(); + console.setData("No Danger","Green Light"); + Arrays.stream(console.getData()).forEach(LOGGER::info); + console.setData("Danger","Red Light"); + Arrays.stream(console.getData()).forEach(LOGGER::info); +``` + +## مخطط الفئات + +![alt text](./etc/composite_entity.urm.png "نمط الكائن المركب") + +## القابلية للتطبيق + +* مفيد في التطبيقات التجارية حيث تكون الكائنات التجارية معقدة وتنطوي على عدة كائنات مترابطة. +* مثالي للسيناريوهات التي يحتاج فيها العملاء للعمل مع واجهة موحدة لمجموعة من الكائنات بدلاً من الكائنات الفردية. +* قابل للتطبيق في الأنظمة التي تتطلب عرضًا مبسطًا لنموذج بيانات معقد للعملاء أو الخدمات الخارجية. + +## الاستخدامات المعروفة + +* التطبيقات التجارية ذات النماذج التجارية المعقدة، وخاصة تلك التي تستخدم EJB أو أطر عمل تجارية مشابهة. +* الأنظمة التي تتطلب تجريدًا فوق مخططات قواعد بيانات معقدة لتبسيط التفاعلات مع العملاء. +* التطبيقات التي تحتاج إلى تعزيز التناسق أو المعاملات عبر عدة كائنات في كائن تجاري واحد. + +## العواقب + +الفوائد: + +* يبسط تفاعلات العميل مع النماذج الكائنية المعقدة من خلال توفير واجهة موحدة. +* يعزز إعادة الاستخدام والصيانة في طبقة الأعمال عن طريق فصل كود العميل عن المكونات الداخلية المعقدة للكائنات التجارية. +* يسهل إدارة المعاملات وتطبيق التناسق في مجموعة من الكائنات المترابطة. + +السلبيات: + +* قد يقدم مستوى من الاستدلال الذي قد يؤثر على الأداء. +* قد يؤدي إلى واجهات ذات حبوب خشنة جدًا قد لا تكون مرنة لجميع احتياجات العملاء. +* يتطلب تصميمًا دقيقًا لتجنب الكائنات المركبة المتضخمة التي يصعب إدارتها. + +## الأنماط ذات الصلة + +* [الزخرفة](https://java-design-patterns.com/patterns/decorator/): لإضافة سلوك ديناميكي للكائنات الفردية داخل الكائن المركب دون التأثير على الهيكل. +* [الواجهة](https://java-design-patterns.com/patterns/facade/): يوفر واجهة مبسطة لنظام فرعي معقد، بشكل مشابه لكيفية تبسيط الكائن المركب الوصول إلى مجموعة من الكائنات. +* [الوزن الخفيف](https://java-design-patterns.com/patterns/flyweight/): مفيد لإدارة الكائنات المشتركة داخل الكائن المركب لتقليل بصمة الذاكرة. + +## الاعتمادات + +* [نمط الكائن المركب في ويكيبيديا](https://en.wikipedia.org/wiki/Composite_entity_pattern) +* [أفضل الممارسات واستراتيجيات التصميم في الأنماط الأساسية لـ J2EE](https://amzn.to/4cAbDap) +* [أنماط المؤسسة و MDA: بناء البرمجيات الأفضل باستخدام أنماط الأركيتايب و UML](https://amzn.to/49mslqS) +* [أنماط بنية التطبيقات المؤسسية](https://amzn.to/3xjKdpe) diff --git a/localization/ar/composite-entity/etc/composite_entity.urm.png b/localization/ar/composite-entity/etc/composite_entity.urm.png new file mode 100644 index 000000000000..d6c29a718837 Binary files /dev/null and b/localization/ar/composite-entity/etc/composite_entity.urm.png differ diff --git a/localization/ar/composite-view/README.md b/localization/ar/composite-view/README.md new file mode 100644 index 000000000000..dcd543c43a2f --- /dev/null +++ b/localization/ar/composite-view/README.md @@ -0,0 +1,353 @@ +--- +title: Composite View +shortTitle: Composite View +category: Structural +language: ar +tag: + - Enterprise patterns + - Presentation +--- + +## الغرض + +الهدف الرئيسي من نمط التصميم "عرض مركب" هو تكوين الكائنات في هياكل شجرية لتمثيل الهيراركية جزء-كامل. هذا يتيح للعملاء التعامل مع الكائنات الفردية وتركيبات الكائنات بشكل موحد، مما يبسط إدارة الهياكل المعقدة. + +## التفسير + +مثال من العالم الحقيقي + +> موقع إخباري يريد عرض التاريخ الحالي والأخبار لعدة مستخدمين بناءً على تفضيلات كل مستخدم. سيستبدل الموقع في مكونات تغذية الأخبار المختلفة حسب اهتمامات المستخدم، مع الأخبار المحلية كافتراضي. + +بإيجاز + +> نمط العرض المركب يتكون من عرض رئيسي مكون من عروض فرعية أصغر. يعتمد تصميم هذا العرض المركب على قالب. ثم يقرر مدير العرض أي العروض الفرعية يجب تضمينها في هذا القالب. + +تقول ويكيبيديا + +> العروض المركبة التي تتكون من العديد من العروض الفرعية الذرية. يمكن تضمين كل مكون من القالب ديناميكيًا في المجموعة ويمكن إدارة تصميم الصفحة بشكل مستقل عن المحتوى. يتيح هذا الحل إنشاء عرض مركب استنادًا إلى تضمين واستبدال أجزاء قابلة لإعادة الاستخدام من القوالب الديناميكية والثابتة. يعزز التصميم المعياري من خلال تشجيع إعادة استخدام أجزاء الذرة من العرض. + +**مثال برمجي** + +نظرًا لأن هذا نمط تطوير ويب، فإن الخادم مطلوب لعرضه. يستخدم هذا المثال Tomcat 10.0.13 لتشغيل السيرفلت، ولن يعمل هذا المثال البرمجي إلا مع Tomcat 10+. + +أولاً، يوجد `AppServlet` الذي هو `HttpServlet` يعمل في Tomcat 10+. + + +```java +public class AppServlet extends HttpServlet { + private String msgPartOne = "

This Server Doesn't Support"; + private String msgPartTwo = "Requests

\n" + + "

Use a GET request with boolean values for the following parameters

\n" + + "

'name'

\n

'bus'

\n

'sports'

\n

'sci'

\n

'world'

"; + + private String destination = "newsDisplay.jsp"; + + public AppServlet() { + + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean reqParams = new ClientPropertiesBean(req); + req.setAttribute("properties", reqParams); + requestDispatcher.forward(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Post " + msgPartTwo); + + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Delete " + msgPartTwo); + + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Put " + msgPartTwo); + + } +} + +``` + +هذا السيرفلت لا يشكل جزءًا من النمط، ويقوم ببساطة بإعادة توجيه طلبات GET إلى JSP الصحيح. الطلبات PUT و POST و DELETE غير مدعومة وستعرض ببساطة رسالة خطأ. + +إدارة العرض في هذا المثال تتم من خلال فئة javabean: `ClientPropertiesBean`، التي تخزن تفضيلات المستخدم. + + +```java +public class ClientPropertiesBean implements Serializable { + + private static final String WORLD_PARAM = "world"; + private static final String SCIENCE_PARAM = "sci"; + private static final String SPORTS_PARAM = "sport"; + private static final String BUSINESS_PARAM = "bus"; + private static final String NAME_PARAM = "name"; + + private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private boolean worldNewsInterest; + private boolean sportsInterest; + private boolean businessInterest; + private boolean scienceNewsInterest; + private String name; + + public ClientPropertiesBean() { + worldNewsInterest = true; + sportsInterest = true; + businessInterest = true; + scienceNewsInterest = true; + name = DEFAULT_NAME; + + } + + public ClientPropertiesBean(HttpServletRequest req) { + worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM)); + sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM)); + businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM)); + scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM)); + String tempName = req.getParameter(NAME_PARAM); + if (tempName == null || tempName == "") { + tempName = DEFAULT_NAME; + } + name = tempName; + } + // getters and setters generated by Lombok +} +``` + +هذا السيرفلت لا يشكل جزءًا من النمط، ويقوم ببساطة بإعادة توجيه طلبات GET إلى JSP الصحيح. الطلبات PUT و POST و DELETE غير مدعومة وستعرض ببساطة رسالة خطأ. + +إدارة العرض في هذا المثال تتم من خلال فئة javabean: `ClientPropertiesBean`، التي تخزن تفضيلات المستخدم. + + +```html + + + + + + +<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> +

Welcome <%= propertiesBean.getName()%>

+ + + + + + <% if(propertiesBean.isWorldNewsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isBusinessInterest()) { %> + + <% } else { %> + + <% } %> + + <% if(propertiesBean.isSportsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isScienceNewsInterest()) { %> + + <% } else { %> + + <% } %> + + +
<%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="businessNews.jsp"%><%@include file="localNews.jsp"%><%@include file="sportsNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="scienceNews.jsp"%><%@include file="localNews.jsp"%>
+ + +``` + +هذه الصفحة JSP هي القالب. تقوم بإعلان جدول يحتوي على ثلاث صفوف، مع مكون في الصف الأول، واثنين من المكونات في الصف الثاني، ومكون واحد في الصف الثالث. + +تعتبر السكربتات في الملف جزءًا من استراتيجية إدارة العرض التي تتضمن مختلف العناصر الفرعية الذرية بناءً على تفضيلات المستخدم في الجافابين. + +فيما يلي مثالان على العناصر الفرعية الذرية المُحاكاة المستخدمة في التكوين: `businessNews.jsp`. + + +```html + + + + + + +

+ Generic Business News +

+ + + + + + + + + +
Stock prices up across the worldNew tech companies to invest in
Industry leaders unveil new projectPrice fluctuations and what they mean
+ + +``` + +`localNews.jsp` + +```html + + + +
+

+ Generic Local News +

+
    +
  • + Mayoral elections coming up in 2 weeks +
  • +
  • + New parking meter rates downtown coming tomorrow +
  • +
  • + Park renovations to finish by the next year +
  • +
  • + Annual marathon sign ups available online +
  • +
+
+ + +``` + +النتائج هي كما يلي: + +1) وضع المستخدم اسمه كـ `Tammy` في معلمات الطلب ولم يضع أي تفضيلات: ![alt text](./etc/images/noparam.png) +2) وضع المستخدم اسمه كـ `Johnny` في معلمات الطلب ولديه تفضيل للأخبار عن العالم، الأعمال والعلوم: ![alt text](./etc/images/threeparams.png) + +يتم تضمين العناصر الفرعية المختلفة مثل `worldNews.jsp`، `businessNews.jsp`، وغيرها بشكل مشروط بناءً على معلمات الطلب. + +**كيفية الاستخدام** + +لاختبار هذا المثال، تأكد من أن لديك Tomcat 10+ مثبتًا. قم بتكوين IDE الخاص بك لإنشاء ملف WAR من الوحدة ونشر هذا الملف على الخادم. + +IntelliJ: + +في `تشغيل` و `تحرير التكوينات` تأكد من أن خادم Tomcat هو أحد تكوينات التنفيذ. اذهب إلى تبويب النشر وتأكد من أنه يتم بناء artifact يسمى `composite-view:war exploded`. إذا لم يكن موجودًا، أضف واحدًا. + +تأكد من أن artifact يتم بناؤه من محتويات مجلد `web` ونتائج تجميع الوحدة. وجه مخرجات artifact إلى مكان مناسب. نفذ التكوين وشاهد الصفحة المستهدفة، واتبع التعليمات في تلك الصفحة للمتابعة. + +## مخطط الفئات + +![alt text](./etc/composite_view.png) + +يُظهر مخطط الفئات هنا الجافابين الذي يُعتبر مدير العرض. العروض هي ملفات JSP داخل مجلد الويب. + +## القابلية للتطبيق: + +استخدم نمط التصميم Composite View عندما: + +## تريد تمثيل الهياكل الجزئية للأشياء. + +* تتوقع أن الهياكل المركبة قد تتضمن مكونات جديدة في المستقبل. +* ترغب في أن يتمكن العملاء من تجاهل الفرق بين تكوينات الكائنات والكائنات الفردية. سيتعامل العملاء مع جميع الكائنات في الهيكل المركب بشكل موحد. + +## الاستخدامات المعروفة + +* واجهات المستخدم الرسومية (GUI) التي يمكن أن تحتوي فيها الأدوات على أدوات أخرى (على سبيل المثال، نافذة تحتوي على لوحات وأزرار وحقول نصية). +* هياكل الوثائق، مثل تمثيل الجداول التي تحتوي على صفوف، تحتوي هذه الصفوف بدورها على خلايا، والتي يمكن معالجتها جميعًا كعناصر في تسلسل هرمي موحد. + +## العواقب + +الفوائد: + +* مرونة كبيرة في إضافة مكونات جديدة: بما أن المكونات المركبة والعقد الورقية يتم التعامل معها بشكل موحد، يكون من الأسهل إضافة أنواع جديدة من المكونات. +* تبسيط الشيفرة البرمجية للعملاء: يمكن للعملاء التعامل مع الهياكل المركبة والعناصر الفردية بشكل موحد، مما يقلل من تعقيد الشيفرة البرمجية للعملاء. + +العيوب: + +* التعميم المفرط: قد يصبح تصميم النظام أكثر تعقيدًا إذا جعلت كل شيء مركبًا، خاصة إذا كانت تطبيقك لا يتطلب ذلك. +* صعوبة تطبيق القيود: قد يكون من الأصعب تقييد مكونات المركب لتكون من أنواع معينة فقط. + +## الأنماط ذات الصلة + +* [المزخرف](https://java-design-patterns.com/patterns/decorator/): بينما يستخدم مزخرف لإضافة مسؤوليات إلى الكائنات، يتم تصميم Composite لبناء هياكل كائنات. +* [الوزن الخفيف](https://java-design-patterns.com/patterns/flyweight/): يمكن دمج Composite غالبًا مع Flyweight لتنفيذ العقد الورقية المشتركة في هيكل مركب، مما يقلل من بصمة الذاكرة. +* [سلسلة المسؤولية](https://java-design-patterns.com/patterns/chain-of-responsibility/): يمكن استخدامها مع Composite للسماح للمكونات بتمرير الطلبات عبر التسلسل الهرمي. +* [المركب](https://java-design-patterns.com/patterns/composite/) +* [مساعد العرض](https://www.oracle.com/java/technologies/viewhelper.html) + +## الاعتمادات + +* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html) +* [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/) +* [Patterns of Enterprise Application Architecture](https://amzn.to/49jpQG3) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3xfntGJ) diff --git a/localization/ar/composite-view/etc/composite_view.png b/localization/ar/composite-view/etc/composite_view.png new file mode 100644 index 000000000000..66215d9416b8 Binary files /dev/null and b/localization/ar/composite-view/etc/composite_view.png differ diff --git a/composite-view/etc/images/noparam.png b/localization/ar/composite-view/etc/images/noparam.png similarity index 100% rename from composite-view/etc/images/noparam.png rename to localization/ar/composite-view/etc/images/noparam.png diff --git a/composite-view/etc/images/threeparams.png b/localization/ar/composite-view/etc/images/threeparams.png similarity index 100% rename from composite-view/etc/images/threeparams.png rename to localization/ar/composite-view/etc/images/threeparams.png diff --git a/localization/ar/composite/README.md b/localization/ar/composite/README.md new file mode 100644 index 000000000000..a90040cc02ab --- /dev/null +++ b/localization/ar/composite/README.md @@ -0,0 +1,222 @@ +--- +title: Composite +shortTitle: Composite +category: Structural +language: ar +tag: + - Gang of Four + - Object composition + - Recursion +--- + +## أيضًا يُعرف بـ + +* شجرة الكائنات +* الهيكل المركب + +## الهدف + +تركيب الكائنات في هياكل شجرية لتمثيل التسلسل الهرمي "جزء-كامل". يسمح نمط المركب للعملاء بمعاملة الكائنات الفردية وتركيبات الكائنات بطريقة موحدة. + +## الشرح + +مثال واقعي + +> تتكون كل جملة من كلمات، وكل كلمة تتكون بدورها من أحرف. كل واحد من هذه الكائنات قابل للطباعة ويمكن أن يكون له شيء مطبوع قبله أو بعده، مثلًا الجملة دائمًا تنتهي بنقطة، والكلمة دائمًا تملك مسافة قبلها. + +بكلمات بسيطة + +> يسمح نمط المركب للعملاء بمعاملة الكائنات الفردية بطريقة موحدة. + +تقول ويكيبيديا + +> في هندسة البرمجيات، نمط المركب هو نمط تصميم تقسيمي. يصف هذا النمط أنه يجب معاملة مجموعة من الكائنات بنفس الطريقة التي يتم بها معاملة كائن واحد. الهدف من المركب هو "تركيب" الكائنات في هياكل شجرية لتمثيل التسلسل الهرمي "جزء-كامل". يسمح تنفيذ نمط التركيب للعملاء بمعاملة الكائنات الفردية وتركيبات الكائنات بطريقة موحدة. + +**مثال برمجي** + +باستخدام مثالنا السابق، هنا لدينا الفئة الأساسية `LetterComposite` وأنواع مختلفة من الكائنات القابلة للطباعة مثل `Letter`, `Word`, و `Sentence`. + + +```java +public abstract class LetterComposite { + + private final List children = new ArrayList<>(); + + public void add(LetterComposite letter) { + children.add(letter); + } + + public int count() { + return children.size(); + } + + protected void printThisBefore() { + } + + protected void printThisAfter() { + } + + public void print() { + printThisBefore(); + children.forEach(LetterComposite::print); + printThisAfter(); + } +} + +public class Letter extends LetterComposite { + + private final char character; + + public Letter(char c) { + this.character = c; + } + + @Override + protected void printThisBefore() { + System.out.print(character); + } +} + +public class Word extends LetterComposite { + + public Word(List letters) { + letters.forEach(this::add); + } + + public Word(char... letters) { + for (char letter : letters) { + this.add(new Letter(letter)); + } + } + + @Override + protected void printThisBefore() { + System.out.print(" "); + } +} + +public class Sentence extends LetterComposite { + + public Sentence(List words) { + words.forEach(this::add); + } + + @Override + protected void printThisAfter() { + System.out.print("."); + } +} +``` + +## لدينا الآن مرسل لنقل الرسائل: + +```java +public class Messenger { + + LetterComposite messageFromOrcs() { + + var words = List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y') + ); + + return new Sentence(words); + + } + + LetterComposite messageFromElves() { + + var words = List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h') + ); + + return new Sentence(words); + + } + +} +``` + +## وبالتالي يمكن استخدامه كالتالي: + +```java +var messenger=new Messenger(); + + LOGGER.info("Message from the orcs: "); + messenger.messageFromOrcs().print(); + + LOGGER.info("Message from the elves: "); + messenger.messageFromElves().print(); +``` + +## مخرجات وحدة التحكم: + + + +``` +Message from the orcs: + Where there is a whip there is a way. +Message from the elves: + Much wind pours from your mouth. +``` + +## Diagrama de clases + +![alt text](./etc/composite.urm.png "Diagrama de clases compuestas") + +## Applicabilidad + +استخدم نمط **Composite** عندما: + +* ترغب في تمثيل الهياكل الجزئية للأشياء. +* ترغب في أن يتجاهل العملاء الفرق بين تراكيب الأشياء والأشياء الفردية. سيعالج العملاء جميع الكائنات في الهيكل المركب بشكل موحد. + +## الاستخدامات المعروفة + +* واجهات المستخدم الرسومية حيث يمكن للمكونات أن تحتوي على مكونات أخرى (مثل الألواح التي تحتوي على أزرار، تسميات، وألواح أخرى). +* تمثيلات أنظمة الملفات حيث يمكن للأدلة أن تحتوي على ملفات وأدلة أخرى. +* الهياكل التنظيمية حيث يمكن للقسم أن يحتوي على أقسام فرعية وموظفين. +* [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) + و [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) +* شجرة المكونات [Apache Wicket](https://github.com/apache/wicket)، + انظر [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) + و [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) + +## العواقب + +### الفوائد: + +* يبسط الكود الخاص بالعميل، حيث يمكنه التعامل مع الهياكل المركبة والأشياء الفردية بشكل موحد. +* يسهل إضافة أنواع جديدة من المكونات، حيث لا يتعين تعديل الكود الموجود. + +### العيوب: + +* قد يجعل التصميم عامًا جدًا. قد يكون من الصعب تقييد مكونات المركب. +* قد يصعب تحديد أنواع المكونات في المركب. + +## الأنماط المتعلقة + +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): يمكن لـ Composite استخدام Flyweight لمشاركة + مثيلات المكونات بين عدة مركبات. +* [Iterador](https://java-design-patterns.com/patterns/iterator/): يمكن استخدامه لتصفح الهياكل المركبة. +* [Visitante](https://java-design-patterns.com/patterns/visitor/): يمكن تطبيق عملية على الهيكل المركب. + +## الائتمانات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3xoLAmi) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3vBKXWb) diff --git a/localization/ar/composite/etc/composite.urm.png b/localization/ar/composite/etc/composite.urm.png new file mode 100644 index 000000000000..93c160f6450a Binary files /dev/null and b/localization/ar/composite/etc/composite.urm.png differ diff --git a/localization/ar/context-object/README.md b/localization/ar/context-object/README.md new file mode 100644 index 000000000000..77fd8d8b754c --- /dev/null +++ b/localization/ar/context-object/README.md @@ -0,0 +1,188 @@ +--- +title: Context object +shortTitle: Context object +category: Creational +language: ar +tags: + - Data access +--- + +## الاسم / التصنيف + +كائن السياق + +## يعرف أيضًا باسم + +السياق، تجميع السياق + +## الغرض + +فصل البيانات عن الفئات الخاصة بالبروتوكول وتخزين بيانات السياق في كائن مستقل عن التكنولوجيا الخاصة بالبروتوكول الأساسي. + +## الشرح + +مثال من العالم الواقعي + +> يحتوي هذا التطبيق على طبقات مختلفة موسومة مثل A وB وC، كل واحدة منها تستخرج معلومات محددة من سياق مشابه لاستخدامها لاحقًا في البرنامج. تمرير كل قطعة من المعلومات بشكل فردي سيكون غير فعال، لذا هناك حاجة إلى طريقة لتخزين وتمرير المعلومات بشكل فعال. +> بكلمات بسيطة + +> أنشئ كائنًا وخزن البيانات هناك، ثم مرر هذا الكائن حيثما كان مطلوبًا. + +[Core J2EE Patterns](http://corej2eepatterns.com/ContextObject.htm) يقول + +> استخدم كائن السياق (Context Object) لتغليف الحالة بطريقة مستقلة عن البروتوكول ليتم مشاركتها عبر تطبيقك. + +**مثال برمجي** + +نحدد البيانات التي يحتوي عليها كائن السياق (Context Object) للخدمة `ServiceContext`. + + +```Java +public class ServiceContext { + + String ACCOUNT_SERVICE, SESSION_SERVICE, SEARCH_SERVICE; + + public void setACCOUNT_SERVICE(String ACCOUNT_SERVICE) { + this.ACCOUNT_SERVICE = ACCOUNT_SERVICE; + } + + public void setSESSION_SERVICE(String SESSION_SERVICE) { + this.SESSION_SERVICE = SESSION_SERVICE; + } + + public void setSEARCH_SERVICE(String SEARCH_SERVICE) { + this.SEARCH_SERVICE = SEARCH_SERVICE; + } + + public String getACCOUNT_SERVICE() { + return ACCOUNT_SERVICE; + } + + public String getSESSION_SERVICE() { + return SESSION_SERVICE; + } + + public String getSEARCH_SERVICE() { + return SEARCH_SERVICE; + } + + public String toString() { return ACCOUNT_SERVICE + " " + SESSION_SERVICE + " " + SEARCH_SERVICE;} +} +``` + +يتم إنشاء واجهة `ServiceContextFactory` تُستخدم في أجزاء من التطبيق لإنشاء كائنات السياق. + + +```Java +public class ServiceContextFactory { + + public static ServiceContext createContext() { + return new ServiceContext(); + } +} +``` + +إنشاء كائن السياق في الطبقة الأولى `LayerA` والطبقة المجاورة `LayerB` حتى يتم استدعاء السياق في الطبقة الحالية `LayerC`، التي تقوم بترتيب الكائن. + + +```Java +public class LayerA { + + private static ServiceContext context; + + public LayerA() { + context = ServiceContextFactory.createContext(); + } + + public static ServiceContext getContext() { + return context; + } + + public void addAccountInfo(String accountService) { + context.setACCOUNT_SERVICE(accountService); + } +} + +public class LayerB { + + private static ServiceContext context; + + public LayerB(LayerA layerA) { + this.context = layerA.getContext(); + } + + public static ServiceContext getContext() { + return context; + } + + public void addSessionInfo(String sessionService) { + context.setSESSION_SERVICE(sessionService); + } +} + +public class LayerC { + + public static ServiceContext context; + + public LayerC(LayerB layerB) { + this.context = layerB.getContext(); + } + + public static ServiceContext getContext() { + return context; + } + + public void addSearchInfo(String searchService) { + context.setSEARCH_SERVICE(searchService); + } +} +``` + +إليك كائن السياق والطبقات قيد التنفيذ. + + +```Java +var layerA = new LayerA(); +layerA.addAccountInfo(SERVICE); +LOGGER.info("Context = {}",layerA.getContext()); +var layerB = new LayerB(layerA); +layerB.addSessionInfo(SERVICE); +LOGGER.info("Context = {}",layerB.getContext()); +var layerC = new LayerC(layerB); +layerC.addSearchInfo(SERVICE); +LOGGER.info("Context = {}",layerC.getContext()); +``` + +إخراج البرنامج: + + +```Java +Context = SERVICE null null +Context = SERVICE SERVICE null +Context = SERVICE SERVICE SERVICE +``` + +## رسم توضيحي للفئات + +![alt text](./etc/context-object.png "كائن السياق") + +## القابلية للتطبيق + +استخدم نمط كائن السياق (Context Object) لـ: + +* مشاركة المعلومات بين الطبقات المختلفة للنظام. +* فصل البيانات عن السياقات المحددة للبروتوكولات. +* عرض واجهات البرمجة ذات الصلة فقط ضمن السياق. + +## الاستخدامات المعروفة + +* [Spring: ApplicationContext](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html) +* [Oracle: SecurityContext](https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/SecurityContext.html) +* [Oracle: ServletContext](https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html) + +## الفضل + +* [Core J2EE Design Patterns](https://amzn.to/3IhcY9w) +* [موقع Core J2EE Design Patterns - كائن السياق](http://corej2eepatterns.com/ContextObject.htm) +* [Allan Kelly - نمط تجميع السياق](https://accu.org/journals/overload/12/63/kelly_246/) +* [Arvid S. Krishna وآخرون - كائن السياق](https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf) diff --git a/localization/ar/context-object/etc/context-object.png b/localization/ar/context-object/etc/context-object.png new file mode 100644 index 000000000000..a1f670812f7b Binary files /dev/null and b/localization/ar/context-object/etc/context-object.png differ diff --git a/localization/ar/converter/README.md b/localization/ar/converter/README.md new file mode 100644 index 000000000000..cef553e1da32 --- /dev/null +++ b/localization/ar/converter/README.md @@ -0,0 +1,102 @@ +--- +title: Converter +shortTitle: Converter +category: Creational +language: ar +tag: + - Decoupling +--- + +## الهدف + +الهدف من نمط المحول (Converter) هو توفير وسيلة عامة وشائعة للتحويل الثنائي الاتجاه بين الأنواع المقابلة، مما يتيح تنفيذًا نظيفًا حيث لا يحتاج الأنواع إلى معرفة بعضها البعض. علاوة على ذلك، يقدم نمط المحول تعيينًا ثنائي الاتجاه للمجموعات الثنائية الاتجاه، مما يقلل من الكود المكرر إلى الحد الأدنى. + +## الشرح + +مثال من الحياة الواقعية + +> في التطبيقات الواقعية، غالبًا ما يحدث أن يتكون طبقة قاعدة البيانات من كيانات تحتاج إلى أن يتم تحويلها إلى DTO لاستخدامها في طبقة منطق الأعمال. يتم إجراء تحويل مشابه لعدد potentially ضخم من الفئات ونحتاج إلى طريقة عامة لتحقيق ذلك. + +ببساطة + +> يسهل نمط التحويل (Converter) تعيين الكائنات من فئة إلى كائنات من فئة أخرى. + +**مثال برمجي** + +نحتاج إلى حل عام لمشكلة التعيين. لذلك، دعونا نقدم محولًا عامًا. + + +```java +public class Converter { + + private final Function fromDto; + private final Function fromEntity; + + public Converter(final Function fromDto, final Function fromEntity) { + this.fromDto = fromDto; + this.fromEntity = fromEntity; + } + + public final U convertFromDto(final T dto) { + return fromDto.apply(dto); + } + + public final T convertFromEntity(final U entity) { + return fromEntity.apply(entity); + } + + public final List createFromDtos(final Collection dtos) { + return dtos.stream().map(this::convertFromDto).collect(Collectors.toList()); + } + + public final List createFromEntities(final Collection entities) { + return entities.stream().map(this::convertFromEntity).collect(Collectors.toList()); + } +} +``` + +## المحولات المتخصصة ترث من هذه الفئة الأساسية كما يلي. + + +```java +public class UserConverter extends Converter { + + public UserConverter() { + super(UserConverter::convertToEntity, UserConverter::convertToDto); + } + + private static UserDto convertToDto(User user) { + return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId()); + } + + private static User convertToEntity(UserDto dto) { + return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail()); + } + +} +``` + +الآن يصبح التحويل بين `User` و `UserDto` أمرًا تافهًا. + + +```java +var userConverter = new UserConverter(); +var dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); +var user = userConverter.convertFromDto(dtoUser); +``` + +## مخطط الفئات + +![alt text](./etc/converter.png "نمط المحول") + +## القابلية للتطبيق + +استخدم نمط المحول في الحالات التالية: + +* عندما يكون لديك أنواع تتطابق منطقيًا مع بعضها البعض وتحتاج إلى تحويل الكيانات بينهما. +* عندما ترغب في توفير أشكال مختلفة لتحويل الأنواع اعتمادًا على السياق. +* كلما قدمت كائن نقل البيانات (DTO)، من المحتمل أن تحتاج إلى تحويله إلى معادله في المجال. + +## الاعتمادات + +* [نمط المحول في Java 8](http://www.xsolve.pl/blog/converter-pattern-in-java-8/) diff --git a/localization/ar/converter/etc/converter.png b/localization/ar/converter/etc/converter.png new file mode 100644 index 000000000000..01435ef5ae29 Binary files /dev/null and b/localization/ar/converter/etc/converter.png differ diff --git a/localization/ar/crtp/README.md b/localization/ar/crtp/README.md new file mode 100644 index 000000000000..f7ab6de3a210 --- /dev/null +++ b/localization/ar/crtp/README.md @@ -0,0 +1,139 @@ +--- +title: Curiously Recurring Template Pattern +language: ar +category: Structural +tag: +- Extensibility +- Instantiation +--- + +## الاسم / التصنيف + +نمط القالب المتكرر بغرابة + +## المعروف أيضًا باسم + +الحدود النوعية المتكررة، الجينيريك المتكرر + +## الهدف + +السماح للمكونات المشتقة بالوراثة من بعض الوظائف من مكون أساسي تكون متوافقة مع النوع المشتق. + +## الشرح + +مثال حقيقي + +> لتنظيم حدث للفنون القتالية المختلطة، من المهم التأكد من أن المباريات تتم بين رياضيين في نفس فئة الوزن. هذا يضمن تجنب المواجهات بين مقاتلين من أحجام مختلفة للغاية، مثل الوزن الثقيل ضد الوزن الخفيف. + +ببساطة + +> جعل بعض الطرق داخل نوع ما تقبل المعاملات الخاصة بأنواعه الفرعية. + +تقول ويكيبيديا + +> نمط القالب المتكرر بغرابة (CRTP) هو أسلوب برمجي، بدأ في C++، حيث تقوم فئة X بالاشتقاق من تطبيق قالب فئة باستخدام X نفسها كحجة للقالب. + +**مثال برمجي** + +لنحدد الواجهة العامة Fighter + + +```java +public interface Fighter { + + void fight(T t); + +} +``` + +تستخدم فئة `MMAFighter` لإنشاء مقاتلين يتميزون بفئة وزنهم. + + +```java +public class MmaFighter> implements Fighter { + + private final String name; + private final String surname; + private final String nickName; + private final String speciality; + + public MmaFighter(String name, String surname, String nickName, String speciality) { + this.name = name; + this.surname = surname; + this.nickName = nickName; + this.speciality = speciality; + } + + @Override + public void fight(T opponent) { + LOGGER.info("{} is going to fight against {}", this, opponent); + } + + @Override + public String toString() { + return name + " \"" + nickName + "\" " + surname; + } +} +``` + +فيما يلي بعض الأنواع الفرعية لـ `MMAFighter`: + + +```java +class MmaBantamweightFighter extends MmaFighter { + + public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} + +public class MmaHeavyweightFighter extends MmaFighter { + + public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} +``` + +يمكن للمقاتل أن يواجه خصمًا من نفس فئة الوزن، وإذا كان الخصم من فئة وزن مختلفة يحدث خطأ. + + +```java +MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); +MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); +fighter1.fight(fighter2); // This is fine + +MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); +MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); +fighter3.fight(fighter4); // This is fine too + +fighter1.fight(fighter3); // This will raise a compilation error +``` + +## Diagrama de clases + +![alt text](./etc/crtp.png "Diagrama de clases CRTP") + +## قابلية التطبيق + +استخدم نمط "القالب المتكرر بشكل غريب" عندما: + +* تواجه تعارضات في الأنواع عند ربط الأساليب في هيكل الكائنات +* ترغب في استخدام طريقة من الفئة معلمة يمكن أن تقبل الفئات الفرعية كوسائط، مما يسمح بتطبيقها على الكائنات التي ترث من هذه الفئة +* ترغب في أن تعمل بعض الأساليب فقط مع كائنات من نفس النوع، على سبيل المثال، لتحقيق المقارنة المتبادلة. + +## دروس تعليمية + +* [مدونة NuaH](https://nuah.livejournal.com/328187.html) +* إجابة من Yogesh Umesh Vaity على [ماذا يعني "الحدود التكرارية للنوع" في الأنواع العامة؟](https://stackoverflow.com/questions/7385949/what-does-recursive-type-bound-in-generics-mean) + +## الاستخدامات المعروفة + +* [java.lang.Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html) + +## الاعتمادات + +* [كيف أفك تشفير "Enum>"؟](http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106) +* الفصل 5 Generics، العنصر 30 في [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) diff --git a/crtp/etc/crtp.png b/localization/ar/crtp/etc/crtp.png similarity index 100% rename from crtp/etc/crtp.png rename to localization/ar/crtp/etc/crtp.png diff --git a/localization/ar/data-locality/README.md b/localization/ar/data-locality/README.md new file mode 100644 index 000000000000..9150d2b51bc6 --- /dev/null +++ b/localization/ar/data-locality/README.md @@ -0,0 +1,30 @@ +--- +title: Data Locality +shortTitle: Data Locality +category: Behavioral +language: ar +tag: + - Game programming + - Performance +--- + +## الهدف +يُسرع الوصول إلى الذاكرة من خلال تنظيم البيانات للاستفادة من ذاكرة التخزين المؤقت في وحدة المعالجة المركزية. + +تحتوي وحدات المعالجة المركزية الحديثة على ذاكرات تخزين مؤقت لتسريع الوصول إلى الذاكرة. يمكنها الوصول بسرعة أكبر إلى الذاكرة المجاورة لذاكرة تم الوصول إليها مؤخرًا. استفد من ذلك لتحسين الأداء عن طريق زيادة محلية البيانات، من خلال الحفاظ عليها في ذاكرة متجاورة بالترتيب الذي تعالجها فيه. + +## مخطط الفئات +![alt text](./etc/data-locality.urm.png "Data Locality pattern class diagram") + +## قابلية التطبيق + +* كما هو الحال مع معظم التحسينات، القاعدة الأولى لاستخدام نمط "محلية البيانات" هي عندما يكون لديك مشكلة في الأداء. +* مع هذا النمط بشكل خاص، ستحتاج أيضًا إلى التأكد من أن مشاكل الأداء ناتجة عن فقدان الذاكرة المؤقتة. + +## مثال من العالم الحقيقي + +* محرك الألعاب [Artemis](http://gamadu.com/artemis/) هو واحد من أولى وأكثر الأطر شهرة التي تستخدم معرفات بسيطة لكائنات اللعبة. + +## الاعتمادات + +* [أنماط برمجة الألعاب - أنماط التحسين: محلية البيانات](http://gameprogrammingpatterns.com/data-locality.html) diff --git a/localization/ar/data-locality/etc/data-locality.urm.png b/localization/ar/data-locality/etc/data-locality.urm.png new file mode 100644 index 000000000000..d19873739551 Binary files /dev/null and b/localization/ar/data-locality/etc/data-locality.urm.png differ diff --git a/localization/ar/decorator/README.md b/localization/ar/decorator/README.md new file mode 100644 index 000000000000..e36cc2695d79 --- /dev/null +++ b/localization/ar/decorator/README.md @@ -0,0 +1,164 @@ +--- +title: Decorator +shortTitle: Decorator +category: Structural +language: ar +tag: + - Gang of Four + - Extensibility +--- + +## المعروف أيضا باسم + +التغليف + +## الهدف + +إضافة مسؤوليات إضافية إلى كائن بطريقة ديناميكية. يوفر الزخرفة بديلاً مرنًا للوراثة لتوسيع الوظائف. + +## الشرح + +مثال من العالم الحقيقي + +> في التلال القريبة يعيش تيرول غاضب. عادة ما يكون يده عارية، ولكن في بعض الأحيان يحمل سلاحًا. لتسليح التيرول لا يتطلب الأمر إنشاء تيرول جديد بل تزيينه ديناميكيًا بسلاح مناسب. + +ببساطة + +> يتيح لك نمط الزخرفة تغيير سلوك كائن ديناميكيًا أثناء وقت التشغيل من خلال تغليفه في كائن من فئة الزخرفة. + +يقول Wikipedia + +> في البرمجة الكائنية التوجه، يعتبر نمط الزخرفة نمط تصميم يسمح بإضافة سلوك إلى كائن فردي، سواء بطريقة ثابتة أو ديناميكية، دون التأثير على سلوك الكائنات الأخرى من نفس الفئة. يُعتبر نمط الزخرفة مفيدًا للامتثال لمبدأ المسؤولية الفردية، حيث يسمح بتقسيم الوظائف بين الفئات ذات مجالات الاهتمام الفريدة، وكذلك لمبدأ الانفتاح-الإغلاق، من خلال السماح بتمديد وظائف الفئة دون تعديلها. + +**مثال برمجي** + +لنأخذ مثال التيرول. في البداية لدينا `SimpleTroll` الذي ينفذ الواجهة `Troll`: + + +```java +public interface Troll { + void atacar(); + int getPoderAtaque(); + void huirBatalla(); +} + +@Slf4j +public class SimpleTroll implements Troll { + + @Override + public void atacar() { + LOGGER.info("¡El troll intenta atraparte!"); + } + + @Override + public int getPoderAtaque() { + return 10; + } + + @Override + public void huirBatalla() { + LOGGER.info("¡El troll chilla de horror y huye!"); + } +} +``` + +بعد ذلك، نريد إضافة عصا للتيرول. يمكننا فعل ذلك بشكل ديناميكي باستخدام الزخرفة: + + +```java +@Slf4j +public class TrollConGarrote implements Troll { + + private final Troll decorado; + + public TrollConGarrote(Troll decorado) { + this.decorado = decorado; + } + + @Override + public void atacar() { + decorado.atacar(); + LOGGER.info("¡El troll te golpea con un garrote!"); + } + + @Override + public int getPoderAtaque() { + return decorado.getPoderAtaque() + 10; + } + + @Override + public void huirBatalla() { + decorado.huirBatalla(); + } +} +``` + +إليك التيرول في العمل: + + +```java +// التيرول البسيط +LOGGER.info("تيرول ذو مظهر بسيط يقترب."); +var troll = new SimpleTroll(); +troll.atacar(); +troll.huirBatalla(); +LOGGER.info("قوة التيرول البسيط: {}.\n", troll.getPoderAtaque()); + +// تغيير سلوك التيرول البسيط عن طريق إضافة ديكور +LOGGER.info("تيرول يحمل عصا ضخمة يفاجئك."); +var trollConGarrote = new TrollConGarrote(troll); +trollConGarrote.atacar(); +trollConGarrote.huirBatalla(); +LOGGER.info("قوة التيرول مع العصا: {}.\n", trollConGarrote.getPoderAtaque()); + +``` + +نتيجة البرنامج: + +```java +تيرول ذو مظهر بسيط يقترب. +!التيرول يحاول الإمساك بك! +!التيرول يصرخ من الرعب ويهرب! +قوة التيرول البسيط: 10. + +تيرول يحمل عصا ضخمة يفاجئك. +!التيرول يحاول الإمساك بك! +!التيرول يضربك بعصا ضخمة! +!التيرول يصرخ من الرعب ويهرب! +قوة التيرول مع العصا: 20. + +``` + +## مخطط الفئات + +![alt text](./etc/decorator.urm.png "مخطط فئات نمط الديكور") + +## القابلية للتطبيق + +يُستخدم نمط الديكور لـ: + +* إضافة مسؤوليات إلى كائنات فردية بشكل ديناميكي وشفاف، أي دون التأثير على الكائنات الأخرى. +* للمسؤوليات التي يمكن إزالتها. +* عندما يكون التوسيع بواسطة الفئات الفرعية غير عملي. في بعض الأحيان، قد يكون من الممكن إضافة عدد كبير من الامتدادات المستقلة التي قد تؤدي إلى انفجار في الفئات الفرعية لدعم كل مجموعة من التركيبات. أو قد تكون تعريفات الفئات مخفية أو غير متاحة للفئات الفرعية. + +## الدروس التعليمية + +* [دورة نمط الديكور](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) + +## الاستخدامات المعروفة + +* [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html)، [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html)، + [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) + و [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) +* [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) +* [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) +* [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) + +## الاعتمادات + +* [تصميم الأنماط: عناصر البرمجيات القابلة لإعادة الاستخدام](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [البرمجة الوظيفية في جافا: تسخير قوة تعبيرات لامبدا في جافا 8](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [أنماط تصميم J2EE](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [رأس الأنماط: دليل سهل الفهم](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [إعادة الهيكلة إلى الأنماط](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [أنماط تصميم J2EE](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/localization/ar/decorator/etc/decorator.urm.png b/localization/ar/decorator/etc/decorator.urm.png new file mode 100644 index 000000000000..141c0563f0c6 Binary files /dev/null and b/localization/ar/decorator/etc/decorator.urm.png differ diff --git a/localization/ar/delegation/README.md b/localization/ar/delegation/README.md new file mode 100644 index 000000000000..1f0d84734dd1 --- /dev/null +++ b/localization/ar/delegation/README.md @@ -0,0 +1,119 @@ +--- +title: Delegation +shortTitle: Delegation +category: Structural +language: ar +tag: + - Decoupling +--- + +## يُعرف أيضًا بـ + +نمط الوكيل (Proxy Pattern) + +## الهدف + +هي تقنية يتم من خلالها أن يعبر كائن عن سلوك معين للخارج ولكنه في الواقع يفوض مسؤولية تنفيذ ذلك السلوك إلى كائن مرتبط. + +## الشرح + +مثال من الواقع + +> لنتخيل أن لدينا مغامرين يقاتلون ضد وحوش بأسلحة مختلفة حسب مهاراتهم وقدراتهم. يجب أن نكون قادرين على تجهيزهم بأسلحة مختلفة دون الحاجة لتعديل الشيفرة المصدرية لكل سلاح. يقوم نمط التفويض بهذا من خلال تفويض العمل بشكل ديناميكي إلى كائن معين يقوم بتنفيذ واجهة بها الطرق ذات الصلة. + +يقول ويكيبيديا + +> في البرمجة الشيئية، يشير التفويض إلى تقييم أحد الأعضاء (خاصية أو طريقة) لكائن (المستقبل) في سياق كائن آخر أصلي (المرسل). يمكن أن يتم التفويض بشكل صريح، عن طريق تمرير الكائن المرسل إلى الكائن المستقبل، وهو ما يمكن القيام به في أي لغة برمجة موجهة للكائنات؛ أو ضمنيًا، من خلال قواعد البحث عن الأعضاء في اللغة، وهو ما يتطلب دعم اللغة لهذه الوظيفة. + +**مثال برمجي** + +لدينا واجهة `Printer` وثلاثة تطبيقات هي `CanonPrinter`، `EpsonPrinter` و `HpPrinter`. + +```java +public interface Printer { + void print(final String message); +} + +@Slf4j +public class CanonPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("Canon Printer : {}", message); + } +} + +@Slf4j +public class EpsonPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("Epson Printer : {}", message); + } +} + +@Slf4j +public class HpPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("HP Printer : {}", message); + } +} +``` + +El `PrinterController` puede ser utilizado como un `Printer` delegando cualquier trabajo manejado por este +a un objeto que la implemente. + +```java +public class PrinterController implements Printer { + + private final Printer printer; + + public PrinterController(Printer printer) { + this.printer = printer; + } + + @Override + public void print(String message) { + printer.print(message); + } +} +``` + +الآن في شفرة العميل، يمكن لوحدات تحكم الطابعة طباعة الرسائل بطرق مختلفة اعتمادًا على الكائن الذي يتم تفويض العمل إليه. + + +```java +private static final String MESSAGE_TO_PRINT = "hello world"; + +var hpPrinterController = new PrinterController(new HpPrinter()); +var canonPrinterController = new PrinterController(new CanonPrinter()); +var epsonPrinterController = new PrinterController(new EpsonPrinter()); + +hpPrinterController.print(MESSAGE_TO_PRINT); +canonPrinterController.print(MESSAGE_TO_PRINT); +epsonPrinterController.print(MESSAGE_TO_PRINT) +``` + +مخرجات البرنامج: + + +```java +HP Printer : hello world +Canon Printer : hello world +Epson Printer : hello world +``` + +## مخطط الفئات + +![alt text](./etc/delegation.png "Delegate") + +## القابلية للتطبيق + +استخدم نمط التفويض لتحقيق ما يلي: + +* تقليل ارتباط الأساليب بالفئة الخاصة بها +* مكونات تتصرف بشكل متطابق، مع مراعاة أن هذا الوضع قد يتغير في المستقبل. + +## الاعتمادات + +* [نمط التفويض: ويكيبيديا](https://en.wikipedia.org/wiki/Delegation_pattern) +* [نمط الوكيل: ويكيبيديا](https://en.wikipedia.org/wiki/Proxy_pattern) diff --git a/localization/ar/delegation/etc/delegation.png b/localization/ar/delegation/etc/delegation.png new file mode 100644 index 000000000000..375ef4d6b00f Binary files /dev/null and b/localization/ar/delegation/etc/delegation.png differ diff --git a/localization/ar/dependency-injection/README.md b/localization/ar/dependency-injection/README.md new file mode 100644 index 000000000000..9b45931c6f03 --- /dev/null +++ b/localization/ar/dependency-injection/README.md @@ -0,0 +1,101 @@ +--- +title: Dependency Injection +shortTitle: Dependency Injection +category: Creational +language: ar +tag: + - Decoupling +--- + +## الغرض + +حقن التبعيات هو نمط تصميم برمجي يتم فيه حقن واحدة أو أكثر من التبعيات (أو الخدمات) إلى كائن تابع (أو عميل) وتصبح جزءاً من حالة العميل. يفصل النمط بين إنشاء التبعيات للعميل وسلوكه الخاص، مما يسمح بتصاميم برامج منخفضة الارتباط وتلتزم بمبادئ عكس التحكم والمسؤولية الواحدة. + +## الشرح + +مثال من العالم الحقيقي + +> يحب الساحر العجوز ملء غليونه والتدخين من وقت لآخر. ومع ذلك، لا يريد أن يعتمد على علامة تجارية واحدة من التبغ، بل يحب أن يتمكن من الاستمتاع بها جميعاً بشكل قابل للتبادل. + +بكلمات بسيطة + +> حقن التبعيات يفصل إنشاء التبعيات للعميل عن سلوكه الخاص. + +تقول ويكيبيديا + +> في هندسة البرمجيات، حقن التبعيات هو تقنية يحصل فيها كائن على كائنات أخرى يعتمد عليها. تُسمى هذه الكائنات الأخرى بالتبعيات. + +**مثال برمجي** + +لنقم أولاً بتقديم واجهة التبغ `Tobacco` والعلامات التجارية المحددة. + + +```java +@Slf4j +public abstract class Tobacco { + + public void smoke(Wizard wizard) { + LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), + this.getClass().getSimpleName()); + } +} + +public class SecondBreakfastTobacco extends Tobacco { +} + +public class RivendellTobacco extends Tobacco { +} + +public class OldTobyTobacco extends Tobacco { +} +``` + +التالي هو واجهة `Wizard` وتنفيذها. + + +```java +public interface Wizard { + + void smoke(); +} + +public class AdvancedWizard implements Wizard { + + private final Tobacco tobacco; + + public AdvancedWizard(Tobacco tobacco) { + this.tobacco = tobacco; + } + + @Override + public void smoke() { + tobacco.smoke(this); + } +} +``` + +وأخيراً يمكننا أن نثبت مدى سهولة إعطاء التبغ `Tobacco` لأي علامة تجارية قديمة للساحر. + + +```java + var advancedWizard = new AdvancedWizard(new SecondBreakfastTobacco()); + advancedWizard.smoke(); +``` + +## Class Diagram + +![alt text](./etc/dependency-injection.png "Dependency Injection") + +## Applicability + +Use the Dependency Injection pattern when: + +* You need to eliminate the knowledge of the concrete implementation of the object. +* To allow unit testing of classes in isolation using mock objects or stubs. + +## Credits + +* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871) +* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1) +* [Google Guice: Agile Lightweight Dependency Injection Framework](https://www.amazon.com/gp/product/1590599977/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1590599977&linkId=3b10c90b7ba480a1b7777ff38000f956) diff --git a/localization/ar/dependency-injection/etc/dependency-injection.png b/localization/ar/dependency-injection/etc/dependency-injection.png new file mode 100644 index 000000000000..2a92c9eb228b Binary files /dev/null and b/localization/ar/dependency-injection/etc/dependency-injection.png differ diff --git a/localization/ar/dirty-flag/README.md b/localization/ar/dirty-flag/README.md new file mode 100644 index 000000000000..4c9b5742e942 --- /dev/null +++ b/localization/ar/dirty-flag/README.md @@ -0,0 +1,28 @@ +--- +title: Dirty Flag +shortTitle: Dirty Flag +category: Behavioral +language: ar +tag: + - Game programming + - Performance +--- + +## Also known as +* IsDirty pattern + +## Purpose +Avoid the costly re-acquisition of resources. Resources retain their identity, are stored in some fast-access storage, and are reused to avoid having to acquire them again. + +## Class Diagram +![alt text](./etc/dirty-flag.png "Dirty Flag") + +## Applicability +Use the Dirty Flag pattern when + +* The repetitive acquisition, initialization, and release of the same resource causes unnecessary performance overhead. + +## Credits + +* [Design Patterns: Dirty Flag](https://www.takeupcode.com/podcast/89-design-patterns-dirty-flag/) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) diff --git a/localization/ar/dirty-flag/etc/dirty-flag.png b/localization/ar/dirty-flag/etc/dirty-flag.png new file mode 100644 index 000000000000..98d4f679d17c Binary files /dev/null and b/localization/ar/dirty-flag/etc/dirty-flag.png differ diff --git a/localization/ar/double-buffer/README.md b/localization/ar/double-buffer/README.md new file mode 100644 index 000000000000..46b47a33ef48 --- /dev/null +++ b/localization/ar/double-buffer/README.md @@ -0,0 +1,245 @@ +--- +title: Double Buffer +shortTitle: Double Buffer +category: Behavioral +language: ar +tag: + - Performance + - Game programming +--- + +## الهدف + +الذاكرة المزدوجة هي مصطلح يُستخدم لوصف جهاز يحتوي على مخزنين. استخدام المخازن المتعددة يزيد من الأداء العام للجهاز ويساعد في تجنب الاختناقات. يُظهر هذا المثال استخدام الذاكرة المزدوجة في الرسومات. يُستخدم لعرض صورة أو إطار بينما يتم تخزين إطار آخر في المخزن ليتم عرضه لاحقًا. هذه الطريقة تجعل الرسوم المتحركة والألعاب تبدو أكثر واقعية مقارنة بتلك التي تُعرض باستخدام الذاكرة الفردية. + +## الشرح + +مثال من الحياة الواقعية +> مثال نموذجي، ويجب أن تتعامل معه جميع محركات الألعاب، هو التقديم. عندما يرسم اللعبة العالم الذي يراه المستخدمون، تقوم بذلك قطعة قطعة: الجبال البعيدة، التلال المتدحرجة، الأشجار، كل منها على حدة. إذا رأى المستخدم كيف يتم رسم العرض بشكل تدريجي، فسوف تنكسر الوهم لعالم متماسك. يجب تحديث المشهد بسلاسة وسرعة، مع عرض سلسلة من الإطارات المكتملة، يظهر كل منها فورًا. يحل التقديم المزدوج هذه المشكلة. + +ببساطة +> يضمن حالة يتم عرضها بشكل صحيح بينما يتم تعديل تلك الحالة تدريجيًا. يتم استخدامه كثيرًا في الرسومات الحاسوبية. + +ويكيبيديا تقول +> في علوم الكمبيوتر، التخزين في الذاكرة المتعددة هو استخدام أكثر من مخزن واحد لحمل كتلة من البيانات، بحيث يرى "القارئ" نسخة كاملة (على الرغم من أنها قديمة) من البيانات، بدلاً من نسخة محدثة جزئيًا من البيانات التي يقوم "الكاتب" بإنشائها. يُستخدم ذلك كثيرًا في الصور الحاسوبية. + +**مثال برمجي** + +واجهة `Buffer` التي تضمن الوظائف الأساسية للمخزن. + +```java +/** + * Buffer interface. + */ +public interface Buffer { + + /** + * Clear the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void clear(int x, int y); + + /** + * Draw the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void draw(int x, int y); + + /** + * Clear all the pixels. + */ + void clearAll(); + + /** + * Get all the pixels. + * + * @return pixel list + */ + Pixel[] getPixels(); + +} +``` + +إحدى تطبيقات واجهة `Buffer`. + + +```java +/** + * FrameBuffer implementation class. + */ +public class FrameBuffer implements Buffer { + + public static final int WIDTH = 10; + public static final int HEIGHT = 8; + + private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; + + public FrameBuffer() { + clearAll(); + } + + @Override + public void clear(int x, int y) { + pixels[getIndex(x, y)] = Pixel.WHITE; + } + + @Override + public void draw(int x, int y) { + pixels[getIndex(x, y)] = Pixel.BLACK; + } + + @Override + public void clearAll() { + Arrays.fill(pixels, Pixel.WHITE); + } + + @Override + public Pixel[] getPixels() { + return pixels; + } + + private int getIndex(int x, int y) { + return x + WIDTH * y; + } +} +``` + +```java +/** + * Pixel enum. Each pixel can be white (not drawn) or black (drawn). + */ +public enum Pixel { + + WHITE, BLACK; +} +``` + +`Scene` representa la escena del juego en la que ya se ha renderizado el búfer actual. + +```java +/** + * Scene class. Render the output frame. + */ +@Slf4j +public class Scene { + + private final Buffer[] frameBuffers; + + private int current; + + private int next; + + /** + * Constructor of Scene. + */ + public Scene() { + frameBuffers = new FrameBuffer[2]; + frameBuffers[0] = new FrameBuffer(); + frameBuffers[1] = new FrameBuffer(); + current = 0; + next = 1; + } + + /** + * Draw the next frame. + * + * @param coordinateList list of pixels of which the color should be black + */ + public void draw(List> coordinateList) { + LOGGER.info("Start drawing next frame"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + frameBuffers[next].clearAll(); + coordinateList.forEach(coordinate -> { + var x = coordinate.getKey(); + var y = coordinate.getValue(); + frameBuffers[next].draw(x, y); + }); + LOGGER.info("Swap current and next buffer"); + swap(); + LOGGER.info("Finish swapping"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + } + + public Buffer getBuffer() { + LOGGER.info("Get current buffer: " + current); + return frameBuffers[current]; + } + + private void swap() { + current = current ^ next; + next = current ^ next; + current = current ^ next; + } + +} +``` + +```java +public static void main(String[] args) { + final var scene = new Scene(); + var drawPixels1 = List.of(new MutablePair<>(1, 1), new MutablePair<>(5, 6), new MutablePair<>(3, 2)); + scene.draw(drawPixels1); + var buffer1 = scene.getBuffer(); + printBlackPixelCoordinate(buffer1); + + var drawPixels2 = List.of(new MutablePair<>(3, 7), new MutablePair<>(6, 1)); + scene.draw(drawPixels2); + var buffer2 = scene.getBuffer(); + printBlackPixelCoordinate(buffer2); +} + +private static void printBlackPixelCoordinate(Buffer buffer) { + StringBuilder log = new StringBuilder("Black Pixels: "); + var pixels = buffer.getPixels(); + for (var i = 0; i < pixels.length; ++i) { + if (pixels[i] == Pixel.BLACK) { + var y = i / FrameBuffer.WIDTH; + var x = i % FrameBuffer.WIDTH; + log.append(" (").append(x).append(", ").append(y).append(")"); + } + } + LOGGER.info(log.toString()); +} +``` + +مخرجات وحدة التحكم + + +```text +[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 +[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer +[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 +[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 1 +[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (1, 1) (3, 2) (5, 6) +[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 +[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer +[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 +[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 0 +[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (6, 1) (3, 7) +``` + +## مخطط الفئات + +![alt text](./etc/double-buffer.urm.png "مخطط فئة نمط المخزن المؤقت المزدوج") + +## القابلية للتطبيق + +هذا النمط هو أحد الأنماط التي ستعرف متى تحتاج إليها. إذا كان لديك نظام يفتقر إلى المخزن المؤقت المزدوج، فمن المحتمل أن يبدو بشكل غير صحيح مرئيًا (تمزق الصورة، إلخ) أو سيعمل بشكل غير صحيح. ولكن قول "ستعرف متى تحتاج إليه" لا يعطي الكثير من التوضيح. بشكل أكثر تحديدًا، هذا النمط مناسب عندما يكون كل هذا صحيحًا: + +- لدينا حالة يتم تعديلها بشكل تدريجي. +- يمكن الوصول إلى نفس الحالة في منتصف التعديل. +- نريد تجنب أن يرى الكود الذي يصل إلى الحالة العمل الجاري. +- نريد أن نتمكن من قراءة الحالة دون الحاجة إلى الانتظار أثناء الكتابة. + +## المصادر + +* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html) + diff --git a/localization/ar/double-buffer/etc/double-buffer.urm.png b/localization/ar/double-buffer/etc/double-buffer.urm.png new file mode 100644 index 000000000000..072ec4dad8be Binary files /dev/null and b/localization/ar/double-buffer/etc/double-buffer.urm.png differ diff --git a/localization/ar/embedded-value/README.md b/localization/ar/embedded-value/README.md new file mode 100644 index 000000000000..c147fef4edab --- /dev/null +++ b/localization/ar/embedded-value/README.md @@ -0,0 +1,131 @@ +--- +title: Embedded Value +shortTitle: Embedded Value +category: Structural +language: ar +tag: + - Data Access + - Enterprise Application Pattern +--- + +## معروف أيضًا باسم + +التخصيص المدمج، المُركب + +## الهدف + +العديد من الكائنات الصغيرة تكون منطقية في نظام البرمجة الكائنية ولا تكون منطقية كجداول في قاعدة بيانات. القيمة المدمجة تخصيص قيم كائن إلى حقول سجل الكائن المالك. + +## الشرح + +مثال من الواقع + +> بعض الأمثلة تشمل الكائنات النقدية والفترات الزمنية. على الرغم من أن التفكير الافتراضي هو تخزين كائن كجدول، إلا أنه لا أحد في عقله السليم يرغب في جدول للقيم النقدية. +> مثال آخر هو الطلبات عبر الإنترنت التي تحتوي على عنوان الشحن مثل الشارع، المدينة، الدولة. نقوم بتخصيص هذه القيم من كائن عنوان الشحن إلى حقول سجل كائن الطلب. + +بكلمات بسيطة + +> يسمح نمط القيم المدمجة بتخصيص كائن لعدة حقول في جدول كائن آخر. + +**مثال برمجي** + +لنأخذ مثالًا من طلب عبر الإنترنت حيث لدينا تفاصيل العنصر المطلوب وعنوان الشحن. لدينا +عنوان الشحن مدمج في كائن الطلب. ولكن في قاعدة البيانات نقوم بتخصيص قيم عنوان الشحن في سجل الطلب بدلاً من إنشاء جدول منفصل لعنوان الشحن واستخدام مفتاح خارجي للإشارة إلى كائن الطلب. + +أولاً، لدينا كائنات `Order` و `ShippingAddress`. + + +```java +public class Order { + + private int id; + private String item; + private String orderedBy; + private ShippingAddress ShippingAddress; + + public Order(String item, String orderedBy, ShippingAddress ShippingAddress) { + this.item = item; + this.orderedBy = orderedBy; + this.ShippingAddress = ShippingAddress; + } +} +``` + +```java +public class ShippingAddress { + + private String city; + private String state; + private String pincode; + + public ShippingAddress(String city, String state, String pincode) { + this.city = city; + this.state = state; + this.pincode = pincode; + } +} +``` + +الآن، علينا إنشاء جدول واحد فقط للطلب مع الحقول الخاصة بسمات عنوان الشحن. + + +```Sql +CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50) city VARCHAR(50), state VARCHAR(50), pincode CHAR(6) NOT NULL, PRIMARY KEY(Id)) +``` + +أثناء إجراء الاستفسارات والإدخالات في قاعدة البيانات، نقوم بتغليف وفك تغليف تفاصيل عناوين الشحن. + + +```java +final String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES (?, ?, ?, ?, ?)"; + +public boolean insertOrder(Order order) throws Exception { + var insertOrder = new PreparedStatement(INSERT_ORDER); + var address = order.getShippingAddress(); + conn.setAutoCommit(false); + insertIntoOrders.setString(1, order.getItem()); + insertIntoOrders.setString(2, order.getOrderedBy()); + insertIntoOrders.setString(3, address.getCity()); + insertIntoOrders.setString(4, address.getState()); + insertIntoOrders.setString(5, address.getPincode()); + + var affectedRows = insertIntoOrders.executeUpdate(); + if(affectedRows == 1){ + Logger.info("Inserted successfully"); + }else{ + Logger.info("Couldn't insert " + order); + } +} +``` + +## مخطط الفئات + +![alt text](./etc/embedded-value.urm.png "مخطط فئة القيمة المدمجة") + +## القابلية للتطبيق + +استخدم نمط القيمة المدمجة عندما: + +* تكون العديد من الكائنات الصغيرة ذات معنى في نظام OO ولكن ليس لها معنى كجداول في قاعدة بيانات. +* الحالات الأبسط للقيمة المدمجة هي الكائنات ذات القيم الواضحة والبسيطة مثل المال ونطاق التواريخ. +* إذا كنت تقوم بربط إلى مخطط موجود، يمكنك استخدام هذا النمط عندما تحتوي إحدى الجداول على بيانات ترغب في تقسيمها إلى أكثر من كائن في الذاكرة. قد يحدث هذا عندما ترغب في استخراج بعض السلوك في نموذج الكائنات. +* في معظم الحالات، ستستخدم القيمة المدمجة فقط في كائن مرجعي عندما تكون العلاقة بينهما ذات قيمة واحدة في كلا الطرفين (علاقة واحد إلى واحد). + +## الدروس التعليمية + +* [Dzone](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-3) +* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) +* [Five's Weblog](https://powerdream5.wordpress.com/2007/10/09/embedded-value/) + +## العواقب + +* الميزة الكبرى للقيمة المدمجة هي أنها تسمح بإجراء استفسارات SQL ضد قيم الكائن التابع. +* الكائن ذو القيمة المدمجة ليس له سلوك استمرارية. +* عند استخدام هذا، يجب أن تكون حذرًا من أن أي تغيير في التابع سيجعل المالك يعتبره "متسخًا" — وهو ما لا يمثل مشكلة مع الكائنات ذات القيمة التي يتم استبدالها في المالك. +* مشكلة أخرى هي التحميل والحفظ. إذا كنت تقوم بتحميل ذاكرة الكائن المدمج فقط عندما تقوم بتحميل المالك، فهذا حجة لحفظ كلاهما في نفس الجدول. +* قضية أخرى هي ما إذا كنت ترغب في الوصول إلى بيانات الكائنات المدمجة بشكل منفصل عبر SQL. قد يكون هذا مهمًا إذا كنت تقوم بإعداد تقارير عبر SQL وليس لديك قاعدة بيانات منفصلة للتقارير. + +## الائتمان + +* [Fowler, Martin - Patterns of enterprise application architecture-Addison-Wesley](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) +* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) diff --git a/embedded-value/etc/embedded-value.urm.png b/localization/ar/embedded-value/etc/embedded-value.urm.png similarity index 100% rename from embedded-value/etc/embedded-value.urm.png rename to localization/ar/embedded-value/etc/embedded-value.urm.png diff --git a/localization/ar/event-aggregator/README.md b/localization/ar/event-aggregator/README.md new file mode 100644 index 000000000000..03eae11fbe3c --- /dev/null +++ b/localization/ar/event-aggregator/README.md @@ -0,0 +1,167 @@ +--- +title: Event Aggregator +shortTitle: Event Aggregator +category: Structural +language: ar +tag: + - Reactive +--- + +## الاسم + +مجمع الأحداث + +## النية + +قد يصبح النظام الذي يحتوي على العديد من الكائنات معقدًا عندما يريد العميل الاشتراك في الأحداث. يجب على العميل +البحث والتسجيل في +كل كائن على حدة، وإذا كان كل كائن يحتوي على العديد من الأحداث فإن كل حدث يتطلب اشتراكًا +منفصلًا. يعمل مجمع الأحداث كمصدر واحد +للأحداث للعديد من الكائنات. يقوم بالتسجيل في جميع الأحداث للكائنات المتعددة مما يسمح للعملاء +بالتسجيل فقط مع المجمع. + +## الشرح + +مثال واقعي + +> يجلس الملك جوفري على عرش الحديد ويحكم الممالك السبع في وينترفل. يحصل على معظم معلوماته الهامة من يد الملك، +> وهو ثاني أقوى شخص في المملكة. يد الملك لديها العديد من المستشارين المقربين الذين يوفرون له معلومات هامة حول الأحداث التي تحدث في المملكة. + +بكلمات بسيطة + +> مجمع الأحداث هو وسيط للأحداث يقوم بجمع الأحداث من مصادر متعددة وتقديمها للمراقبين المسجلين. + +**مثال برمجي** + +في مثالنا البرمجي، نعرض تنفيذ نمط مجمع الأحداث. بعض الكائنات +هي مستمعون للأحداث، وبعضها الآخر هو مرسلو أحداث، والمجمع للأحداث يقوم بكلتا الوظيفتين. + + +```java +public interface EventObserver { + void onEvent(Event e); +} + +public abstract class EventEmitter { + + private final Map> observerLists; + + public EventEmitter() { + observerLists = new HashMap<>(); + } + + public final void registerObserver(EventObserver obs, Event e) { + ... + } + + protected void notifyObservers(Event e) { + ... + } +} +``` + +`KingJoffrey` يستمع إلى أحداث `KingsHand`. + + +```java +@Slf4j +public class KingJoffrey implements EventObserver { + @Override + public void onEvent(Event e) { + LOGGER.info("Received event from the King's Hand: {}", e.toString()); + } +} +``` + +`ReyMano` يستمع إلى الأحداث من مرؤوسيه `LordBaelish`، `LordVarys` و `Scout`. +ما يسمعه منهم، ينقله إلى "الملك جوفري". + + +```java +public class KingsHand extends EventEmitter implements EventObserver { + + public KingsHand() { + } + + public KingsHand(EventObserver obs, Event e) { + super(obs, e); + } + + @Override + public void onEvent(Event e) { + notifyObservers(e); + } +} +``` + +على سبيل المثال، `LordVarys` يجد خائنًا كل يوم أحد ويقوم بإبلاغ `KingsHand`. + + +```java +@Slf4j +public class LordVarys extends EventEmitter implements EventObserver { + @Override + public void timePasses(Weekday day) { + if (day == Weekday.SATURDAY) { + notifyObservers(Event.TRAITOR_DETECTED); + } + } +} +``` + +الجزء التالي يوضح كيفية بناء وربط الكائنات. + + +```java + var kingJoffrey = new KingJoffrey(); + + var kingsHand = new KingsHand(); + kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED); + kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED); + kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING); + kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED); + + var varys = new LordVarys(); + varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED); + varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED); + + var scout = new Scout(); + scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING); + scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED); + + var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); + + var emitters = List.of( + kingsHand, + baelish, + varys, + scout + ); + + Arrays.stream(Weekday.values()) + .>map(day -> emitter -> emitter.timePasses(day)) + .forEachOrdered(emitters::forEach); +``` + +الناتج في وحدة التحكم بعد تنفيذ المثال. + +``` +18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected +``` + +## قابلية الاستخدام + +استخدم نمط "مجمّع الأحداث" عندما + +* يعد مجمّع الأحداث خيارًا جيدًا عندما يكون لديك العديد من الكائنات التي تعد مصادر محتملة للأحداث. بدلاً من جعل المراقب يتعامل مع التسجيل مع كل منها، يمكنك مركزية منطق التسجيل في مجمّع الأحداث. بالإضافة إلى تبسيط التسجيل، يبسط مجمّع الأحداث أيضًا مشكلات إدارة الذاكرة في استخدام المراقبين. + +## الأنماط المتعلقة + +* [Observer](https://java-design-patterns.com/patterns/observer/) + +## الاعتمادات + +* [مارتن فاولر - مجمّع الأحداث](http://martinfowler.com/eaaDev/EventAggregator.html) diff --git a/localization/ar/event-aggregator/etc/classes.png b/localization/ar/event-aggregator/etc/classes.png new file mode 100644 index 000000000000..295719ea3712 Binary files /dev/null and b/localization/ar/event-aggregator/etc/classes.png differ diff --git a/localization/ar/extension-objects/README.md b/localization/ar/extension-objects/README.md new file mode 100644 index 000000000000..0f96c8a0a516 --- /dev/null +++ b/localization/ar/extension-objects/README.md @@ -0,0 +1,149 @@ +--- +title: Extension objects +shortTitle: Extension objects +category: Behavioral +language: ar +tag: + - Extensibility +--- + +# نمط كائنات التوسيع (Extension Objects Pattern) + +## الهدف +التنبؤ بأنه يجب توسيع واجهة كائن ما في المستقبل. يتم تعريف الواجهات الإضافية من خلال كائنات التوسيع (Extension objects). + +## الشرح +مثال واقعي + +> افترض أنك تطور لعبة تعتمد على Java لعميل، وأثناء عملية التطوير، يُقترح عليك إضافة وظائف جديدة. يسمح لك نمط كائنات التوسيع بتكييف برنامجك مع التغييرات غير المتوقعة مع الحد الأدنى من إعادة الهيكلة، خاصة عند دمج وظائف إضافية في مشروعك. + +ببساطة + +> يتم استخدام نمط كائنات التوسيع لإضافة وظائف ديناميكيًا إلى الكائنات دون تعديل فئاتها الرئيسية. إنه نمط تصميم سلوكي يستخدم لإضافة وظائف جديدة إلى الفئات والكائنات الموجودة داخل البرنامج. يوفر هذا النمط للمبرمجين القدرة على تمديد/تعديل وظائف الفئات دون الحاجة إلى إعادة هيكلة الكود المصدر الحالي. + +تقول ويكيبيديا + +> في البرمجة الكائنية التوجه، يعتبر نمط كائنات التوسيع نمط تصميم يضاف إلى كائن بعد أن يتم تجميع الكائن الأصلي. الكائن المعدل غالبًا ما يكون فئة أو نموذجًا أو نوعًا. تعتبر أنماط كائنات التوسيع سمة من سمات بعض لغات البرمجة الكائنية التوجه. لا يوجد فرق نحوي بين استدعاء طريقة توسيع وطريقة معرفة في تعريف النوع. + +**مثال برمجي** + +الهدف من استخدام نمط كائنات التوسيع (Extension objects) هو تنفيذ ميزات/وظائف جديدة دون الحاجة إلى إعادة هيكلة كل فئة. +توضح الأمثلة التالية استخدام هذا النمط في فئة "العدو" التي تمتد من "الكيان" داخل لعبة: + +الفئة الرئيسية للتطبيق التي يتم تشغيل برنامجنا منها. + + +```java +public class App { + public static void main(String[] args) { + Entity enemy = new Enemy("Enemy"); + checkExtensionsForEntity(enemy); + } + + private static void checkExtensionsForEntity(Entity entity) { + Logger logger = Logger.getLogger(App.class.getName()); + String name = entity.getName(); + Function func = (e) -> () -> logger.info(name + " without " + e); + + String extension = "EnemyExtension"; + Optional.ofNullable(entity.getEntityExtension(extension)) + .map(e -> (EnemyExtension) e) + .ifPresentOrElse(EnemyExtension::extendedAction, func.apply(extension)); + } +} +``` +فئة العدو مع الإجراءات الأولية والتوسعات. + + +```java +class Enemy extends Entity { + public Enemy(String name) { + super(name); + } + + @Override + protected void performInitialAction() { + super.performInitialAction(); + System.out.println("Enemy wants to attack you."); + } + + @Override + public EntityExtension getEntityExtension(String extensionName) { + if (extensionName.equals("EnemyExtension")) { + return Optional.ofNullable(entityExtension).orElseGet(EnemyExtension::new); + } + return super.getEntityExtension(extensionName); + } +} +``` + +فئة EnemyExtension مع إعادة كتابة طريقة extendAction(). + + +```java +class EnemyExtension implements EntityExtension { + @Override + public void extendedAction() { + System.out.println("Enemy has advanced towards you!"); + } +} +``` + +فئة الكائن التي سيتم توسيعها بواسطة العدو. + + +```java +class Entity { + private String name; + protected EntityExtension entityExtension; + + public Entity(String name) { + this.name = name; + performInitialAction(); + } + + protected void performInitialAction() { + System.out.println(name + " performs the initial action."); + } + + public EntityExtension getEntityExtension(String extensionName) { + return null; + } + + public String getName() { + return name; + } +} +``` +واجهة `EntityExtension` التي ستستخدم `EnemyExtension`. + + +```java +interface EntityExtension { + void extendedAction(); +} +``` +إخراج البرنامج: + + +```markdown +Enemy performs the initial action. +Enemy wants to attack you. +Enemy has advanced towards you! +``` +في هذا المثال، يسمح نمط "كائنات التمديد" (Extension Objects) لكي يقوم الكائن العدو بتنفيذ إجراءات مبدئية فريدة وإجراءات متقدمة عند تطبيق التمديدات الخاصة. يوفر هذا النمط مرونة وقابلية تمديد لقاعدة الشيفرة مع تقليل الحاجة إلى إجراء تغييرات كبيرة في الشيفرة. + +## مخطط الفئات +![Extension_objects](./etc/extension_obj.png "Extension objects") + +## قابلية التطبيق +استخدم نمط "كائنات التمديد" (Extension objects) عندما: + +* تحتاج إلى دعم إضافة واجهات جديدة أو غير متوقعة إلى فئات موجودة ولا تريد التأثير على العملاء الذين لا يحتاجون إلى هذه الواجهة الجديدة. تتيح لك كائنات التمديد الاحتفاظ بالعمليات ذات الصلة معًا عن طريق تعريفها في فئة منفصلة. +* فئة تمثل تجريدًا رئيسيًا تؤدي وظائف مختلفة لعملاء مختلفين. يجب أن يكون عدد الوظائف التي يمكن أن تؤديها الفئة غير محدود. من الضروري الحفاظ على التجريد الرئيسي نفسه. على سبيل المثال، يبقى كائن العميل كائن عميل رغم أن الأنظمة الفرعية المختلفة قد تراها بطريقة مختلفة. +* يجب أن تكون الفئة قابلة للتمديد بسلوكيات جديدة دون الحاجة إلى تصنيفها منها. + +## أمثلة من العالم الحقيقي + +* [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc) +* [ربط الكائنات وإدماجها](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding) diff --git a/localization/ar/extension-objects/etc/extension_obj.png b/localization/ar/extension-objects/etc/extension_obj.png new file mode 100644 index 000000000000..a2b750e9dedd Binary files /dev/null and b/localization/ar/extension-objects/etc/extension_obj.png differ diff --git a/localization/ar/facade/README.md b/localization/ar/facade/README.md new file mode 100644 index 000000000000..99fa3bd04e9f --- /dev/null +++ b/localization/ar/facade/README.md @@ -0,0 +1,216 @@ +--- +title: Facade +shortTitle: Facade +category: Structural +language: ar +tag: + - Gang Of Four + - Decoupling +--- + +## الهدف + +توفير واجهة موحدة لمجموعة من واجهات النظام الفرعي. تقوم الواجهة البسيطة بتحديد واجهة تسهل استخدام النظام الفرعي. + +## الشرح + +مثال واقعي + +> كيف يعمل منجم الذهب؟ "حسنًا، ينزل العمال لاستخراج الذهب!" تقول. هذا ما تعتقده لأنك تستخدم واجهة بسيطة يوفرها منجم الذهب، ولكن من الداخل يجب أن يقوم بالكثير من الأشياء لتحقيق ذلك. هذه الواجهة البسيطة للنظام الفرعي المعقد هي الواجهة الأمامية. + +بإيجاز + +> يوفر نمط الواجهة الأمامية واجهة مبسطة لنظام فرعي معقد. + +تقول ويكيبيديا + +> الواجهة الأمامية هي كائن يوفر واجهة مبسطة لجسم من الكود الأكبر، مثل مكتبة الفصول. + +**مثال برمجي** + +لنأخذ مثال منجم الذهب. هنا لدينا تسلسل عمال الأقزام في المنجم. أولاً، لدينا فئة أساسية `DwarvenMineWorker`: + + +```java + +@Slf4j +public abstract class DwarvenMineWorker { + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP -> goToSleep(); + case WAKE_UP -> wakeUp(); + case GO_HOME -> goHome(); + case GO_TO_MINE -> goToMine(); + case WORK -> work(); + default -> LOGGER.info("Undefined action"); + } + } + + public void action(Action... actions) { + Arrays.stream(actions).forEach(this::action); + } + + public abstract void work(); + + public abstract String name(); + + enum Action { + GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK + } +} +``` + +ثم لدينا الفئات المحددة للأقزام `DwarvenTunnelDigger`، `DwarvenGoldDigger` و `DwarvenCartOperator`: + + +```java +@Slf4j +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} + +@Slf4j +public class DwarvenGoldDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} + +@Slf4j +public class DwarvenCartOperator extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} + +``` + +لإدارة جميع هؤلاء العمال في منجم الذهب، لدينا `FachadaDwarvenGoldmine`: + + +```java +public class DwarvenGoldmineFacade { + + private final List workers; + + public DwarvenGoldmineFacade() { + workers = List.of( + new DwarvenGoldDigger(), + new DwarvenCartOperator(), + new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions(Collection workers, + DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } +} +``` + +الآن سنستخدم الواجهة: + + +```java +var facade = new DwarvenGoldmineFacade(); +facade.startNewDay(); +facade.digOutGold(); +facade.endDay(); +``` + +إخراج البرنامج: + + +```java +// Dwarf gold digger wakes up. +// Dwarf gold digger goes to the mine. +// Dwarf cart operator wakes up. +// Dwarf cart operator goes to the mine. +// Dwarven tunnel digger wakes up. +// Dwarven tunnel digger goes to the mine. +// Dwarf gold digger digs for gold. +// Dwarf cart operator moves gold chunks out of the mine. +// Dwarven tunnel digger creates another promising tunnel. +// Dwarf gold digger goes home. +// Dwarf gold digger goes to sleep. +// Dwarf cart operator goes home. +// Dwarf cart operator goes to sleep. +// Dwarven tunnel digger goes home. +// Dwarven tunnel digger goes to sleep. +``` + +## مخطط الفئات + +![alt text](./etc/facade.urm.png "مخطط فئة نمط الواجهة") + +## قابلية التطبيق + +استخدم نمط الواجهة عندما: + +* ترغب في توفير واجهة بسيطة إلى نظام فرعي معقد. غالبًا ما تصبح الأنظمة الفرعية أكثر تعقيدًا مع تطورها. معظم الأنماط، عند تطبيقها، تؤدي إلى المزيد من الفئات الأصغر. هذا يجعل النظام الفرعي أكثر قابلية لإعادة الاستخدام وأسهل في التخصيص، ولكن أيضًا يصبح أكثر صعوبة في الاستخدام للعملاء الذين لا يحتاجون إلى تخصيصه. يمكن أن توفر الواجهة عرضًا بسيطًا افتراضيًا للنظام الفرعي الذي يكفي لمعظم العملاء. فقط العملاء الذين يحتاجون إلى مزيد من التخصيص سيضطرون للنظر إلى ما وراء الواجهة. +* هناك العديد من الاعتمادات بين العملاء وفئات تنفيذ التجريد. إدخال واجهة لفصل النظام الفرعي عن العملاء والأنظمة الفرعية الأخرى، مما يعزز الاستقلالية وقابلية النقل للنظام الفرعي. +* ترغب في تقسيم أنظمتك الفرعية. استخدم واجهة لتعريف نقطة دخول إلى كل مستوى من النظام الفرعي. إذا كانت الأنظمة الفرعية تعتمد على بعضها البعض، يمكنك تبسيط الاعتمادات بينها من خلال جعلها تتواصل مع بعضها البعض فقط من خلال واجهاتها. + +## الدروس التعليمية + +* [DigitalOcean](https://www.digitalocean.com/community/tutorials/facade-design-pattern-in-java) + +* [Refactoring Guru](https://refactoring.guru/design-patterns/facade) +* [GeekforGeeks](https://www.geeksforgeeks.org/facade-design-pattern-introduction/) +* [Tutorialspoint](https://www.tutorialspoint.com/design_pattern/facade_pattern.htm) + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/ar/facade/etc/facade.urm.png b/localization/ar/facade/etc/facade.urm.png new file mode 100644 index 000000000000..8e3ec7aca45e Binary files /dev/null and b/localization/ar/facade/etc/facade.urm.png differ diff --git a/localization/ar/factory/README.md b/localization/ar/factory/README.md new file mode 100644 index 000000000000..5241065e3590 --- /dev/null +++ b/localization/ar/factory/README.md @@ -0,0 +1,140 @@ +--- +title: Factory +shortTitle: Factory +category: Creational +language: ar +tag: + - Gang of Four +--- + +## أيضًا معروف بـ + +* المصنع البسيط +* طريقة المصنع الثابتة + +## الهدف + +توفير طريقة ثابتة محاطة في فئة تسمى المصنع (Factory)، لإخفاء منطق التنفيذ وجعل شفرة العميل تركز على الاستخدام بدلاً من تهيئة الكائنات الجديدة. + +## الشرح + +مثال من الحياة الواقعية + +> تخيل كيميائيًا على وشك تصنيع العملات. يجب أن يكون الكيميائي قادرًا على إنشاء عملات ذهبية وكذلك عملات نحاسية، ويجب أن يكون من الممكن التبديل بينهما دون تعديل الكود المصدري الحالي. يجعل نمط المصنع هذا ممكنًا من خلال توفير طريقة بناء ثابتة يمكن استدعاؤها مع المعلمات ذات الصلة. + +تقول ويكيبيديا + +> المصنع (Factory) هو كائن لإنشاء كائنات أخرى: بشكل رسمي، المصنع هو دالة أو طريقة تُرجع كائنات من نموذج أو فئة متغيرة. + +**مثال برمجي** + +لدينا واجهة عملة `Coin` واثنان من تطبيقات العملة: عملة ذهبية `GoldCoin` وعملة نحاسية `CopperCoin`. + + +```java +public interface Coin { + String getDescription(); +} + +public class GoldCoin implements Coin { + + static final String DESCRIPTION = "This is a gold coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +public class CopperCoin implements Coin { + + static final String DESCRIPTION = "This is a copper coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +تُمثل التعداد التالي أنواع العملات التي نقبلها (`GoldCoin` و `CopperCoin`). + + +```java +@RequiredArgsConstructor +@Getter +public enum CoinType { + + COPPER(CopperCoin::new), + GOLD(GoldCoin::new); + + private final Supplier constructor; +} +``` + +ثم لدينا الطريقة الثابتة للحصول على العملة `getCoin` لإنشاء كائنات العملة مغلفة داخل فئة المصنع `CoinFactory`. + + +```java +public class CoinFactory { + + public static Coin getCoin(CoinType type) { + return type.getConstructor().get(); + } +} +``` + +الآن في كود العميل يمكننا إنشاء أنواع مختلفة من العملات باستخدام فئة المصنع. + + +```java +LOGGER.info("The alchemist begins his work."); +var coin1 = CoinFactory.getCoin(CoinType.COPPER); +var coin2 = CoinFactory.getCoin(CoinType.GOLD); +LOGGER.info(coin1.getDescription()); +LOGGER.info(coin2.getDescription()); +``` + +### مخرجات البرنامج: + + +```java +The alchemist begins his work. +This is a copper coin. +This is a gold coin. +``` + +## المخطط البياني للفئات + +![alt text](./etc/factory.urm.png "Factory pattern diagrama de clases") + +## قابلية الاستخدام + +استخدم نمط المصنع (Factory) عندما يكون تركيزك على إنشاء الكائنات فقط دون القلق حول كيفية إنشائها وإدارتها. + +### المزايا + +* يسمح بجمع كل عمليات إنشاء الكائنات في مكان واحد وتجنب نشر الكلمة الأساسية 'new' عبر قاعدة الشيفرة. +* يُمكنك من كتابة شيفرة منخفضة الارتباط. من بين مزاياه الرئيسية: قابلية أفضل للاختبار، شيفرة سهلة الفهم، مكونات قابلة للاستبدال، قابلية التوسع، وميزات معزولة. + +### العيوب + +* قد يصبح الشيفرة أكثر تعقيدًا مما ينبغي. + +## أمثلة استخدام معروفة + +* [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) + (يعيد كائنات Singleton مختلفة استنادًا إلى بروتوكول معين) +* [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E)) +* [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) + وأيضًا طرق مشابهة أخرى. + +## الأنماط المرتبطة + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/) diff --git a/localization/ar/factory/etc/factory.urm.png b/localization/ar/factory/etc/factory.urm.png new file mode 100644 index 000000000000..4b3420792e06 Binary files /dev/null and b/localization/ar/factory/etc/factory.urm.png differ diff --git a/localization/de/README.md b/localization/de/README.md index 0e33ffbfa712..b727e6ba47e1 100644 --- a/localization/de/README.md +++ b/localization/de/README.md @@ -1,69 +1,48 @@ - - # In Java implementierte Entwurfsmuster ![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg) -[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) -[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) -[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Lizenz MIT](https://img.shields.io/badge/lizenz-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) +[![Codezeilen](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Abdeckung](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Chat beitreten unter https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![All Contributors](https://img.shields.io/badge/all_contributors-208-orange.svg?style=flat-square)](#contributors-)
-In einer anderen Sprache lesen : [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md), [**de**](localization/de/README.md), [**ja**](localization/ja/README.md) +In anderen Sprachen lesen: [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md), [**de**](localization/de/README.md), [**ja**](localization/ja/README.md)
# Einführung -Entwurfschemas sind die besten formalisierten Praktiken, die ein Programmierer verwenden kann, -um allgemeine Probleme beim Entwurf einer Anwendung oder eines Systems zu lösen. +Entwurfsmuster sind bewährte Lösungen, die Entwickler nutzen können, um häufige Probleme beim Entwurf von Anwendungen oder Systemen zu lösen. -Entwurfschemas können den Entwicklungsprozess beschleunigen, indem sie getestete und bewährte -Entwicklungsparadigmen bereitstellen. +Sie helfen dabei, den Entwicklungsprozess zu beschleunigen, indem sie erprobte und zuverlässige Ansätze bereitstellen. -Die Wiederverwendung von Entwurfschemas hilft, subtile Fehler zu vermeiden, die größere -Probleme verursachen können, sowie die Lesbarkeit des Codes für Programmierer und Architekten zu verbessern, -welche mit den Prinzipien der Entwurfsmuster vertraut sind. +Die Wiederverwendung von Entwurfsmustern verhindert subtile Fehler, die zu größeren Problemen führen können, und verbessert die Lesbarkeit des Codes – besonders für Entwickler und Architekten, die mit diesen Mustern vertraut sind. # Erste Schritte -Auf dieser Seite werden Java Entwurfschemas vorgestellt. Die Lösungen wurden entwickelt von -erfahrenen Programmierern und Architekten aus der Open-Source-Gemeinschaft. Die -Schemas können anhand ihrer übergeordneten Beschreibungen oder anhand ihres -Quellcodes durchsucht werden. Die Quellcode-Beispiele sind gut kommentiert und können als -Programmiertutorials zur Implementierung eines bestimmten Schemas angesehen werden. Wir verwenden die am besten -bekannten und erprobtesten Open-Source Java-Technologien. +Diese Seite stellt Java-Entwurfsmuster vor. Die Lösungen wurden von erfahrenen Entwicklern und Architekten aus der Open-Source-Community erstellt. Die Muster können entweder durch ihre Beschreibungen oder durch den Quellcode erkundet werden. Die Codebeispiele sind gut kommentiert und eignen sich als Tutorials, um die Muster zu verstehen und umzusetzen. Wir verwenden dabei bekannte und bewährte Open-Source-Java-Technologien. -Bevor Sie in die Materie der Entwurfschemas eintauchen, sollten sie sich mit den verschiednen -[Software-Entwurfsprinzipien](https://java-design-patterns.com/principles/) auseinandersetzen. +Bevor Sie sich mit den Entwurfsmustern beschäftigen, sollten Sie sich mit den grundlegenden [Software-Entwurfsprinzipien](https://java-design-patterns.com/principles/) vertraut machen. -Alle Entwürfe sollten so einfach wie möglich gehalten werden. Sie sollten mit KISS, YAGNI, -und Do The Simplest Thing That Could Possibly Work prinzipen anfangen. Komplexe Entwurfschemas sollen nur eingesetzt werden, wenn diese für sinnvolle Erweiterungen benötigt werden. +Entwürfe sollten immer so einfach wie möglich gehalten werden. Beginnen Sie mit den Prinzipien KISS (Keep It Simple, Stupid), YAGNI (You Aren’t Gonna Need It) und "Do The Simplest Thing That Could Possibly Work". Komplexe Muster sollten nur dann verwendet werden, wenn sie wirklich notwendig sind. -Sobald sie mit diesen Konzepten vertraut sind, können sie beginnen, sich mit den [verfügbaren Entwurfschemas](https://java-design-patterns.com/patterns/) auseinanderzusetzen, durch einen -der folgenden Ansätze +Sobald Sie mit diesen Konzepten vertraut sind, können Sie sich die [verfügbaren Entwurfsmuster](https://java-design-patterns.com/patterns/) ansehen. Dafür gibt es verschiedene Ansätze: - - Nach einem bestimmten Schema anhand des Namens suchen. - Sie können keins finden? Bitte melden sie ein neues Schema [hier](https://github.com/iluwatar/java-design-patterns/issues). - - Verwendung von Tags wie `Performance`, `Gang of Four` oder `Data access`. - - Verwendung von Entwurfschema-Kategorien wie `Creational`, `Behavioral` und andere. +- Suchen Sie nach einem bestimmten Muster anhand des Namens. Fehlt ein Muster? Melden Sie es gerne [hier](https://github.com/iluwatar/java-design-patterns/issues). +- Nutzen Sie Tags wie `Performance`, `Gang of Four` oder `Data access`. +- Durchsuchen Sie die Muster nach Kategorien wie `Creational`, `Behavioral` und anderen. -Hoffentlich finden sie die auf dieser Website vorgestellten objektorientierten Lösungen -für ihre Architekturen nützlich und dass sie genauso viel Spaß beim Lernen haben, wie wir bei ihrer Entwicklung hatten. +Wir hoffen, dass Sie die hier vorgestellten Lösungen für Ihre Projekte nützlich finden und genauso viel Spaß beim Lernen haben, wie wir bei der Entwicklung hatten. -# Wie man bei diesem Projekt mitwirken kann +# Mitwirken -Wenn sie zu dem Projekt beitragen wollen, finden sie die entsprechenden Informationen in -unserem [Entwickler-Wiki](https://github.com/iluwatar/java-design-patterns/wiki).mWir helfen Ihnen -gerne und beantworten ihre Fragen im [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns). +Wenn Sie zum Projekt beitragen möchten, finden Sie alle notwendigen Informationen in unserem [Entwickler-Wiki](https://github.com/iluwatar/java-design-patterns/wiki). Bei Fragen helfen wir Ihnen gerne im [Gitter-Chatraum](https://gitter.im/iluwatar/java-design-patterns) weiter. # Lizenz -Dieses Projekt steht unter den Bedingungen der MIT-Lizenz. - +Dieses Projekt steht unter der MIT-Lizenz. \ No newline at end of file diff --git a/localization/de/abstract-document/README.md b/localization/de/abstract-document/README.md new file mode 100644 index 000000000000..13f8d26fbbbf --- /dev/null +++ b/localization/de/abstract-document/README.md @@ -0,0 +1,226 @@ +--- +title: "Abstract Document Pattern in Java: Vereinfachung der Datenverwaltung mit Flexibilität" +shortTitle: Abstract Document +description: "Erkunden Sie das Abstract Document Design Pattern in Java. Lernen Sie seine Absicht, Erklärung, Anwendbarkeit, Vorteile kennen und sehen Sie reale Beispiele zur Implementierung flexibler und dynamischer Datenstrukturen." +category: Strukturell +language: de +tag: + - Abstraktion + - Entkopplung + - Dynamische Typisierung + - Kapselung + - Erweiterbarkeit + - Polymorphismus +--- + +## Absicht des Abstract Document Design Patterns + +Das Abstract Document Design Pattern in Java ist ein wichtiges strukturelles Design Pattern, das eine konsistente Möglichkeit bietet, hierarchische und baumartige Datenstrukturen zu handhaben, indem es eine gemeinsame Schnittstelle für verschiedene Dokumenttypen definiert. Es trennt die Kernstruktur des Dokuments von spezifischen Datenformaten und ermöglicht dynamische Aktualisierungen und vereinfachte Wartung. + +## Detaillierte Erklärung des Abstract Document Patterns mit realen Beispielen + +Das Abstract Document Design Pattern in Java ermöglicht die dynamische Handhabung nicht-statischer Eigenschaften. Dieses Pattern verwendet das Konzept der Traits, um Typsicherheit zu gewährleisten und Eigenschaften verschiedener Klassen in eine Menge von Schnittstellen zu trennen. + +Reales Beispiel + +> Betrachten Sie ein Bibliothekssystem, das das Abstract Document Design Pattern in Java implementiert, wo Bücher verschiedene Formate und Attribute haben können: physische Bücher, eBooks und Hörbücher. Jedes Format hat einzigartige Eigenschaften, wie Seitenzahl für physische Bücher, Dateigröße für eBooks und Dauer für Hörbücher. Das Abstract Document Design Pattern ermöglicht es dem Bibliothekssystem, diese verschiedenen Formate flexibel zu verwalten. Durch die Verwendung dieses Patterns kann das System Eigenschaften dynamisch speichern und abrufen, ohne dass eine starre Struktur für jeden Buchtyp erforderlich ist, was es einfacher macht, neue Formate oder Attribute in der Zukunft hinzuzufügen, ohne dass wesentliche Änderungen am Codebase erforderlich sind. + +In einfachen Worten + +> Das Abstract Document Pattern ermöglicht das Anhängen von Eigenschaften an Objekte, ohne dass diese davon wissen. + +Wikipedia sagt + +> Ein objektorientiertes strukturelles Design Pattern zur Organisation von Objekten in schwach typisierten Schlüssel-Wert-Speichern und zur Bereitstellung der Daten über typisierte Ansichten. Der Zweck des Patterns besteht darin, einen hohen Grad an Flexibilität zwischen Komponenten in einer stark typisierten Sprache zu erreichen, in der neue Eigenschaften zur Objektstruktur dynamisch hinzugefügt werden können, ohne die Unterstützung der Typsicherheit zu verlieren. Das Pattern verwendet Traits, um verschiedene Eigenschaften einer Klasse in verschiedene Schnittstellen zu trennen. + +## Programmatisches Beispiel des Abstract Document Patterns in Java + +Betrachten Sie ein Auto, das aus mehreren Teilen besteht. Wir wissen jedoch nicht, ob das spezifische Auto wirklich alle Teile hat oder nur einige davon. Unsere Autos sind dynamisch und extrem flexibel. + +Lassen Sie uns zunächst die Basisklassen `Document` und `AbstractDocument` definieren. Sie sorgen im Wesentlichen dafür, dass das Objekt eine Eigenschaftsmap und eine beliebige Anzahl von Kindobjekten enthält. + +```java +public interface Document { + + Void put(String key, Object value); + + Object get(String key); + + Stream children(String key, Function, T> constructor); +} + +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map 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 Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + + // Andere Eigenschaften und Methoden... +} +``` +Als nächstes definieren wir ein Enum Property und eine Menge von Schnittstellen für Typ, Preis, Modell und Teile. Dies ermöglicht es uns, eine statisch aussehende Schnittstelle für unsere Car-Klasse zu erstellen. + +```java +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} + +public interface HasType extends Document { + + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } +} + +public interface HasPrice extends Document { + + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } +} + +public interface HasModel extends Document { + + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } +} + +public interface HasParts extends Document { + + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} +``` + +Jetzt sind wir bereit, das `Car` einzuführen. + +```java + public static void main(String[] args) { + LOGGER.info("Konstruktion von Teilen und Auto"); + + 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 carProperties = Map.of( + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); + + var car = new Car(carProperties); + + LOGGER.info("Hier ist unser Auto:"); + LOGGER.info("-> Modell: {}", car.getModel().orElseThrow()); + LOGGER.info("-> Preis: {}", car.getPrice().orElseThrow()); + LOGGER.info("-> Teile: "); + car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}", + p.getType().orElse(null), + p.getModel().orElse(null), + p.getPrice().orElse(null)) + ); +} +``` +Die Programmausgabe: + +``` +07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Konstruktion von Teilen und Auto +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Hier ist unser Auto: +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- -> Modell: 300SL +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> Preis: 10000 +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> Teile: +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- Rad/15C/100 +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- Tür/Lambo/300 +``` +## Abstract Document Pattern Klassendiagramm + +![Abstract Document](./etc/abstract-document.png "Abstract Document Traits und Domain") + +## Wann sollte das Abstract Document Pattern in Java verwendet werden? + +Das Abstract Document Design Pattern ist besonders vorteilhaft in Szenarien, die eine Verwaltung unterschiedlicher Dokumenttypen in Java erfordern, die einige gemeinsame Attribute oder Verhaltensweisen teilen, aber auch einzigartige Attribute oder Verhaltensweisen haben, die spezifisch für ihren Typ sind. Hier sind einige Szenarien, in denen das Abstract Document Design Pattern anwendbar ist: + +* **Content-Management-Systeme (CMS)**: In einem CMS könnten verschiedene Arten von Inhalten wie Artikel, Bilder, Videos usw. vorkommen. Jede Inhaltsart könnte gemeinsame Attribute wie Erstellungsdatum, Autor und Tags haben, aber auch spezifische Attribute wie Bildabmessungen für Bilder oder Videodauer für Videos. + +* **Dateisysteme**: Wenn Sie ein Dateisystem entwerfen, in dem unterschiedliche Dateitypen verwaltet werden müssen, wie Dokumente, Bilder, Audiodateien und Verzeichnisse, kann das Abstract Document Pattern helfen, eine konsistente Möglichkeit zum Zugriff auf Attribute wie Dateigröße, Erstellungsdatum usw. zu bieten, während spezifische Attribute wie Bildauflösung oder Audiodauer berücksichtigt werden. + +* **E-Commerce-Systeme**: Eine E-Commerce-Plattform könnte verschiedene Produkttypen haben, wie physische Produkte, digitale Downloads und Abonnements. Jeder Typ könnte gemeinsame Attribute wie Name, Preis und Beschreibung haben, aber auch einzigartige Attribute wie Versandgewicht für physische Produkte oder Download-Link für digitale Produkte. + +* **Medizinische Aufzeichnungssysteme**: Im Gesundheitswesen könnten Patientenakten verschiedene Datentypen enthalten, wie demografische Daten, medizinische Vorgeschichte, Testergebnisse und Rezepte. Das Abstract Document Pattern kann helfen, gemeinsame Attribute wie Patienten-ID und Geburtsdatum zu verwalten, während spezialisierte Attribute wie Testergebnisse oder verschriebene Medikamente berücksichtigt werden. + +* **Konfigurationsmanagement**: Bei der Verwaltung von Konfigurationseinstellungen für Softwareanwendungen gibt es möglicherweise verschiedene Arten von Konfigurationselementen, jedes mit einer eigenen Reihe von Attributen. Das Abstract Document Pattern kann verwendet werden, um diese Konfigurationselemente zu verwalten, während eine konsistente Möglichkeit zum Zugriff auf und Bearbeiten der Attribute sichergestellt wird. + +* **Bildungsplattformen**: Bildungssysteme könnten verschiedene Arten von Lernmaterialien wie textbasierte Inhalte, Videos, Quizze und Aufgaben haben. Gemeinsame Attribute wie Titel, Autor und Veröffentlichungsdatum können geteilt werden, während spezifische Attribute wie Videodauer oder Aufgabenfälligkeit für jeden Typ einzigartig sind. + +* **Projektmanagement-Tools**: In Projektmanagement-Anwendungen könnten unterschiedliche Aufgabenarten wie To-Do-Items, Meilensteine und Probleme vorliegen. Das Abstract Document Pattern könnte verwendet werden, um allgemeine Attribute wie Aufgabenname und Zuweisung zu handhaben, während spezifische Attribute wie Meilensteindaten oder Problemprioritäten zugelassen werden. + +* **Dokumente haben vielfältige und sich entwickelnde Attributstrukturen.** + +* **Dynamisches Hinzufügen neuer Eigenschaften ist eine häufige Anforderung.** + +* **Entkopplung des Datenzugriffs von spezifischen Formaten ist entscheidend.** + +* **Wartbarkeit und Flexibilität sind entscheidend für die Codebasis.** + +Die Hauptidee hinter dem Abstract Document Design Pattern ist es, eine flexible und erweiterbare Möglichkeit zur Verwaltung unterschiedlicher Dokumenttypen oder Entitäten mit gemeinsamen und spezifischen Attributen bereitzustellen. Durch die Definition einer gemeinsamen Schnittstelle und deren Implementierung über verschiedene Dokumenttypen hinweg können Sie einen besser organisierten und konsistenteren Ansatz zur Handhabung komplexer Datenstrukturen erreichen. + +## Vorteile und Abwägungen des Abstract Document Patterns + +**Vorteile:** + +* **Flexibilität**: Ermöglicht die Handhabung unterschiedlicher Dokumentstrukturen und -eigenschaften. +* **Erweiterbarkeit**: Neue Attribute können dynamisch hinzugefügt werden, ohne bestehenden Code zu brechen. +* **Wartbarkeit**: Fördert sauberen und anpassungsfähigen Code durch Trennung der Verantwortlichkeiten. +* **Wiederverwendbarkeit**: Typspezifische Ansichten ermöglichen eine Wiederverwendung des Codes zum Zugriff auf bestimmte Attributtypen. + +**Abwägungen:** + +* **Komplexität**: Erfordert die Definition von Schnittstellen und Ansichten, was zu zusätzlichem Implementierungsaufwand führt. +* **Leistung**: Kann im Vergleich zum direkten Datenzugriff zu leichtem Leistungsaufwand führen. + +## Quellen und Danksagungen + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://amzn.to/49zRP4R) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Abstract Document Pattern (Wikipedia)](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) +* [Dealing with Properties (Martin Fowler)](http://martinfowler.com/apsupp/properties.pdf) + + + + + diff --git a/localization/de/abstract-document/etc/abstract-document.png b/localization/de/abstract-document/etc/abstract-document.png new file mode 100644 index 000000000000..6bc0b29a4e77 Binary files /dev/null and b/localization/de/abstract-document/etc/abstract-document.png differ diff --git a/localization/es/abstract-document/README.md b/localization/es/abstract-document/README.md index 56f3432180ba..ad07e56a292a 100644 --- a/localization/es/abstract-document/README.md +++ b/localization/es/abstract-document/README.md @@ -1,5 +1,6 @@ --- title: Abstract Document +shortTitle: Abstract Document category: Structural language: es tag: diff --git a/localization/es/abstract-factory/README.md b/localization/es/abstract-factory/README.md index 6f7475316216..84d63f941f9a 100644 --- a/localization/es/abstract-factory/README.md +++ b/localization/es/abstract-factory/README.md @@ -1,5 +1,6 @@ --- title: Abstract Factory +shortTitle: Abstract Factory category: Creational language: es tag: diff --git a/localization/es/active-object/README.md b/localization/es/active-object/README.md index 1d2948c58892..37f4ac911835 100644 --- a/localization/es/active-object/README.md +++ b/localization/es/active-object/README.md @@ -1,5 +1,6 @@ --- title: Active Object +shortTitle: Active Object category: Concurrency language: es tag: diff --git a/localization/es/acyclic-visitor/README.md b/localization/es/acyclic-visitor/README.md index 8b835f43ae58..a4fe5e8e60d1 100644 --- a/localization/es/acyclic-visitor/README.md +++ b/localization/es/acyclic-visitor/README.md @@ -1,5 +1,6 @@ --- title: Acyclic Visitor +shortTitle: Acyclic Visitor category: Behavioral language: es tag: diff --git a/localization/es/adapter/README.md b/localization/es/adapter/README.md index 0790d72206b9..3f8d9eb3c5dd 100644 --- a/localization/es/adapter/README.md +++ b/localization/es/adapter/README.md @@ -1,5 +1,6 @@ --- title: Adapter +shortTitle: Adapter category: Structural language: es tag: diff --git a/localization/es/aggregator-microservices/README.md b/localization/es/aggregator-microservices/README.md index 835863efbf56..71d869697825 100644 --- a/localization/es/aggregator-microservices/README.md +++ b/localization/es/aggregator-microservices/README.md @@ -1,5 +1,6 @@ --- title: Aggregator Microservices +shortTitle: Aggregator Microservices category: Architectural language: es tag: diff --git a/localization/es/ambassador/README.md b/localization/es/ambassador/README.md index 83601c0f2bcd..e9510ffb0869 100644 --- a/localization/es/ambassador/README.md +++ b/localization/es/ambassador/README.md @@ -1,5 +1,6 @@ --- title: Ambassador +shortTitle: Ambassador category: Structural language: es tag: diff --git a/localization/es/api-gateway/README.md b/localization/es/api-gateway/README.md index 5b6abb98a86b..e2a7a00241fb 100644 --- a/localization/es/api-gateway/README.md +++ b/localization/es/api-gateway/README.md @@ -1,5 +1,6 @@ --- title: API Gateway +shortTitle: API Gateway category: Architectural language: es tag: diff --git a/localization/es/arrange-act-assert/README.md b/localization/es/arrange-act-assert/README.md index 99ac9540e446..64e75c4cbdaf 100644 --- a/localization/es/arrange-act-assert/README.md +++ b/localization/es/arrange-act-assert/README.md @@ -1,5 +1,6 @@ --- title: Arrange/Act/Assert +shortTitle: Arrange/Act/Assert category: Idiom language: es tag: diff --git a/localization/es/async-method-invocation/README.md b/localization/es/async-method-invocation/README.md index f4b590a2fda5..6de9b93be43f 100644 --- a/localization/es/async-method-invocation/README.md +++ b/localization/es/async-method-invocation/README.md @@ -1,5 +1,6 @@ --- title: Async Method Invocation +shortTitle: Async Method Invocation category: Concurrency language: es tag: diff --git a/localization/es/balking/README.md b/localization/es/balking/README.md index e92a1d5d9840..2bf1433eab37 100644 --- a/localization/es/balking/README.md +++ b/localization/es/balking/README.md @@ -1,5 +1,6 @@ --- title: Balking +shortTitle: Balking category: Concurrency language: es tag: diff --git a/localization/es/bridge/README.md b/localization/es/bridge/README.md index 605340e8657c..e04d94225f8e 100644 --- a/localization/es/bridge/README.md +++ b/localization/es/bridge/README.md @@ -1,5 +1,6 @@ --- title: Bridge +shortTitle: Bridge category: Structural language: es tag: diff --git a/localization/es/builder/README.md b/localization/es/builder/README.md index e931c4ca2b71..955b8e55d700 100644 --- a/localization/es/builder/README.md +++ b/localization/es/builder/README.md @@ -1,5 +1,6 @@ --- title: Builder +shortTitle: Builder category: Creational language: es tag: diff --git a/localization/es/business-delegate/README.md b/localization/es/business-delegate/README.md new file mode 100644 index 000000000000..8f606c1bbd7f --- /dev/null +++ b/localization/es/business-delegate/README.md @@ -0,0 +1,203 @@ +--- +title: Business Delegate +shortTitle: Business Delegate +category: Structural +language: es +tag: + - Decoupling +--- + +## Propósito + +El patrón Business Delegate añade una capa de abstracción entre los niveles de presentación y de negocio. Al utilizar +este patrón, conseguimos un acoplamiento flexible entre los niveles y encapsulamos el conocimiento sobre cómo localizar, +conectar e interactuar con los objetos de negocio que componen la aplicación. + +## También conocido como + +Service Representative + +## Explicación + +Ejemplo del mundo real + +> Una aplicación para teléfonos móviles promete transmitir a tu dispositivo cualquier película existente. Captura la +> cadena de búsqueda del usuario y se la pasa al Delegado de Negocio. El Delegado de Negocio selecciona el +> servicio de streaming de vídeo más adecuado y reproduce el vídeo. + +En pocas palabras + +> Business Delegate añade una capa de abstracción entre los niveles de presentación y de negocio. + +Wikipedia dice + +> Business Delegate es un patrón de diseño de Java EE. Este patrón está dirigido a reducir el acoplamiento entre los +> servicios de negocio y el nivel de presentación conectado, y para ocultar los detalles de implementación de los +> servicios (incluyendo la búsqueda y la accesibilidad de la arquitectura EJB). Los delegados de negocio actúan como un +> adaptador para invocar objetos de negocio desde la capa de presentación. + +**Ejemplo programático** + +En primer lugar, tenemos una abstracción para los servicios de streaming de vídeo `VideoStreamingService` y un par de +implementaciones `NetflixService` y `YouTubeService`. + +```java +public interface VideoStreamingService { + void doProcessing(); +} + +@Slf4j +public class NetflixService implements VideoStreamingService { + @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"); + } +} +``` + +A continuación, tenemos un servicio de búsqueda `BusinessLookup` que decide qué servicio de transmisión de vídeo +utilizar. + +```java + +@Setter +public class BusinessLookup { + + private NetflixService netflixService; + private YouTubeService youTubeService; + + public VideoStreamingService getBusinessService(String movie) { + if (movie.toLowerCase(Locale.ROOT).contains("die hard")) { + return netflixService; + } else { + return youTubeService; + } + } +} +``` + +El Delegado de Negocio `BusinessDelegate` utiliza una búsqueda de negocio para dirigir las solicitudes de reproducción +de películas a un servicio de streaming de vídeo adecuado. + +```java + +@Setter +public class BusinessDelegate { + + private BusinessLookup lookupService; + + public void playbackMovie(String movie) { + VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie); + videoStreamingService.doProcessing(); + } +} +``` + +El cliente móvil `MobileClient` utiliza Business Delegate para llamar al nivel de negocio. + +```java +public class MobileClient { + + private final BusinessDelegate businessDelegate; + + public MobileClient(BusinessDelegate businessDelegate) { + this.businessDelegate = businessDelegate; + } + + public void playbackMovie(String movie) { + businessDelegate.playbackMovie(movie); + } +} +``` + +Por último, podemos demostrar el ejemplo completo en acción. + +```java + public static void main(String[]args){ + + // preparar los objetos + var businessDelegate=new BusinessDelegate(); + var businessLookup=new BusinessLookup(); + businessLookup.setNetflixService(new NetflixService()); + businessLookup.setYouTubeService(new YouTubeService()); + businessDelegate.setLookupService(businessLookup); + + // crear el cliente y utilizar el Business Delegate + var client=new MobileClient(businessDelegate); + client.playbackMovie("Die Hard 2"); + client.playbackMovie("Maradona: The Greatest Ever"); + } +``` + +Aquí está la salida de la consola. + +``` +21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing +21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing +``` + +## Diagrama de clases + +![Diagrama de clases](./etc/business-delegate.urm.png "Business Delegate") + +## Patrones relacionados + +* [Patrón de localización de servicios](https://java-design-patterns.com/patterns/service-locator/) + +## Aplicabilidad + +Utilice el patrón Business Delegate cuando + +* Desea un acoplamiento flexible entre los niveles de presentación y de negocio. +* Quieres orquestar llamadas a múltiples servicios de negocio +* Se desea encapsular las búsquedas y llamadas a servicios. +* Es necesario abstraer y encapsular la comunicación entre la capa cliente y los servicios de negocio. + +## Tutoriales + +* [Patrón Delegado de Negocio en TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) + +## Usos conocidos + +* Aplicaciones empresariales que utilicen Java EE (Java Platform, Enterprise Edition) +* Aplicaciones que requieren acceso remoto a servicios empresariales + +## Consecuencias + +Ventajas: + +* Desacoplamiento de los niveles de presentación y de negocio: Permite que el nivel de cliente y los servicios + empresariales evolucionen de forma independiente. +* Transparencia de ubicación: Los clientes no se ven afectados por cambios en la ubicación o la instanciación de los + servicios de negocio. +* Reutilización y escalabilidad: Los objetos Business Delegate pueden ser reutilizados por múltiples clientes, y el + patrón soporta el equilibrio de carga y la escalabilidad. + carga y escalabilidad. + +Contrapartidas: + +* Complejidad: Introduce capas y abstracciones adicionales que pueden aumentar la complejidad. +* Sobrecarga de rendimiento: La indirección adicional puede suponer una ligera penalización en el rendimiento. + +## Patrones relacionados + +* [Localizador de servicios](https://java-design-patterns.com/patterns/service-locator/): El Delegado de Negocio ( + Business Delegate) utiliza el Localizador de Servicios (Service Locator) para localizar servicios de negocio. +* [Fachada de Sesión](https://java-design-patterns.com/patterns/session-facade/): El Delegado de Negocio (Business + Delegate) puede utilizar la Fachada de Sesión (Session Facade) para proporcionar una interfaz unificada a un conjunto + de servicios de negocio. +* [Entidad Compuesta](https://java-design-patterns.com/patterns/composite-entity/): El Delegado (Business Delegate) de + Negocio puede utilizar Entidad Compuesta (Composite Entity) para gestionar el estado de los servicios de negocio. + +## Créditos + +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947) diff --git a/localization/es/business-delegate/etc/business-delegate.urm.png b/localization/es/business-delegate/etc/business-delegate.urm.png new file mode 100644 index 000000000000..4dca6c263b99 Binary files /dev/null and b/localization/es/business-delegate/etc/business-delegate.urm.png differ diff --git a/localization/es/bytecode/README.md b/localization/es/bytecode/README.md new file mode 100644 index 000000000000..35cf357f429e --- /dev/null +++ b/localization/es/bytecode/README.md @@ -0,0 +1,256 @@ +--- +title: Bytecode +shortTitle: Bytecode +category: Behavioral +language: es +tag: + - Game programming +--- + +## Propósito + +Permite codificar el comportamiento como instrucciones para una máquina virtual. + +## Explicación + +Ejemplo del mundo real + +> Un equipo está trabajando en un nuevo juego en el que los magos luchan entre sí. El comportamiento de los magos necesita ser cuidadosamente ajustado e iterado cientos de veces a través de pruebas de juego. No es óptimo pedir al programador que haga cambios cada vez que el diseñador del juego quiere variar el comportamiento, así que el comportamiento del mago se implementa como una máquina virtual basada en datos. + +En palabras sencillas + +> El patrón Bytecode permite un comportamiento dirigido por datos en lugar de por código. + +[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) indica la documentación: + +> Un conjunto de instrucciones define las operaciones de bajo nivel que pueden realizarse. Una serie de instrucciones se codifica como una secuencia de bytes. Una máquina virtual ejecuta estas instrucciones de una en una, utilizando una pila para los valores intermedios. La combinación de instrucciones permite definir comportamientos complejos de alto nivel. + +**Ejemplo programático** + +Uno de los objetos más importantes del juego es la clase Mago `Wizard`. + +```java + +@AllArgsConstructor +@Setter +@Getter +@Slf4j +public class Wizard { + + 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 spawnParticles() { + LOGGER.info("Spawning particles"); + numberOfSpawnedParticles++; + } +} +``` + +A continuación, mostramos las instrucciones disponibles para nuestra máquina virtual. Cada una de las instrucciones tiene su propia semántica sobre cómo opera con los datos de la pila. Por ejemplo, la instrucción ADD toma los dos elementos superiores de la pila, los suma y coloca el resultado en la pila. + +```java + +@AllArgsConstructor +@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 + // ... +} +``` + +En el corazón de nuestro ejemplo está la clase `VirtualMachine`. Toma instrucciones como entrada y las ejecuta para proporcionar el comportamiento del objeto de juego. + +```java + +@Getter +@Slf4j +public class VirtualMachine { + + private final Stack stack = new Stack<>(); + + 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 void setHealth(int wizard, int amount) { + wizards[wizard].setHealth(amount); + } + // other setters -> + // ... +} +``` + +Ahora podemos mostrar el ejemplo completo utilizando la máquina virtual. + +```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)); + + 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")); + } +``` + +Aquí está la salida de la consola. + +``` +16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0] +16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains [] +``` + +## Diagrama de clases + +![alt text](./etc/bytecode.urm.png "Bytecode class diagram") + +## Aplicabilidad + +Utiliza el patrón Bytecode cuando tengas que definir muchos comportamientos y el lenguaje de implementación de tu juego no sea el adecuado porque: + +* Es demasiado de bajo nivel, por lo que es tedioso o propenso a errores para programar. +* Iterar en él lleva demasiado tiempo debido a tiempos de compilación lentos u otros problemas de herramientas. +* Tiene demasiada confianza. Si quieres asegurarte de que el comportamiento definido no puede romper el juego, necesitas separarlo del resto del código base. + +## Usos Conocidos + +* Java Virtual Machine (JVM) utiliza bytecode para permitir que los programas Java se ejecuten en cualquier dispositivo que tenga JVM instalado. +* Python compila sus scripts a bytecode que luego es interpretado por la Máquina Virtual Python. +* NET Framework utiliza una forma de bytecode llamada Microsoft Intermediate Language (MSIL). + +## Consecuencias + +Ventajas: + +* Portabilidad: Los programas pueden ejecutarse en cualquier plataforma que disponga de una máquina virtual compatible. +* Seguridad: La máquina virtual puede aplicar controles de seguridad al código de bytes. +* Rendimiento: Los compiladores JIT pueden optimizar el código de bytes en tiempo de ejecución, mejorando potencialmente el rendimiento respecto al código interpretado. + +Desventajas: + +* Sobrecarga: Ejecutar bytecode normalmente implica más sobrecarga que ejecutar código nativo, lo que puede afectar al rendimiento. +* Complejidad: Implementar y mantener una máquina virtual añade complejidad al sistema. + +## Patrones relacionados + +* [Intérprete](https://java-design-patterns.com/patterns/interpreter/) se utiliza a menudo dentro de la implementación de VMs para interpretar instrucciones bytecode. +* [Comando](https://java-design-patterns.com/patterns/command/): Las instrucciones bytecode pueden ser vistas como comandos ejecutados por la VM. +* [Método de fábrica](https://java-design-patterns.com/patterns/factory-method/): Las VMs pueden utilizar métodos de fábrica para instanciar operaciones o instrucciones definidas en el bytecode. + +## Créditos + +* [Game programming patterns](http://gameprogrammingpatterns.com/bytecode.html) +* [Programming Language Pragmatics](https://amzn.to/49Tusnn) diff --git a/localization/es/bytecode/etc/bytecode.urm.png b/localization/es/bytecode/etc/bytecode.urm.png new file mode 100644 index 000000000000..51335fa0a4b4 Binary files /dev/null and b/localization/es/bytecode/etc/bytecode.urm.png differ diff --git a/localization/es/chain-of-responsibility/README.md b/localization/es/chain-of-responsibility/README.md new file mode 100644 index 000000000000..1c36af8e75cf --- /dev/null +++ b/localization/es/chain-of-responsibility/README.md @@ -0,0 +1,201 @@ +--- +title: Chain of responsibility +shortTitle: Chain of responsibility +category: Behavioral +language: es +tag: + - Gang of Four + - Decoupling +--- + +## También conocido como + +* Chain of Command +* Chain of Objects +* Responsibility Chain + +## Propósito + +Evita acoplar el emisor de una petición a su receptor dando a más de un objeto la oportunidad de gestionar la petición. Encadena los objetos receptores y pasa la solicitud a lo largo de la cadena hasta que un objeto la gestione. + +## Explicación + +Ejemplo real + +> El Rey Orco da órdenes en voz alta a su ejército. El más cercano a reaccionar es el comandante, luego un oficial y después un soldado. El comandante, el oficial y el soldado forman una cadena de responsabilidad. + +En palabras sencillas + +> Ayuda a construir una cadena de objetos. Una solicitud entra por un extremo y sigue pasando de un objeto a otro hasta que encuentra un gestor adecuado. + +Wikipedia dice + +> En diseño orientado a objetos, el patrón de cadena de responsabilidad es un patrón de diseño que consiste en una fuente de objetos de comando y una serie de objetos de procesamiento. Cada objeto de procesamiento contiene lógica que define los tipos de objetos de comando que puede manejar; el resto se pasa al siguiente objeto de procesamiento de la cadena. + +**Ejemplo programático** + +Traduciendo nuestro ejemplo con los orcos de arriba. Primero, tenemos la clase `Request`: + +```java +import lombok.Getter; + +@Getter +public class Request { + + 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 void markHandled() { + this.handled = true; + } + + @Override + public String toString() { + return getRequestDescription(); + } +} + +public enum RequestType { + DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX +} +``` + +A continuación, mostramos la jerarquía del gestor de peticiones. + +```java +public interface RequestHandler { + + boolean canHandleRequest(Request req); + + int getPriority(); + + void handle(Request req); + + String name(); +} + +@Slf4j +public class OrcCommander implements RequestHandler { + @Override + public boolean canHandleRequest(Request req) { + return req.getRequestType() == RequestType.DEFEND_CASTLE; + } + + @Override + public int getPriority() { + return 2; + } + + @Override + public void handle(Request req) { + req.markHandled(); + LOGGER.info("{} handling request \"{}\"", name(), req); + } + + @Override + public String name() { + return "Orc commander"; + } +} + +// OrcOfficer and OrcSoldier are defined similarly as OrcCommander + +``` + +El Rey Orco da las órdenes y forma la cadena. + +```java +public class OrcKing { + + private List handlers; + + public OrcKing() { + buildChain(); + } + + 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)); + } +} +``` + +La cadena de responsabilidad en acción. + +```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")); +``` + +La salida de la consola. + +``` +Orc commander handling request "defend castle" +Orc officer handling request "torture prisoner" +Orc soldier handling request "collect tax" +``` + +## Diagrama de clases + +![alt text](./etc/chain-of-responsibility.urm.png "Diagrama de clases de la cadena de responsabilidad") + +## Aplicabilidad + +Utilice Cadena de Responsabilidad cuando + +* Más de un objeto puede gestionar una petición, y el gestor no se conoce a priori. El gestor debe determinarse automáticamente. +* Se desea enviar una petición a uno de varios objetos sin especificar explícitamente el receptor. +* El conjunto de objetos que pueden gestionar una solicitud debe especificarse dinámicamente. + +## Usos conocidos + +* Burbujeo de eventos en frameworks GUI donde un evento puede ser manejado en múltiples niveles de la jerarquía de un componente UI. +* Frameworks de middleware en los que una petición pasa a través de una cadena de objetos de procesamiento. +* Marcos de trabajo de registro donde los mensajes pueden pasar a través de una serie de registradores, cada uno posiblemente manejándolos de manera diferente. +* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) +* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) +* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) + +## Consecuencias + +Ventajas: + +* Acoplamiento reducido. El emisor de una petición no necesita conocer el manejador concreto que procesará la petición. +* Mayor flexibilidad a la hora de asignar responsabilidades a los objetos. Se pueden añadir o cambiar responsabilidades para gestionar una petición cambiando los miembros y el orden de la cadena. +* Permite establecer un gestor por defecto si no hay ningún gestor concreto que pueda gestionar la solicitud. + +Desventajas: + +* Puede ser difícil depurar y entender el flujo, especialmente si la cadena es larga y compleja. +* La petición puede quedar sin gestionar si la cadena no incluye un gestor "catch-all". +* Pueden surgir problemas de rendimiento debido a la posibilidad de pasar por varios gestores antes de encontrar el correcto, o no encontrarlo en absoluto. + +## Patrones Relacionados + +* [Comando](https://java-design-patterns.com/patterns/command/): puede ser usado para encapsular una petición como un objeto, que puede ser pasado a lo largo de la cadena. +* [Composite](https://java-design-patterns.com/patterns/composite/): la Cadena de Responsabilidad se aplica a menudo junto con el patrón Composite. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): los decoradores pueden encadenarse de forma similar a las responsabilidades en el patrón Cadena de responsabilidad. + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) +* [Pattern languages of program design 3](https://amzn.to/4a4NxTH) diff --git a/localization/es/chain-of-responsibility/etc/chain-of-responsibility.urm.png b/localization/es/chain-of-responsibility/etc/chain-of-responsibility.urm.png new file mode 100644 index 000000000000..af1bd105455b Binary files /dev/null and b/localization/es/chain-of-responsibility/etc/chain-of-responsibility.urm.png differ diff --git a/localization/es/client-session/README.md b/localization/es/client-session/README.md new file mode 100644 index 000000000000..d032bd6db8d5 --- /dev/null +++ b/localization/es/client-session/README.md @@ -0,0 +1,117 @@ +--- +title: Client Session +shortTitle: Client Session +category: Behavioral +language: es +tags: + - Session management + - Web development +--- + +## También conocido como + +* User session + +## Propósito + +El patrón de diseño Client Session tiene como objetivo mantener el estado y los datos de un usuario a través de múltiples peticiones dentro de una aplicación web, asegurando una experiencia de usuario continua y personalizada. + +## Explicación + +Ejemplo real + +> Quieres crear una aplicación de gestión de datos que permita a los usuarios enviar peticiones al servidor para modificar y realizar cambios en los datos almacenados en sus dispositivos. Estas peticiones son pequeñas y los datos son individuales para cada usuario, negando la necesidad de una implementación de base de datos a gran escala. Utilizando el patrón de sesión de cliente, se pueden gestionar múltiples peticiones simultáneas, equilibrando la carga de clientes entre diferentes servidores con facilidad debido a que los servidores permanecen sin estado. También se elimina la necesidad de almacenar identificadores de sesión en el lado del servidor debido a que los clientes proporcionan toda la información que un servidor necesita para realizar su proceso. + +En pocas palabras + +> En lugar de almacenar información sobre el cliente actual y la información a la que se está accediendo en el servidor, se mantiene sólo en el lado del cliente. El cliente tiene que enviar datos de sesión con cada solicitud al servidor y tiene que enviar un estado actualizado de vuelta al cliente, que se almacena en la máquina del cliente. El servidor no tiene que almacenar la información del cliente. ([ref](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client)) + +**Ejemplo programático** + +Aquí está el código de ejemplo para describir el patrón cliente-sesión. En el siguiente código estamos creando primero una instancia del Servidor. Esta instancia del servidor se utilizará entonces para obtener objetos Session para dos clientes. Como puedes ver en el siguiente código, el objeto Session puede ser utilizado para almacenar cualquier información relevante que sea requerida por el servidor para procesar la petición del cliente. Estos objetos Session serán pasados con cada Request al servidor. La solicitud tendrá el objeto Session que almacena los detalles relevantes del cliente junto con los datos requeridos para procesar la solicitud. La información de sesión en cada solicitud ayuda al servidor a identificar al cliente y procesar la solicitud en consecuencia. + +```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); + } +} + +@Data +@AllArgsConstructor +public class Session { + + /** + * Session id. + */ + private String id; + + /** + * Client name. + */ + private String clientName; + +} + +@Data +@AllArgsConstructor +public class Request { + + private String data; + + private Session session; + +} +``` + +## Diagrama de arquitectura + +![alt text](./etc/session_state_pattern.png "Session State Pattern") + +## Aplicabilidad + +Utilice el patrón de estado del cliente cuando: + +* Aplicaciones web que requieran autenticación y autorización del usuario. +* Aplicaciones que necesiten realizar un seguimiento de las actividades y preferencias del usuario a lo largo de múltiples peticiones o visitas. +* Sistemas donde los recursos del servidor necesitan ser optimizados descargando la gestión del estado al lado del cliente. + +## Usos conocidos + +* Sitios web de comercio electrónico para rastrear el contenido de la cesta de la compra a lo largo de las sesiones. +* Plataformas en línea que ofrecen contenidos personalizados basados en las preferencias y el historial del usuario. +* Aplicaciones web que requieren el inicio de sesión del usuario para acceder a contenidos personalizados o seguros. + +## Consecuencias + +Beneficios: + +* Mejora del rendimiento del servidor al reducir la necesidad de almacenar el estado del usuario en el servidor. +* Mejora de la experiencia del usuario a través de contenidos personalizados y navegación fluida a través de las diferentes partes de la aplicación. +* Flexibilidad en la gestión de sesiones a través de varios mecanismos de almacenamiento del lado del cliente (por ejemplo, cookies, Web Storage API). + +Desventajas: + +* Riesgos potenciales de seguridad si se almacena información sensible en las sesiones del cliente sin el cifrado y la validación adecuados. +* Dependencia de las capacidades y ajustes del cliente, como las políticas de cookies, que pueden variar según el navegador y la configuración del usuario. +* Mayor complejidad en la lógica de gestión de sesiones, especialmente en la gestión de la caducidad, renovación y sincronización de sesiones en varios dispositivos o pestañas. + +## Patrones relacionados + +* Sesión Servidor: A menudo se utiliza junto con el patrón Client Session para proporcionar un equilibrio entre la eficiencia del lado del cliente y el control del lado del servidor. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Asegurar una única instancia de la sesión de un usuario en toda la aplicación. +* [Estado](https://java-design-patterns.com/patterns/state/): Gestionar las transiciones de estado en una sesión, como los estados autenticado, invitado o caducado. + +## Créditos + +* [DZone - Practical PHP patterns](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client) +* [Client Session State Design Pattern - Ram N Java](https://www.youtube.com/watch?v=ycOSj9g41pc) +* [Professional Java for Web Applications](https://amzn.to/4aazY59) +* [Securing Web Applications with Spring Security](https://amzn.to/3PCCEA1) diff --git a/localization/es/client-session/etc/session_state_pattern.png b/localization/es/client-session/etc/session_state_pattern.png new file mode 100644 index 000000000000..f1e23be95766 Binary files /dev/null and b/localization/es/client-session/etc/session_state_pattern.png differ diff --git a/localization/es/collecting-parameter/README.md b/localization/es/collecting-parameter/README.md new file mode 100644 index 000000000000..b54c4ba80d80 --- /dev/null +++ b/localization/es/collecting-parameter/README.md @@ -0,0 +1,220 @@ +--- +title: Collecting Parameter +shortTitle: Collecting Parameter +category: Behavioral +language: es +tag: + - Accumulation + - Generic +--- + +## También conocido como + +* Collector +* Accumulator + +## Propósito + +Su objetivo es simplificar los métodos que recopilan información pasando un único objeto de colección a través de varias llamadas a métodos, permitiéndoles añadir resultados a esta colección en lugar de que cada método cree su propia colección. + +## Explicación + +### Ejemplo del mundo real + +Dentro de un gran edificio corporativo, existe una cola de impresión global que es una colección de todos los trabajos de impresión que están actualmente pendientes. Las diferentes plantas contienen diferentes modelos de impresoras, cada una con una política de impresión diferente. Debemos construir un programa que pueda añadir continuamente trabajos de impresión apropiados a una colección, que se llama el *parámetro de recogida*. + +### En palabras sencillas + +En lugar de tener un método gigante que contenga numerosas políticas para recoger información en una variable, podemos crear numerosas funciones más pequeñas que tomen cada parámetro, y añadan nueva información. Podemos pasar el parámetro a todas estas funciones más pequeñas y al final, tendremos lo que queríamos originalmente. Esta vez, el código es más limpio y fácil de entender. Debido a que la función más grande se ha dividido, el código también es más fácil de modificar ya que los cambios se localizan en las funciones más pequeñas. + +### Wikipedia dice + +En el modismo de Parámetros de Recolección una colección (lista, mapa, etc.) se pasa repetidamente como parámetro a un método que añade elementos a la colección. + +### Ejemplo programático + +Codificando nuestro ejemplo anterior, podemos utilizar la colección `resultado` como parámetro recolector. Se implementan las siguientes restricciones: + +- Si un papel A4 es de color, también debe ser de una sola cara. Se aceptan todos los demás papeles no coloreados. +- El papel A3 no debe ser de color y debe ser de una sola cara. +- El papel A2 debe ser de una sola página, a una cara y sin colorear. + +```java +package com.iluwatar.collectingparameter; + +import java.util.LinkedList; +import java.util.Queue; + +public class App { + static PrinterQueue printerQueue = PrinterQueue.getInstance(); + + /** + * 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)); + + /* + This variable is the collecting parameter. + */ + var result = new LinkedList(); + + /* + * Using numerous sub-methods to collaboratively add information to the result collecting parameter + */ + addA4Papers(result); + addA3Papers(result); + addA2Papers(result); + } +} +``` + +Utilizamos los métodos `addA4Paper`, `addA3Paper` y `addA2Paper` para rellenar el parámetro de recogida `result` con los trabajos de impresión adecuados según la política descrita anteriormente. Las tres políticas se codifican a continuación, + +```java +public class App { + 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 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); + } + } + } + } + + /** + * 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 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); + } + } + } + } + + /** + * 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 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); + } + } + } + } +} +``` + +Cada método toma como argumento un parámetro de recogida. A continuación, añade elementos, tomados de una variable global, a este parámetro de recogida si cada elemento satisface un criterio determinado. Estos métodos pueden tener la política que desee el cliente. + +En este ejemplo de programación, se añaden tres trabajos de impresión a la cola. Sólo los dos primeros trabajos de impresión deben añadirse al parámetro de recogida según la política. Los elementos de la variable `result` después de la ejecución son, + +| paperSize | pageCount | isDoubleSided | isColour | +|-----------|-----------|---------------|----------| +| A4 | 5 | false | false | +| A3 | 2 | false | false | + +que es lo que esperábamos. + +## Diagrama de clases + +![alt text](./etc/collecting-parameter.urm.png "Collecting Parameter") + +## Aplicabilidad + +Utilice el patrón de diseño Recopilación de parámetros cuando + +- Cuando múltiples métodos producen una colección de resultados y quieres agregar estos resultados de una manera unificada. +- En escenarios donde reducir el número de colecciones creadas por métodos puede mejorar la eficiencia de memoria y el rendimiento. +- Al refactorizar métodos grandes que realizan varias tareas, incluida la recopilación de resultados de varias operaciones. + +## Tutoriales + +Los tutoriales para este método se encuentran en: + +- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) por Joshua Kerivsky +- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) por Kent Beck + +## Usos conocidos + +Joshua Kerivsky da un ejemplo real en su libro 'Refactoring to Patterns'. Da un ejemplo de uso del patrón de diseño "Collecting Parameter" para crear un método `toString()` para un árbol XML. Sin utilizar este patrón de diseño, esto requeriría una función voluminosa con condicionales y concatenación que empeoraría la legibilidad del código. Un método de este tipo puede dividirse en métodos más pequeños, cada uno de los cuales añade su propio conjunto de información al parámetro de recogida. Véase esto en [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf). + +Otros ejemplos son: + +- Agregar mensajes de error o fallos de validación en un proceso de validación complejo. +- Recopilar elementos o información mientras se recorre una estructura de datos compleja. +- Refactorización de funcionalidades de informes complejas en las que varias partes de un informe se generan mediante métodos diferentes. + +## Consecuencias + +Ventajas: + +- Reduce la duplicación de código centralizando el manejo de las colecciones en un único lugar. +- Mejora la claridad y la capacidad de mantenimiento al hacer explícito dónde y cómo se recogen los resultados. +- Mejora el rendimiento al minimizar la creación y gestión de múltiples objetos de recopilación. + +Desventajas: + +- Aumenta el acoplamiento entre el invocador y los métodos invocados, ya que deben ponerse de acuerdo sobre la colección a utilizar. +- Puede introducir efectos secundarios en los métodos si no se gestionan con cuidado, ya que los métodos ya no son autónomos en su gestión de resultados. + +## Patrones relacionados + +- [Composite](https://java-design-patterns.com/patterns/composite/): Puede utilizarse junto con Collecting Parameter cuando se trabaja con estructuras jerárquicas, permitiendo que los resultados se recojan a través de una estructura compuesta. +- [Visitante](https://java-design-patterns.com/patterns/visitor/): A menudo se utiliza conjuntamente, donde Visitor se encarga de recorrer y realizar operaciones en una estructura, y Collecting Parameter acumula los resultados. +- [Comando](https://java-design-patterns.com/patterns/command/): Los comandos pueden utilizar el parámetro de recopilación para agregar resultados de varias operaciones ejecutadas por los objetos de comando. + +## Créditos + +- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) by Joshua Kerivsky +- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) by Kent Beck +- [Wiki](https://wiki.c2.com/?CollectingParameter) +- [Refactoring: Improving the Design of Existing Code](https://amzn.to/3TVEgaB) +- [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/4aApLP0) diff --git a/localization/es/collecting-parameter/etc/collecting-parameter.urm.png b/localization/es/collecting-parameter/etc/collecting-parameter.urm.png new file mode 100644 index 000000000000..785d6ecc2da1 Binary files /dev/null and b/localization/es/collecting-parameter/etc/collecting-parameter.urm.png differ diff --git a/localization/es/command/README.md b/localization/es/command/README.md new file mode 100644 index 000000000000..a67e974d0087 --- /dev/null +++ b/localization/es/command/README.md @@ -0,0 +1,244 @@ +--- +title: Command +shortTitle: Command +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## También conocido como + +* Action +* Transaction + +## Propósito + +El patrón de diseño Command encapsula una petición como un objeto, permitiendo así la parametrización de clientes con colas, peticiones y operaciones. También permite soportar operaciones deshechas. + +## Explicación + +Ejemplo real + +> Hay un mago lanzando hechizos sobre un goblin. Los hechizos se ejecutan sobre el duende uno a uno. El primer hechizo encoge al duende y el segundo lo hace invisible. A continuación, el mago invierte los hechizos uno a uno. Cada hechizo es un objeto de comando que se puede deshacer. + +En palabras simples: + +> Almacenar peticiones como objetos de comando permite realizar una acción o deshacerla en un momento posterior. + +Wikipedia dice: + +> En programación orientada a objetos, el patrón de comandos es un patrón de diseño de comportamiento en el que un objeto se utiliza para encapsular toda la información necesaria para realizar una acción o desencadenar un evento en un momento posterior. + +**Ejemplo programático** + +Aquí está el código de ejemplo con mago `Wizard` y duende `Goblin`. Empecemos por la clase Mago `Wizard`. + +```java + +@Slf4j +public class Wizard { + + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); + + public Wizard() { + } + + public void castSpell(Runnable runnable) { + runnable.run(); + undoStack.offerLast(runnable); + } + + public void undoLastSpell() { + if (!undoStack.isEmpty()) { + var previousSpell = undoStack.pollLast(); + redoStack.offerLast(previousSpell); + previousSpell.run(); + } + } + + public void redoLastSpell() { + if (!redoStack.isEmpty()) { + var previousSpell = redoStack.pollLast(); + undoStack.offerLast(previousSpell); + previousSpell.run(); + } + } + + @Override + public String toString() { + return "Wizard"; + } +} +``` + +A continuación, tenemos al duende `Goblin` que es el objetivo `Target` de los hechizos. + +```java + +@Slf4j +public abstract class Target { + + private Size size; + + private Visibility visibility; + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public Visibility getVisibility() { + return visibility; + } + + public void setVisibility(Visibility visibility) { + this.visibility = visibility; + } + + @Override + public abstract String toString(); + + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } +} + +public class Goblin extends Target { + + public Goblin() { + setSize(Size.NORMAL); + setVisibility(Visibility.VISIBLE); + } + + @Override + public String toString() { + return "Goblin"; + } + + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } + + public void changeVisibility() { + var visible = getVisibility() == Visibility.INVISIBLE + ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } +} +``` + +Por último, tenemos al mago en la función principal lanzando hechizos. + +```java +public static void main(String[]args){ + var wizard=new Wizard(); + var goblin=new Goblin(); + + // casts shrink/unshrink spell + wizard.castSpell(goblin::changeSize); + + // casts visible/invisible spell + wizard.castSpell(goblin::changeVisibility); + + // undo and redo casts + wizard.undoLastSpell(); + wizard.redoLastSpell(); +``` + +Este es el ejemplo en acción. + +```java +var wizard=new Wizard(); + var goblin=new Goblin(); + + goblin.printStatus(); + wizard.castSpell(goblin::changeSize); + goblin.printStatus(); + + wizard.castSpell(goblin::changeVisibility); + goblin.printStatus(); + + wizard.undoLastSpell(); + goblin.printStatus(); + + wizard.undoLastSpell(); + goblin.printStatus(); + + wizard.redoLastSpell(); + goblin.printStatus(); + + wizard.redoLastSpell(); + goblin.printStatus(); +``` + +Aquí está la salida del programa: + +```java +Goblin,[size=normal][visibility=visible] + Goblin,[size=small][visibility=visible] + Goblin,[size=small][visibility=invisible] + Goblin,[size=small][visibility=visible] + Goblin,[size=normal][visibility=visible] + Goblin,[size=small][visibility=visible] + Goblin,[size=small][visibility=invisible] +``` + +## Diagrama de clases + +![alt text](./etc/command.png "Command") + +## Aplicabilidad + +Utilice el patrón Comando (Command) para: + +* Parametrizar objetos mediante una acción a realizar. Puedes expresar dicha parametrización en un lenguaje procedimental con una función callback, es decir, una función que se registra en algún lugar para ser llamada en un momento posterior. Los comandos son un sustituto orientado a objetos de las retrollamadas. +* Especifican, ponen en cola y ejecutan peticiones en diferentes momentos. Un objeto Command puede tener una vida independiente de la petición original. Si el receptor de una petición puede ser representado de una manera independiente del espacio de direcciones, entonces puedes transferir un objeto comando para la petición a un proceso diferente y cumplir la petición allí. +* Soporta deshacer. La operación de ejecución del comando puede almacenar el estado para revertir sus efectos en el propio comando. La interfaz del Comando debe tener una operación añadida de des-ejecutar que revierta los efectos de una llamada previa a ejecutar. Los comandos ejecutados se almacenan en una lista de historial. La funcionalidad de deshacer y rehacer a nivel ilimitado se consigue recorriendo esta lista hacia atrás y hacia delante llamando a un-ejecutar y ejecutar, respectivamente. +* Soporta el registro de cambios para que puedan volver a aplicarse en caso de caída del sistema. Al aumentar la interfaz de comandos con operaciones de carga y almacenamiento, puede mantener un registro persistente de los cambios. La recuperación de un fallo implica volver a cargar los comandos registrados desde el disco y volver a ejecutarlos con la operación de ejecución. +* Estructurar un sistema en torno a operaciones de alto nivel construidas sobre operaciones primitivas. Esta estructura es común en los sistemas de información que admiten transacciones. Una transacción encapsula un conjunto de cambios de datos. El patrón Command ofrece una forma de modelar las transacciones. Los comandos tienen una interfaz común que permite invocar todas las transacciones de la misma manera. El patrón también facilita la ampliación del sistema con nuevas transacciones. +* Mantener un historial de peticiones. +* Implementar la funcionalidad de callback. +* Implementar la funcionalidad de deshacer. + +## Usos conocidos + +* Botones GUI y elementos de menú en aplicaciones de escritorio. +* Operaciones en sistemas de bases de datos y sistemas transaccionales que soportan rollback. +* Grabación de macros en aplicaciones como editores de texto y hojas de cálculo. +* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) +* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java) +* [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) +* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) + +## Consecuencias + +Ventajas: + +* Desacopla el objeto que invoca la operación del que sabe cómo realizarla. +* Es fácil añadir nuevos Comandos, porque no tienes que cambiar las clases existentes. +* Puedes ensamblar un conjunto de comandos en un comando compuesto. + +Desventajas: + +* Aumenta el número de clases para cada comando individual. +* Puede complicar el diseño al añadir múltiples capas entre emisores y receptores. + +## Patrones Relacionados + +* [Composite](https://java-design-patterns.com/patterns/composite/): Los comandos pueden ser compuestos usando el patrón Composite para crear macro comandos. +* [Memento](https://java-design-patterns.com/patterns/memento/): Puede usarse para implementar mecanismos de deshacer. +* [Observador](https://java-design-patterns.com/patterns/observer/): El patrón puede ser observado para cambios que activan comandos. + +## Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PFUqSY) diff --git a/localization/es/command/etc/command.png b/localization/es/command/etc/command.png new file mode 100644 index 000000000000..0f026464ecc4 Binary files /dev/null and b/localization/es/command/etc/command.png differ diff --git a/localization/es/commander/README.md b/localization/es/commander/README.md new file mode 100644 index 000000000000..c46e8e5b3faa --- /dev/null +++ b/localization/es/commander/README.md @@ -0,0 +1,132 @@ +--- +title: Commander +shortTitle: Commander +category: Behavioral +language: es +tag: + - Cloud distributed + - Microservices + - Transactions +--- + +## También conocido como + +* Distributed Transaction Commander +* Transaction Coordinator + +## Propósito + +La intención del patrón Commander en el contexto de las transacciones distribuidas es gestionar y coordinar transacciones complejas a través de múltiples componentes o servicios distribuidos, asegurando la consistencia e integridad de la transacción global. Encapsula comandos de transacciones y lógica de coordinación, facilitando la implementación de protocolos de transacciones distribuidas como commit de dos fases o Saga. + +## Explicación + +Ejemplo real + +> Imagine que organiza un gran festival internacional de música en el que están programadas actuaciones de varios grupos de todo el mundo. La llegada, la prueba de sonido y la actuación de cada grupo son como transacciones individuales en un sistema distribuido. El organizador del festival actúa como el "Comandante", coordinando estas transacciones para garantizar que si el vuelo de una banda se retrasa (similar a un fallo de transacción), hay un plan de respaldo, como reprogramar o intercambiar franjas horarias con otra banda (acciones compensatorias), para mantener intacto el programa general. Esta configuración refleja el patrón del Comandante en las transacciones distribuidas, en las que varios componentes deben coordinarse para lograr un resultado satisfactorio a pesar de los fallos individuales. + +En palabras sencillas + +> El patrón Commander convierte una petición en un objeto independiente, permitiendo la parametrización de comandos, la puesta en cola de acciones y la implementación de operaciones de deshacer. + +**Ejemplo programático** + +La gestión de transacciones a través de diferentes servicios en un sistema distribuido, como una plataforma de comercio electrónico con microservicios separados de Pago y Envío, requiere una cuidadosa coordinación para evitar problemas. Cuando un usuario realiza un pedido pero un servicio (por ejemplo, Pago) no está disponible mientras que el otro (por ejemplo, Envío) está listo, necesitamos una solución robusta para manejar esta discrepancia. + +Una estrategia para resolver este problema consiste en utilizar un componente Commander que orqueste el proceso. Inicialmente, el pedido es procesado por el servicio disponible (Envío en este caso). A continuación, el comandante intenta sincronizar el pedido con el servicio no disponible en ese momento (Pago) almacenando los detalles del pedido en una base de datos o poniéndolo en cola para su procesamiento futuro. Este sistema de colas también debe tener en cuenta posibles fallos al añadir solicitudes a la cola. + +El comandante intenta repetidamente procesar los pedidos en cola para garantizar que ambos servicios reflejen finalmente los mismos datos de transacción. Este proceso implica garantizar la idempotencia, lo que significa que incluso si la misma solicitud de sincronización de pedidos se realiza varias veces, sólo se ejecutará una vez, evitando transacciones duplicadas. El objetivo es lograr una coherencia final entre los servicios, en la que todos los sistemas se sincronicen a lo largo del tiempo a pesar de los fallos o retrasos iniciales. + +En el código proporcionado, el patrón Commander se utiliza para manejar transacciones distribuidas a través de múltiples servicios (PaymentService, ShippingService, MessagingService, EmployeeHandle). Cada servicio tiene su propia base de datos y puede lanzar excepciones para simular fallos. + +La clase Commander es la parte central de este patrón. Toma instancias de todos los servicios y sus bases de datos, junto con algunos parámetros de configuración. El método placeOrder de la clase Commander se utiliza para realizar un pedido, lo que implica interactuar con todos los servicios. + +```java +public class Commander { + // ... constructor and other methods ... + + public void placeOrder(Order order) { + // ... implementation ... + } +} +``` + +Las clases Usuario y Pedido representan un usuario y un pedido respectivamente. Un pedido lo realiza un usuario. + +```java +public class User { + // ... constructor and other methods ... +} + +public class Order { + // ... constructor and other methods ... +} +``` + +Cada servicio (por ejemplo, PaymentService, ShippingService, MessagingService, EmployeeHandle) tiene su propia base de datos y puede lanzar excepciones para simular fallos. Por ejemplo, el PaymentService puede lanzar una DatabaseUnavailableException si su base de datos no está disponible. + +```java +public class PaymentService { + // ... constructor and other methods ... +} +``` + +Las clases DatabaseUnavailableException, ItemUnavailableException y ShippingNotPossibleException representan diferentes tipos de excepciones que pueden ocurrir. + +```java +public class DatabaseUnavailableException extends Exception { + // ... constructor and other methods ... +} + +public class ItemUnavailableException extends Exception { + // ... constructor and other methods ... +} + +public class ShippingNotPossibleException extends Exception { + // ... constructor and other methods ... +} +``` + +En el método principal de cada clase (AppQueueFailCases, AppShippingFailCases), se simulan diferentes escenarios creando instancias de la clase Commander con diferentes configuraciones y llamando al método placeOrder. + +## Diagrama de clases + +![alt text](./etc/commander.urm.png "Commander class diagram") + +## Aplicabilidad + +Utilice el patrón Commander para transacciones distribuidas cuando: + +* Necesites asegurar la consistencia de los datos entre servicios distribuidos en caso de fallos parciales del sistema. +* Las transacciones abarcan múltiples microservicios o componentes distribuidos que requieren un commit o rollback coordinado. +* Está implementando transacciones de larga duración que requieren acciones compensatorias para la reversión. + +## Usos conocidos + +* Protocolos Two-Phase Commit (2PC): Coordinación de commit o rollback a través de bases de datos o servicios distribuidos. +* Implementaciones del patrón Saga: Gestión de procesos de negocio de larga duración que abarcan múltiples microservicios, con cada paso teniendo una acción compensatoria para la reversión. +* Transacciones distribuidas en arquitectura de microservicios: Coordinación de operaciones complejas entre microservicios manteniendo la integridad y consistencia de los datos. + +## Consecuencias + +Beneficios: + +* Proporciona un mecanismo claro para gestionar transacciones distribuidas complejas, mejorando la fiabilidad del sistema. +* Permite la implementación de transacciones compensatorias, que son cruciales para mantener la coherencia en transacciones de larga duración. +* Facilita la integración de sistemas heterogéneos en un contexto transaccional. + +Contrapartidas: + +* Aumenta la complejidad, especialmente en situaciones de fallo, debido a la necesidad de mecanismos de reversión coordinados. +* Potencialmente afecta al rendimiento debido a la sobrecarga de la coordinación y las comprobaciones de coherencia. +* Las implementaciones basadas en Saga pueden llevar a una mayor complejidad en la comprensión del flujo global del proceso de negocio. + +## Patrones relacionados + +[Saga Pattern](https://java-design-patterns.com/patterns/saga/): A menudo se discute junto con el patrón Commander para transacciones distribuidas, centrándose en transacciones de larga duración con acciones compensatorias. + +## Créditos + +* [Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/) +* [Microservices Patterns: With examples in Java](https://amzn.to/4axjnYW) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/4axHwOV) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/4aATcRe) diff --git a/localization/es/commander/etc/commander.urm.png b/localization/es/commander/etc/commander.urm.png new file mode 100644 index 000000000000..6b5ebba75bd6 Binary files /dev/null and b/localization/es/commander/etc/commander.urm.png differ diff --git a/localization/es/composite-entity/README.md b/localization/es/composite-entity/README.md new file mode 100644 index 000000000000..d3a908312e09 --- /dev/null +++ b/localization/es/composite-entity/README.md @@ -0,0 +1,173 @@ +--- +title: Composite Entity +shortTitle: Composite Entity +category: Structural +language: es +tag: + - Client-server + - Data access + - Enterprise patterns +--- + +## También conocido como + +* Coarse-Grained Entity + +## Propósito + +El patrón de diseño Entidad Compuesta tiene como objetivo gestionar un conjunto de objetos persistentes +interrelacionados como si fueran una única entidad. Se utiliza comúnmente en el contexto de Enterprise JavaBeans (EJB) y +marcos empresariales similares para representar estructuras de datos basadas en gráficos dentro de modelos de negocio, +permitiendo a los clientes tratarlos como una sola unidad. + +## Explicación + +Ejemplo real + +> En una consola, puede haber muchas interfaces que necesiten ser gestionadas y controladas. Usando el patrón de entidad +> compuesta, objetos dependientes como mensajes y señales pueden ser combinados y controlados usando un único objeto. + +En palabras sencillas + +> El patrón de entidad compuesta permite representar y gestionar un conjunto de objetos relacionados mediante un objeto +> unificado. + +**Ejemplo programático** + +Necesitamos una solución genérica para el problema. Para ello, vamos a introducir un patrón genérico de entidad +compuesta. + +```java +public abstract class DependentObject { + + T data; + + public void setData(T message) { + this.data = message; + } + + public T getData() { + return data; + } +} + +public abstract class CoarseGrainedObject { + + DependentObject[] dependentObjects; + + public void setData(T... data) { + IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i])); + } + + public T[] getData() { + return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray(); + } +} + +``` + +La entidad compuesta especializada `consola` hereda de esta clase base de la siguiente manera. + +```java +public class MessageDependentObject extends DependentObject { + +} + +public class SignalDependentObject extends DependentObject { + +} + +public class ConsoleCoarseGrainedObject extends CoarseGrainedObject { + + @Override + public String[] getData() { + super.getData(); + return new String[] { + dependentObjects[0].getData(), dependentObjects[1].getData() + }; + } + + public void init() { + dependentObjects = new DependentObject[] { + new MessageDependentObject(), new SignalDependentObject()}; + } +} + +public class CompositeEntity { + + private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); + + public void setData(String message, String signal) { + console.setData(message, signal); + } + + public String[] getData() { + return console.getData(); + } +} +``` + +Gestionando ahora la asignación de objetos mensaje y señal con la entidad compuesta `consola`. + +```java +var console=new CompositeEntity(); + console.init(); + console.setData("No Danger","Green Light"); + Arrays.stream(console.getData()).forEach(LOGGER::info); + console.setData("Danger","Red Light"); + Arrays.stream(console.getData()).forEach(LOGGER::info); +``` + +## Diagrama de clases + +![alt text](./etc/composite_entity.urm.png "Patrón de entidad compuesta") + +## Aplicabilidad + +* Útil en aplicaciones empresariales donde los objetos de negocio son complejos e involucran varios objetos + interdependientes. +* Ideal para escenarios donde los clientes necesitan trabajar con una interfaz unificada para un conjunto de objetos en + lugar de entidades individuales. +* Aplicable en sistemas que requieren una vista simplificada de un modelo de datos complejo para clientes o servicios + externos. + +## Usos conocidos + +* Aplicaciones empresariales con modelos de negocio complejos, particularmente aquellas que utilizan EJB o marcos + empresariales similares. +* Sistemas que requieren abstracción sobre esquemas de bases de datos complejos para simplificar las interacciones con + los clientes. +* Aplicaciones que necesitan reforzar la consistencia o las transacciones a través de múltiples objetos en una entidad + de negocio. + +## Consecuencias + +Ventajas: + +* Simplifica las interacciones del cliente con modelos de entidad complejos proporcionando una interfaz unificada. +* Mejora la reutilización y el mantenimiento de la capa de negocio al desacoplar el código del cliente de los complejos + componentes internos de las entidades de negocio. +* Facilita la gestión de transacciones y la aplicación de la coherencia en un conjunto de objetos relacionados. + +Contrapartidas: + +* Puede introducir un nivel de indirección que podría afectar al rendimiento. +* Puede dar lugar a interfaces de grano demasiado grueso que podrían no ser tan flexibles para todas las necesidades de + los clientes. +* Requiere un diseño cuidadoso para evitar entidades compuestas hinchadas que sean difíciles de gestionar. + +## Patrones relacionados + +* [Decorador](https://java-design-patterns.com/patterns/decorator/): Para añadir dinámicamente comportamiento a objetos + individuales dentro de la entidad compuesta sin afectar a la estructura. +* [Fachada](https://java-design-patterns.com/patterns/facade/): Proporciona una interfaz simplificada a un subsistema + complejo, de forma similar a como una entidad compuesta simplifica el acceso a un conjunto de objetos. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Útil para gestionar objetos compartidos dentro de + una entidad compuesta para reducir la huella de memoria. + +## Créditos + +* [Composite Entity Pattern in wikipedia](https://en.wikipedia.org/wiki/Composite_entity_pattern) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Enterprise Patterns and MDA: Building Better Software with Archetype Patterns and UML](https://amzn.to/49mslqS) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3xjKdpe) diff --git a/localization/es/composite-entity/etc/composite_entity.urm.png b/localization/es/composite-entity/etc/composite_entity.urm.png new file mode 100644 index 000000000000..d6c29a718837 Binary files /dev/null and b/localization/es/composite-entity/etc/composite_entity.urm.png differ diff --git a/localization/es/composite-view/README.md b/localization/es/composite-view/README.md new file mode 100644 index 000000000000..9c178e81533b --- /dev/null +++ b/localization/es/composite-view/README.md @@ -0,0 +1,386 @@ +--- +title: Composite View +shortTitle: Composite View +category: Structural +language: es +tag: + - Enterprise patterns + - Presentation +--- + +## Propósito + +El objetivo principal del patrón de diseño Composite View es componer objetos en estructuras de árbol para representar +jerarquías parte-todo. Esto permite a los clientes tratar objetos individuales y composiciones de objetos de manera +uniforme, simplificando la gestión de estructuras complejas. + +## Explicación + +Ejemplo del mundo real + +> Un sitio de noticias quiere mostrar la fecha actual y las noticias a diferentes usuarios basándose en las preferencias +> de ese usuario. El sitio de noticias sustituirá en diferentes componentes de alimentación de noticias en función de +> los intereses del usuario, por defecto a las noticias locales. + +En pocas palabras + +> El patrón de vista compuesta consiste en tener una vista principal compuesta por subvistas más pequeñas. El diseño de +> esta vista compuesta se basa en una plantilla. Un View-manager decide entonces qué subvistas incluir en esta +> plantilla. + +Wikipedia dice + +> Vistas compuestas que están formadas por múltiples subvistas atómicas. Cada componente de la plantilla puede incluirse +> dinámicamente en el conjunto y el diseño de la página puede gestionarse independientemente del contenido. Esta +> solución permite crear una vista compuesta basada en la inclusión y sustitución de fragmentos modulares de plantillas +> dinámicas y estáticas. Promueve la reutilización de porciones atómicas de la vista fomentando el diseño modular. + +**Ejemplo programático** + +Dado que se trata de un patrón de desarrollo web, se requiere un servidor para demostrarlo. Este ejemplo utiliza Tomcat +10.0.13 para ejecutar el servlet, y este ejemplo programático sólo funcionará con Tomcat 10+. + +En primer lugar, existe `AppServlet` que es un `HttpServlet` que se ejecuta en Tomcat 10+. + +```java +public class AppServlet extends HttpServlet { + private String msgPartOne = "

This Server Doesn't Support"; + private String msgPartTwo = "Requests

\n" + + "

Use a GET request with boolean values for the following parameters

\n" + + "

'name'

\n

'bus'

\n

'sports'

\n

'sci'

\n

'world'

"; + + private String destination = "newsDisplay.jsp"; + + public AppServlet() { + + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean reqParams = new ClientPropertiesBean(req); + req.setAttribute("properties", reqParams); + requestDispatcher.forward(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Post " + msgPartTwo); + + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Delete " + msgPartTwo); + + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Put " + msgPartTwo); + + } +} + +``` + +Este servlet no forma parte del patrón, y simplemente reenvía las peticiones GET a la JSP correcta. Las peticiones PUT, +POST y DELETE no están soportadas y simplemente mostrarán un mensaje de error. + +La gestión de la vista en este ejemplo se realiza a través de una clase javabean: `ClientPropertiesBean`, que almacena +las preferencias del usuario. + +```java +public class ClientPropertiesBean implements Serializable { + + private static final String WORLD_PARAM = "world"; + private static final String SCIENCE_PARAM = "sci"; + private static final String SPORTS_PARAM = "sport"; + private static final String BUSINESS_PARAM = "bus"; + private static final String NAME_PARAM = "name"; + + private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private boolean worldNewsInterest; + private boolean sportsInterest; + private boolean businessInterest; + private boolean scienceNewsInterest; + private String name; + + public ClientPropertiesBean() { + worldNewsInterest = true; + sportsInterest = true; + businessInterest = true; + scienceNewsInterest = true; + name = DEFAULT_NAME; + + } + + public ClientPropertiesBean(HttpServletRequest req) { + worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM)); + sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM)); + businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM)); + scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM)); + String tempName = req.getParameter(NAME_PARAM); + if (tempName == null || tempName == "") { + tempName = DEFAULT_NAME; + } + name = tempName; + } + // getters and setters generated by Lombok +} +``` + +Este javabean tiene un constructor por defecto, y otro que toma un `HttpServletRequest`. + +Este segundo constructor toma el objeto de solicitud, analiza los parámetros de la solicitud que contienen las +preferencias del usuario para los diferentes tipos de noticias. + +La plantilla para la página de noticias está en `newsDisplay.jsp`. + +```html + + + + + + +<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> +

Welcome <%= propertiesBean.getName()%>

+ + + + + + <% if(propertiesBean.isWorldNewsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isBusinessInterest()) { %> + + <% } else { %> + + <% } %> + + <% if(propertiesBean.isSportsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isScienceNewsInterest()) { %> + + <% } else { %> + + <% } %> + + +
<%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="businessNews.jsp"%><%@include file="localNews.jsp"%><%@include file="sportsNews.jsp"%><%@include file="localNews.jsp"%>
<%@include file="scienceNews.jsp"%><%@include file="localNews.jsp"%>
+ + +``` + +Esta página JSP es la plantilla. Declara una tabla con tres filas, con un componente en la primera fila, dos componentes +en la segunda fila y un componente en la tercera fila. + +Los scriplets en el archivo son parte de la estrategia de gestión de vistas que incluyen diferentes subvistas atómicas +basadas en las preferencias del usuario en el Javabean. + +A continuación se muestran dos ejemplos de las subvistas atómicas simuladas utilizadas en el +compuesto: `businessNews.jsp` + +```html + + + + + + +

+ Generic Business News +

+ + + + + + + + + +
Stock prices up across the worldNew tech companies to invest in
Industry leaders unveil new projectPrice fluctuations and what they mean
+ + +``` + +`localNews.jsp` + +```html + + + +
+

+ Generic Local News +

+
    +
  • + Mayoral elections coming up in 2 weeks +
  • +
  • + New parking meter rates downtown coming tomorrow +
  • +
  • + Park renovations to finish by the next year +
  • +
  • + Annual marathon sign ups available online +
  • +
+
+ + +``` + +Los resultados son los siguientes: + +1) El usuario ha puesto su nombre como `Tammy` en los parámetros de la petición y ninguna + preferencia: ![alt text](./etc/images/noparam.png) +2) El usuario ha puesto su nombre como `Johnny` en los parámetros de la petición y tiene preferencia por noticias del + mundo, negocios y ciencia: ![alt text](./etc/images/threeparams.png) + +Las distintas subvistas como `worldNews.jsp`, `businessNews.jsp`, etc. se incluyen condicionalmente en función de los +parámetros de la solicitud. + +**Cómo utilizarlo** + +Para probar este ejemplo, asegúrese de tener Tomcat 10+ instalado. Configure su IDE para construir un archivo WAR a +partir del módulo y despliegue ese archivo en el servidor + +IntelliJ: + +En `Run` y `edit configurations` Asegúrate de que el servidor Tomcat es una de las configuraciones de ejecución. Vaya a +la pestaña de despliegue y asegúrese de que se está construyendo un artefacto llamado `composite-view:war exploded`. Si +no está presente, añada uno. + +Asegúrate de que el artefacto se está construyendo a partir del contenido del directorio `web` y de los resultados de la +compilación del módulo. Apunta la salida del artefacto a un lugar conveniente. Ejecute la configuración y vea la página +de destino, siga las instrucciones de esa página para continuar. + +## Diagrama de clases + +![alt text](./etc/composite_view.png) + +El diagrama de clases aquí muestra el Javabean que es el gestor de vistas. Las vistas son JSP's dentro del directorio +web. + +## Aplicabilidad: + +Utiliza el patrón de diseño Composite View cuando: + +## Desea representar jerarquías parciales de objetos. + +* Esperas que las estructuras compuestas puedan incluir nuevos componentes en el futuro. +* Desea que los clientes puedan ignorar la diferencia entre composiciones de objetos y objetos individuales. Los + clientes tratarán todos los objetos de la estructura compuesta de manera uniforme. + +## Usos conocidos + +* Interfaces gráficas de usuario (GUI) en las que los widgets pueden contener otros widgets (por ejemplo, una ventana + con paneles, botones y campos de texto). +* Estructuras de documentos, como la representación de tablas que contienen filas, que a su vez contienen celdas, todas + las cuales pueden tratarse como elementos de una jerarquía unificada. + +## Consecuencias + +Ventajas: + +* Gran flexibilidad a la hora de añadir nuevos componentes: Como los compuestos y los nodos hoja se tratan de manera + uniforme, es más fácil añadir nuevos tipos de componentes. +* Código cliente simplificado: Los clientes pueden tratar las estructuras compuestas y los elementos individuales de + manera uniforme, lo que reduce la complejidad del código cliente. + +Contrapartidas: + +* Generalización excesiva: El diseño del sistema puede volverse más complejo si haces que todo sea compuesto, + especialmente si tu aplicación no lo requiere. +* Dificultad en la aplicación de restricciones: Puede ser más difícil restringir los componentes de un compuesto a sólo + ciertos tipos. + +## Patrones Relacionados + +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Mientras que Decorator se utiliza para añadir + responsabilidades a los objetos, Composite está pensado para construir estructuras de objetos. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Composite puede combinarse a menudo con Flyweight + para implementar nodos hoja compartidos en una estructura compuesta, reduciendo la huella de memoria. +* [Cadena de responsabilidad](https://java-design-patterns.com/patterns/chain-of-responsibility/): Puede usarse con + Composite para permitir a los componentes pasar peticiones a través de la jerarquía. +* [Composite](https://java-design-patterns.com/patterns/composite/) +* [Ayudante de vista](https://www.oracle.com/java/technologies/viewhelper.html) + +## Créditos + +* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html) +* [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/) +* [Patterns of Enterprise Application Architecture](https://amzn.to/49jpQG3) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3xfntGJ) diff --git a/localization/es/composite-view/etc/composite_view.png b/localization/es/composite-view/etc/composite_view.png new file mode 100644 index 000000000000..66215d9416b8 Binary files /dev/null and b/localization/es/composite-view/etc/composite_view.png differ diff --git a/localization/es/composite-view/etc/images/noparam.png b/localization/es/composite-view/etc/images/noparam.png new file mode 100644 index 000000000000..2979cf2bc657 Binary files /dev/null and b/localization/es/composite-view/etc/images/noparam.png differ diff --git a/localization/es/composite-view/etc/images/threeparams.png b/localization/es/composite-view/etc/images/threeparams.png new file mode 100644 index 000000000000..9338dedb8885 Binary files /dev/null and b/localization/es/composite-view/etc/images/threeparams.png differ diff --git a/localization/es/composite/README.md b/localization/es/composite/README.md new file mode 100644 index 000000000000..62c56e56cdd1 --- /dev/null +++ b/localization/es/composite/README.md @@ -0,0 +1,230 @@ +--- +title: Composite +shortTitle: Composite +category: Structural +language: es +tag: + - Gang of Four + - Object composition + - Recursion +--- + +## También conocido como + +* Object Tree +* Composite Structure + +## Propósito + +Componga objetos en estructuras de árbol para representar jerarquías parte-todo. Composite permite a los clientes tratar +objetos individuales y composiciones de objetos de manera uniforme. + +## Explanation + +Real-world example + +> Cada frase se compone de palabras que a su vez se componen de caracteres. Cada uno de estos objetos es imprimible y +> puede tener algo impreso antes o después de ellos, como la frase siempre termina con punto final y la palabra siempre +> tiene espacio antes de ella. + +En pocas palabras + +> El patrón compuesto permite a los clientes tratar uniformemente los objetos individuales. + +Wikipedia dice + +> En ingeniería de software, el patrón compuesto es un patrón de diseño de partición. El patrón compuesto describe que +> un grupo de objetos debe ser tratado de la misma manera que una única instancia de un objeto. La intención de un +> compuesto es "componer" objetos en estructuras de árbol para representar jerarquías parte-todo. La implementación del +> patrón de composición permite a los clientes tratar los objetos individuales y las composiciones de manera uniforme. + +**Ejemplo programático** + +Tomando nuestro ejemplo anterior. Aquí tenemos la clase base `LetterComposite` y los diferentes tipos +imprimibles `Letter`, `Word` y `Sentence`. + +```java +public abstract class LetterComposite { + + private final List children = new ArrayList<>(); + + public void add(LetterComposite letter) { + children.add(letter); + } + + public int count() { + return children.size(); + } + + protected void printThisBefore() { + } + + protected void printThisAfter() { + } + + public void print() { + printThisBefore(); + children.forEach(LetterComposite::print); + printThisAfter(); + } +} + +public class Letter extends LetterComposite { + + private final char character; + + public Letter(char c) { + this.character = c; + } + + @Override + protected void printThisBefore() { + System.out.print(character); + } +} + +public class Word extends LetterComposite { + + public Word(List letters) { + letters.forEach(this::add); + } + + public Word(char... letters) { + for (char letter : letters) { + this.add(new Letter(letter)); + } + } + + @Override + protected void printThisBefore() { + System.out.print(" "); + } +} + +public class Sentence extends LetterComposite { + + public Sentence(List words) { + words.forEach(this::add); + } + + @Override + protected void printThisAfter() { + System.out.print("."); + } +} +``` + +Entonces tenemos un mensajero para llevar mensajes: + +```java +public class Messenger { + + LetterComposite messageFromOrcs() { + + var words = List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y') + ); + + return new Sentence(words); + + } + + LetterComposite messageFromElves() { + + var words = List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h') + ); + + return new Sentence(words); + + } + +} +``` + +Y entonces se puede utilizar como: + +```java +var messenger=new Messenger(); + + LOGGER.info("Message from the orcs: "); + messenger.messageFromOrcs().print(); + + LOGGER.info("Message from the elves: "); + messenger.messageFromElves().print(); +``` + +La salida de la consola: + +``` +Message from the orcs: + Where there is a whip there is a way. +Message from the elves: + Much wind pours from your mouth. +``` + +## Diagrama de clases + +![alt text](./etc/composite.urm.png "Diagrama de clases compuestas") + +## Aplicabilidad + +Utilice el patrón Composite cuando + +* Desea representar jerarquías parciales de objetos. +* Desea que los clientes puedan ignorar la diferencia entre composiciones de objetos y objetos individuales. Los + clientes tratarán todos los objetos de la estructura compuesta de manera uniforme. + +## Usos conocidos + +* Interfaces gráficas de usuario donde los componentes pueden contener otros componentes (por ejemplo, paneles que + contienen botones, etiquetas, otros paneles). +* Representaciones de sistemas de archivos donde los directorios pueden contener archivos y otros directorios. +* Estructuras organizativas en las que un departamento puede contener subdepartamentos y empleados. +* [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) + y [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) +* Árbol de componentes [Apache Wicket](https://github.com/apache/wicket), + ver [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) + y [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) + +## Consecuencias + +Ventajas: + +* Simplifica el código cliente, ya que puede tratar estructuras compuestas y objetos individuales de manera uniforme. +* Facilita la adición de nuevos tipos de componentes, ya que no es necesario modificar el código existente. + +Contrapartidas: + +* Puede hacer que el diseño sea demasiado general. Puede ser difícil restringir los componentes de un compuesto. +* Puede dificultar la restricción de los tipos de componentes de un compuesto. + +## Patrones relacionados + +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Composite puede usar Flyweight para compartir + instancias de componentes entre varios composites. +* [Iterador](https://java-design-patterns.com/patterns/iterator/): Puede ser utilizado para atravesar estructuras + Composite. +* [Visitante](https://java-design-patterns.com/patterns/visitor/): Puede aplicar una operación sobre una estructura + Composite. + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3xoLAmi) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3vBKXWb) diff --git a/localization/es/composite/etc/composite.urm.png b/localization/es/composite/etc/composite.urm.png new file mode 100644 index 000000000000..93c160f6450a Binary files /dev/null and b/localization/es/composite/etc/composite.urm.png differ diff --git a/localization/es/context-object/README.md b/localization/es/context-object/README.md index d723cbd852dd..5cc16554508d 100644 --- a/localization/es/context-object/README.md +++ b/localization/es/context-object/README.md @@ -1,5 +1,6 @@ --- title: Context object +shortTitle: Context object category: Creational language: es tags: diff --git a/localization/es/converter/README.md b/localization/es/converter/README.md index c346f71c2c4b..199cd79715db 100644 --- a/localization/es/converter/README.md +++ b/localization/es/converter/README.md @@ -1,5 +1,6 @@ --- title: Converter +shortTitle: Converter category: Creational language: es tag: diff --git a/crtp/README.md b/localization/es/crtp/README.md similarity index 54% rename from crtp/README.md rename to localization/es/crtp/README.md index 67627f44da7f..a8160e1ac8bc 100644 --- a/crtp/README.md +++ b/localization/es/crtp/README.md @@ -1,138 +1,135 @@ ---- -title: Curiously Recurring Template Pattern -language: en -category: Structural -tag: -- Extensibility -- Instantiation ---- - -## Name / classification - -Curiously Recurring Template Pattern - -## Also known as - -Recursive Type Bound, Recursive Generic - -## Intent - -Allow derived components to inherit certain functionalities from a base component that are compatible with the derived type. - -## Explanation - -Real-world example - -> For a mixed martial arts promotion that is planning an event, ensuring that the fights are organized between athletes -> of the same weight class is crucial. This prevents mismatches between fighters of significantly different sizes, such -> as a heavyweight facing off against a bantamweight. - -In plain words - -> Make certain methods within a type to accept arguments specific to its subtypes. - -Wikipedia says - -> The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X -> derives from a class template instantiation using X itself as a template argument. - -**Programmatic example** - -Let's define the generic interface Fighter - -```java -public interface Fighter { - - void fight(T t); - -} -``` - -The MMAFighter class is used to instantiate fighters distinguished by their weight class - -```java -public class MmaFighter> implements Fighter { - - private final String name; - private final String surname; - private final String nickName; - private final String speciality; - - public MmaFighter(String name, String surname, String nickName, String speciality) { - this.name = name; - this.surname = surname; - this.nickName = nickName; - this.speciality = speciality; - } - - @Override - public void fight(T opponent) { - LOGGER.info("{} is going to fight against {}", this, opponent); - } - - @Override - public String toString() { - return name + " \"" + nickName + "\" " + surname; - } -``` - -The followings are some subtypes of MmaFighter - -```java -class MmaBantamweightFighter extends MmaFighter { - - public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { - super(name, surname, nickName, speciality); - } - -} - -public class MmaHeavyweightFighter extends MmaFighter { - - public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { - super(name, surname, nickName, speciality); - } - -} -``` - -A fighter is allowed to fight an opponent of the same weight classes, if the opponent is of a different weight class -there is an error - -```java -MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); -MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); -fighter1.fight(fighter2); // This is fine - -MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); -MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); -fighter3.fight(fighter4); // This is fine too - -fighter1.fight(fighter3); // This will raise a compilation error -``` - -## Class diagram - -![alt text](./etc/crtp.png "CRTP class diagram") - -## Applicability - -Use the Curiously Recurring Template Pattern when - -* You have type conflicts when chaining methods in an object hierarchy -* You want to use a parameterized class method that can accept subclasses of the class as arguments, allowing it to be applied to objects that inherit from the class -* You want certain methods to work only with instances of the same type, such as for achieving mutual comparability. - -## Tutorials - -* [The NuaH Blog](https://nuah.livejournal.com/328187.html) -* Yogesh Umesh Vaity answer to [What does "Recursive type bound" in Generics mean?](https://stackoverflow.com/questions/7385949/what-does-recursive-type-bound-in-generics-mean) - -## Known uses - -* [java.lang.Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html) - -## Credits - -* [How do I decrypt "Enum>"?](http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106) -* Chapter 5 Generics, Item 30 in [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) +--- +title: Curiously Recurring Template Pattern +language: es +category: Structural +tag: +- Extensibility +- Instantiation +--- + +## Nombre / clasificación + +Curiously Recurring Template Pattern + +## También conocido como + +Recursive Type Bound, Recursive Generic + +## Propósito + +Permitir que los componentes derivados hereden ciertas funcionalidades de un componente base que sean compatibles con el tipo derivado. + +## Explicación + +Un ejemplo real + +> Para una promoción de artes marciales mixtas que esté planificando un evento, es crucial asegurarse de que los combates se organicen entre atletas de la misma categoría de peso. Así se evitan los enfrentamientos entre luchadores de tallas muy diferentes, como un peso pesado contra un peso gallo. + +En pocas palabras + +> Hacer que ciertos métodos dentro de un tipo acepten argumentos específicos de sus subtipos. + +Wikipedia dice + +> El patrón de plantilla curiosamente recurrente (CRTP) es un modismo, originalmente en C++, en el que una clase X deriva de una instanciación de plantilla de clase usando la propia X como argumento de plantilla. + +**Ejemplo programático** + +Definamos la interfaz genérica Fighter + +```java +public interface Fighter { + + void fight(T t); + +} +``` + +La clase `MMAFighter` se utiliza para crear luchadores que se distinguen por su categoría de peso. + +```java +public class MmaFighter> implements Fighter { + + private final String name; + private final String surname; + private final String nickName; + private final String speciality; + + public MmaFighter(String name, String surname, String nickName, String speciality) { + this.name = name; + this.surname = surname; + this.nickName = nickName; + this.speciality = speciality; + } + + @Override + public void fight(T opponent) { + LOGGER.info("{} is going to fight against {}", this, opponent); + } + + @Override + public String toString() { + return name + " \"" + nickName + "\" " + surname; + } +``` + +Los siguientes son algunos subtipos de `MmaFighter` + +```java +class MmaBantamweightFighter extends MmaFighter { + + public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} + +public class MmaHeavyweightFighter extends MmaFighter { + + public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} +``` + +Un luchador puede enfrentarse a un oponente de la misma categoría de peso, si el oponente es de una categoría de peso diferente +se produce un error. + +```java +MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); +MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); +fighter1.fight(fighter2); // This is fine + +MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); +MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); +fighter3.fight(fighter4); // This is fine too + +fighter1.fight(fighter3); // This will raise a compilation error +``` + +## Diagrama de clases + +![alt text](./etc/crtp.png "Diagrama de clases CRTP") + +## Aplicabilidad + +Utilice el Patrón de Plantilla Curiosamente Recurrente cuando + +* Tienes conflictos de tipos al encadenar métodos en una jerarquía de objetos +* Desea utilizar un método de clase parametrizado que pueda aceptar subclases de la clase como argumentos, permitiendo que se aplique a objetos que heredan de la clase +* Desea que ciertos métodos funcionen sólo con instancias del mismo tipo, por ejemplo, para lograr la comparabilidad mutua. + +## Tutoriales + +* [El blog de NuaH](https://nuah.livejournal.com/328187.html) +* Respuesta de Yogesh Umesh Vaity a [¿Qué significa "Recursive type bound" en Generics?](https://stackoverflow.com/questions/7385949/what-does-recursive-type-bound-in-generics-mean) + +## Usos conocidos + +* [java.lang.Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html) + +## Créditos + +* [How do I decrypt "Enum>"?](http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106) +* Chapter 5 Generics, Item 30 in [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) diff --git a/localization/es/crtp/etc/crtp.png b/localization/es/crtp/etc/crtp.png new file mode 100644 index 000000000000..a348c8af6175 Binary files /dev/null and b/localization/es/crtp/etc/crtp.png differ diff --git a/localization/es/data-locality/README.md b/localization/es/data-locality/README.md new file mode 100644 index 000000000000..bef09ce8b383 --- /dev/null +++ b/localization/es/data-locality/README.md @@ -0,0 +1,30 @@ +--- +title: Data Locality +shortTitle: Data Locality +category: Behavioral +language: es +tag: + - Game programming + - Performance +--- + +## Propósito +Acelera el acceso a la memoria organizando los datos para aprovechar la caché de la CPU. + +Las CPU modernas disponen de cachés para acelerar el acceso a la memoria. Éstas pueden acceder mucho más rápido a la memoria adyacente a la memoria a la que se ha accedido recientemente. Aprovéchate de ello para mejorar el rendimiento aumentando la localidad de los datos, manteniéndolos en memoria contigua en el orden en que los procesas. + +## Diagrama de clases +![alt text](./etc/data-locality.urm.png "Data Locality pattern class diagram") + +## Aplicabilidad + +* Como la mayoría de las optimizaciones, la primera pauta para usar el patrón Data Locality es cuando se tiene un problema de rendimiento. +* Con este patrón específicamente, también querrás estar seguro de que tus problemas de rendimiento son causados por pérdidas de caché. + +## Ejemplo del mundo real + +* El motor de juego [Artemis](http://gamadu.com/artemis/) es uno de los primeros y más conocidos frameworks que utiliza IDs simples para las entidades del juego. + +## Créditos + +* [Game Programming Patterns Optimization Patterns: Data Locality](http://gameprogrammingpatterns.com/data-locality.html) \ No newline at end of file diff --git a/localization/es/data-locality/etc/data-locality.urm.png b/localization/es/data-locality/etc/data-locality.urm.png new file mode 100644 index 000000000000..d19873739551 Binary files /dev/null and b/localization/es/data-locality/etc/data-locality.urm.png differ diff --git a/localization/es/decorator/README.md b/localization/es/decorator/README.md new file mode 100644 index 000000000000..92bffb207702 --- /dev/null +++ b/localization/es/decorator/README.md @@ -0,0 +1,170 @@ +--- +title: Decorator +shortTitle: Decorator +category: Structural +language: es +tag: + - Gang of Four + - Extensibility +--- + +## También conocido como + +Wrapper + +## Propósito + +Adjunte responsabilidades adicionales a un objeto de forma dinámica. Los decoradores proporcionan una alternativa +flexible a la subclase para ampliar la funcionalidad. + +## Explicación + +Ejemplo real + +> En las colinas cercanas vive un trol furioso. Normalmente va con las manos desnudas, pero a veces lleva un arma. Para +> armar al troll no es necesario crear un nuevo troll sino decorarlo dinámicamente con un arma adecuada. + +En pocas palabras + +> El patrón decorador permite cambiar dinámicamente el comportamiento de un objeto en tiempo de ejecución envolviéndolo +> en un objeto de una clase decoradora. + +Wikipedia says + +> En programación orientada a objetos, el patrón decorador es un patrón de diseño que permite añadir comportamiento a un +> objeto individual, ya sea de forma estática o dinámica, sin afectar al comportamiento de otros objetos de la misma +> clase. El patrón decorador suele ser útil para adherirse al Principio de Responsabilidad Única, ya que permite dividir +> la funcionalidad entre clases con áreas de interés únicas, así como al Principio Abierto-Cerrado, al permitir extender +> la funcionalidad de una clase sin modificarla. + +**Ejemplo programático** + +Tomemos el ejemplo del troll. En primer lugar tenemos un `SimpleTroll` que implementa la interfaz `Troll`: + +```java +public interface Troll { + void atacar(); + int getPoderAtaque(); + void huirBatalla(); +} + +@Slf4j +public class SimpleTroll implements Troll { + + @Override + public void atacar() { + LOGGER.info("¡El troll intenta atraparte!"); + } + + @Override + public int getPoderAtaque() { + return 10; + } + + @Override + public void huirBatalla() { + LOGGER.info("¡El troll chilla de horror y huye!"); + } +} +``` + +A continuación, queremos añadir un palo para el troll. Podemos hacerlo de forma dinámica mediante el uso de un +decorador: + +```java +@Slf4j +public class TrollConGarrote implements Troll { + + private final Troll decorado; + + public TrollConGarrote(Troll decorado) { + this.decorado = decorado; + } + + @Override + public void atacar() { + decorado.atacar(); + LOGGER.info("¡El troll te golpea con un garrote!"); + } + + @Override + public int getPoderAtaque() { + return decorado.getPoderAtaque() + 10; + } + + @Override + public void huirBatalla() { + decorado.huirBatalla(); + } +} +``` + +Aquí está el troll en acción: + +```java +// troll simple +LOGGER.info("Un troll de aspecto simple se acerca."); +var troll = new SimpleTroll(); +troll.atacar(); +troll.huirBatalla(); +LOGGER.info("Poder del troll simple: {}.\n", troll.getPoderAtaque()); + +// cambia el comportamiento del troll simple agregando un decorador +LOGGER.info("Un troll con un enorme garrote te sorprende."); +var trollConGarrote = new TrollConGarrote(troll); +trollConGarrote.atacar(); +trollConGarrote.huirBatalla(); +LOGGER.info("Poder del troll con garrote: {}.\n", trollConGarrote.getPoderAtaque()); +``` + +Salida del programa: + +```java +Un troll de aspecto simple se acerca. +¡El troll intenta atraparte! +¡El troll chilla de horror y huye! +Poder del troll simple: 10. + +Un troll con un enorme garrote te sorprende. +¡El troll intenta atraparte! +¡El troll te golpea con un garrote! +¡El troll chilla de horror y huye! +Poder del troll con garrote: 20. +``` + +## Diagrama de clases + +![alt text](./etc/decorator.urm.png "Decorator pattern class diagram") + +## Aplicabilidad + +Decorator se utiliza para: + +* Añadir responsabilidades a objetos individuales de forma dinámica y transparente, es decir, sin + afectar a otros objetos. +* Para responsabilidades que pueden ser retiradas. +* Cuando la extensión por subclase es poco práctica. A veces es posible un gran número de extensiones independientes + son posibles y producirían una explosión de subclases para soportar cada combinación. O la definición de una clase + puede estar oculta o no estar disponible para subclases. + +## Tutoriales + +* [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) + +## Usos conocidos + +* [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), + [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) + y [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) +* [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) +* [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) +* [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/localization/es/decorator/etc/decorator.urm.png b/localization/es/decorator/etc/decorator.urm.png new file mode 100644 index 000000000000..141c0563f0c6 Binary files /dev/null and b/localization/es/decorator/etc/decorator.urm.png differ diff --git a/localization/es/delegation/README.md b/localization/es/delegation/README.md new file mode 100644 index 000000000000..ba128d45a532 --- /dev/null +++ b/localization/es/delegation/README.md @@ -0,0 +1,126 @@ +--- +title: Delegation +shortTitle: Delegation +category: Structural +language: es +tag: + - Decoupling +--- + +## También conocido como + +Proxy Pattern + +## Propósito + +Es una técnica en la que un objeto expresa cierto comportamiento al exterior pero en realidad delega la responsabilidad +de implementar ese comportamiento en un objeto asociado. + +## Explanation + +Real-world example + +> Imaginemos que tenemos aventureros que luchan contra monstruos con diferentes armas dependiendo de sus habilidades y +> destrezas. Debemos ser capaces de equiparles con diferentes sin tener que modificar su código fuente para cada una de +> ellas. El patrón de delegación lo hace posible delegando el trabajo dinámico a un objeto específico que implementa una +> interfaz con métodos relevantes. + +Wikipedia dice + +> En programación orientada a objetos, la delegación se refiere a la evaluación de un miembro (propiedad o método) de un +> objeto (el receptor) en el contexto de otro objeto original (el emisor). La delegación puede hacerse explícitamente, +> pasando el objeto emisor al objeto receptor, lo que puede hacerse en cualquier lenguaje orientado a objetos; o +> implícitamente, mediante las reglas de búsqueda de miembros del lenguaje, lo que requiere soporte del lenguaje para la +> función. + +**Ejemplo programático** + +Tenemos una interfaz `Printer` y tres implementaciones `CanonPrinter`, `EpsonPrinter` y `HpPrinter`. + +```java +public interface Printer { + void print(final String message); +} + +@Slf4j +public class CanonPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("Canon Printer : {}", message); + } +} + +@Slf4j +public class EpsonPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("Epson Printer : {}", message); + } +} + +@Slf4j +public class HpPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("HP Printer : {}", message); + } +} +``` + +El `PrinterController` puede ser utilizado como un `Printer` delegando cualquier trabajo manejado por este +a un objeto que la implemente. + +```java +public class PrinterController implements Printer { + + private final Printer printer; + + public PrinterController(Printer printer) { + this.printer = printer; + } + + @Override + public void print(String message) { + printer.print(message); + } +} +``` + +Now on the client code printer controllers can print messages differently depending on the +object they're delegating that work to. + +```java +private static final String MESSAGE_TO_PRINT = "hello world"; + +var hpPrinterController = new PrinterController(new HpPrinter()); +var canonPrinterController = new PrinterController(new CanonPrinter()); +var epsonPrinterController = new PrinterController(new EpsonPrinter()); + +hpPrinterController.print(MESSAGE_TO_PRINT); +canonPrinterController.print(MESSAGE_TO_PRINT); +epsonPrinterController.print(MESSAGE_TO_PRINT) +``` + +Salida del programa: + +```java +HP Printer : hello world +Canon Printer : hello world +Epson Printer : hello world +``` + +## Diagrama de clases + +![alt text](./etc/delegation.png "Delegate") + +## Aplicabilidad + +Utilice el patrón Delegate para conseguir lo siguiente + +* Reducir el acoplamiento de los métodos a su clase +* Componentes que se comportan de forma idéntica, pero teniendo en cuenta que esta situación puede cambiar en el futuro. + +## Créditos + +* [Delegate Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Delegation_pattern) +* [Proxy Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Proxy_pattern) diff --git a/localization/es/delegation/etc/delegation.png b/localization/es/delegation/etc/delegation.png new file mode 100644 index 000000000000..375ef4d6b00f Binary files /dev/null and b/localization/es/delegation/etc/delegation.png differ diff --git a/localization/es/dependency-injection/README.md b/localization/es/dependency-injection/README.md index 3bb4a9002c92..020e5fddb438 100644 --- a/localization/es/dependency-injection/README.md +++ b/localization/es/dependency-injection/README.md @@ -1,5 +1,6 @@ --- title: Dependency Injection +shortTitle: Dependency Injection category: Creational language: es tag: diff --git a/localization/es/dirty-flag/README.md b/localization/es/dirty-flag/README.md new file mode 100644 index 000000000000..71b9351da199 --- /dev/null +++ b/localization/es/dirty-flag/README.md @@ -0,0 +1,28 @@ +--- +title: Dirty Flag +shortTitle: Dirty Flag +category: Behavioral +language: es +tag: + - Game programming + - Performance +--- + +## También conocido como +* IsDirty pattern + +## Propósito +Evitar la costosa readquisición de recursos. Los recursos conservan su identidad, se guardan en algún almacenamiento de acceso rápido y se reutilizan para evitar tener que adquirirlos de nuevo. + +## Diagrama de clases +![alt text](./etc/dirty-flag.png "Dirty Flag") + +## Aplicabilidad +Utilice el patrón Dirty Flag cuando + +* La adquisición, inicialización y liberación repetitiva del mismo recurso causa una sobrecarga de rendimiento innecesaria. + +## Créditos + +* [Design Patterns: Dirty Flag](https://www.takeupcode.com/podcast/89-design-patterns-dirty-flag/) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) diff --git a/localization/es/dirty-flag/etc/dirty-flag.png b/localization/es/dirty-flag/etc/dirty-flag.png new file mode 100644 index 000000000000..98d4f679d17c Binary files /dev/null and b/localization/es/dirty-flag/etc/dirty-flag.png differ diff --git a/localization/es/double-buffer/README.md b/localization/es/double-buffer/README.md new file mode 100644 index 000000000000..4caaf3691628 --- /dev/null +++ b/localization/es/double-buffer/README.md @@ -0,0 +1,242 @@ +--- +title: Double Buffer +shortTitle: Double Buffer +category: Behavioral +language: es +tag: + - Performance + - Game programming +--- + +## Propósito + +Doble búfer es un término utilizado para describir un dispositivo que tiene dos búferes. El uso de varios búferes aumenta el rendimiento global de un dispositivo y ayuda a evitar cuellos de botella. Este ejemplo muestra el uso de doble búfer en gráficos. Se utiliza para mostrar una imagen o un fotograma mientras se almacena en el búfer otro fotograma que se mostrará a continuación. Este método hace que las animaciones y los juegos parezcan más realistas que los realizados en modo de búfer único. + +## Explicación + +Ejemplo del mundo real +> Un ejemplo típico, y que todo motor de juego debe abordar, es el renderizado. Cuando el juego dibuja el mundo que ven los usuarios, lo hace pieza a pieza: las montañas a lo lejos, las colinas ondulantes, los árboles, cada uno a su vez. Si el usuario viera cómo se dibuja la vista de forma incremental, se rompería la ilusión de un mundo coherente. La escena debe actualizarse con fluidez y rapidez, mostrando una serie de fotogramas completos, cada uno de los cuales aparece al instante. La doble memoria intermedia resuelve el problema. + +En pocas palabras +> Garantiza un estado que se renderiza correctamente mientras ese estado se modifica de forma incremental. Se utiliza mucho en gráficos por ordenador. + +Wikipedia dice +> En informática, el almacenamiento en búfer múltiple es el uso de más de un búfer para contener un bloque de datos, de modo que un "lector" vea una versión completa (aunque quizás antigua) de los datos, en lugar de una versión parcialmente actualizada de los datos que está creando un "escritor". Se utiliza mucho en las imágenes de ordenador. + +**Ejemplo programático** + +Interfaz `Buffer` que asegura las funcionalidades básicas de un buffer. + +```java +/** + * Buffer interface. + */ +public interface Buffer { + + /** + * Clear the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void clear(int x, int y); + + /** + * Draw the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void draw(int x, int y); + + /** + * Clear all the pixels. + */ + void clearAll(); + + /** + * Get all the pixels. + * + * @return pixel list + */ + Pixel[] getPixels(); + +} +``` + +Una de las implementaciones de la interfaz `Buffer`. + +```java +/** + * FrameBuffer implementation class. + */ +public class FrameBuffer implements Buffer { + + public static final int WIDTH = 10; + public static final int HEIGHT = 8; + + private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; + + public FrameBuffer() { + clearAll(); + } + + @Override + public void clear(int x, int y) { + pixels[getIndex(x, y)] = Pixel.WHITE; + } + + @Override + public void draw(int x, int y) { + pixels[getIndex(x, y)] = Pixel.BLACK; + } + + @Override + public void clearAll() { + Arrays.fill(pixels, Pixel.WHITE); + } + + @Override + public Pixel[] getPixels() { + return pixels; + } + + private int getIndex(int x, int y) { + return x + WIDTH * y; + } +} +``` + +```java +/** + * Pixel enum. Each pixel can be white (not drawn) or black (drawn). + */ +public enum Pixel { + + WHITE, BLACK; +} +``` + +`Scene` representa la escena del juego en la que ya se ha renderizado el búfer actual. + +```java +/** + * Scene class. Render the output frame. + */ +@Slf4j +public class Scene { + + private final Buffer[] frameBuffers; + + private int current; + + private int next; + + /** + * Constructor of Scene. + */ + public Scene() { + frameBuffers = new FrameBuffer[2]; + frameBuffers[0] = new FrameBuffer(); + frameBuffers[1] = new FrameBuffer(); + current = 0; + next = 1; + } + + /** + * Draw the next frame. + * + * @param coordinateList list of pixels of which the color should be black + */ + public void draw(List> coordinateList) { + LOGGER.info("Start drawing next frame"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + frameBuffers[next].clearAll(); + coordinateList.forEach(coordinate -> { + var x = coordinate.getKey(); + var y = coordinate.getValue(); + frameBuffers[next].draw(x, y); + }); + LOGGER.info("Swap current and next buffer"); + swap(); + LOGGER.info("Finish swapping"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + } + + public Buffer getBuffer() { + LOGGER.info("Get current buffer: " + current); + return frameBuffers[current]; + } + + private void swap() { + current = current ^ next; + next = current ^ next; + current = current ^ next; + } + +} +``` + +```java +public static void main(String[] args) { + final var scene = new Scene(); + var drawPixels1 = List.of(new MutablePair<>(1, 1), new MutablePair<>(5, 6), new MutablePair<>(3, 2)); + scene.draw(drawPixels1); + var buffer1 = scene.getBuffer(); + printBlackPixelCoordinate(buffer1); + + var drawPixels2 = List.of(new MutablePair<>(3, 7), new MutablePair<>(6, 1)); + scene.draw(drawPixels2); + var buffer2 = scene.getBuffer(); + printBlackPixelCoordinate(buffer2); +} + +private static void printBlackPixelCoordinate(Buffer buffer) { + StringBuilder log = new StringBuilder("Black Pixels: "); + var pixels = buffer.getPixels(); + for (var i = 0; i < pixels.length; ++i) { + if (pixels[i] == Pixel.BLACK) { + var y = i / FrameBuffer.WIDTH; + var x = i % FrameBuffer.WIDTH; + log.append(" (").append(x).append(", ").append(y).append(")"); + } + } + LOGGER.info(log.toString()); +} +``` + +La salida de la consola + +```text +[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 +[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer +[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 +[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 1 +[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (1, 1) (3, 2) (5, 6) +[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 +[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer +[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 +[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 0 +[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (6, 1) (3, 7) +``` + +## Diagrama de clases + +![alt text](./etc/double-buffer.urm.png "Double Buffer pattern class diagram") + +## Aplicabilidad + +Este patrón es uno de esos que sabrás cuándo lo necesitas. Si tienes un sistema que carece de doble búfer, probablemente tendrá un aspecto visiblemente incorrecto (tearing, etc.) o se comportará de forma incorrecta. Pero decir "lo sabrás cuando lo necesites" no da mucho de sí. Más concretamente, este patrón es apropiado cuando todo esto es cierto: + +- Tenemos algún estado que está siendo modificado incrementalmente. +- Ese mismo estado puede ser accedido en medio de la modificación. +- Queremos evitar que el código que está accediendo al estado vea el trabajo en curso. +- Queremos poder leer el estado y no queremos tener que esperar mientras se escribe. + +## Créditos + +* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html) diff --git a/localization/es/double-buffer/etc/double-buffer.urm.png b/localization/es/double-buffer/etc/double-buffer.urm.png new file mode 100644 index 000000000000..072ec4dad8be Binary files /dev/null and b/localization/es/double-buffer/etc/double-buffer.urm.png differ diff --git a/localization/es/embedded-value/README.md b/localization/es/embedded-value/README.md new file mode 100644 index 000000000000..4c1c0700f104 --- /dev/null +++ b/localization/es/embedded-value/README.md @@ -0,0 +1,139 @@ +--- +title: Embedded Value +shortTitle: Embedded Value +category: Structural +language: es +tag: + - Data Access + - Enterprise Application Pattern +--- + +## También conocido como + +Asignación agregada, Compositor + +## Propósito + +Muchos objetos pequeños tienen sentido en un sistema OO que no tienen sentido como tablas en una base de datos. Un valor +incrustado asigna los valores de un objeto a campos del registro del propietario del objeto. + +## Explicación + +Ejemplo real + +> Algunos ejemplos son los objetos monetarios y los intervalos de fechas. Aunque el pensamiento por defecto es guardar +> un objeto como una tabla, ninguna persona en su sano juicio querría una tabla de valores monetarios. +> Otro ejemplo serían los pedidos online que tienen una dirección de envío como calle, ciudad, estado. Asignamos estos +> valores del objeto Dirección de envío a los campos del registro del objeto Pedido. + +En pocas palabras + +> El patrón de valores incrustados permite asignar un objeto a varios campos de la tabla de otro objeto. + +**Ejemplo programático** + +Consideremos el ejemplo de un pedido online donde tenemos detalles del artículo pedido y la dirección de envío. Tenemos +la dirección de envío incrustada en el objeto Pedido. Pero en la base de datos asignamos los valores de la dirección de +envío en el registro del pedido en lugar de crear una tabla separada para la dirección de envío y utilizar una clave +externa para hacer referencia al objeto del pedido. + +Primero, tenemos POJOs `Order` y `ShippingAddress`. + +```java +public class Order { + + private int id; + private String item; + private String orderedBy; + private ShippingAddress ShippingAddress; + + public Order(String item, String orderedBy, ShippingAddress ShippingAddress) { + this.item = item; + this.orderedBy = orderedBy; + this.ShippingAddress = ShippingAddress; + } +``` + +```java +public class ShippingAddress { + + private String city; + private String state; + private String pincode; + + public ShippingAddress(String city, String state, String pincode) { + this.city = city; + this.state = state; + this.pincode = pincode; + } +} +``` + +Ahora, tenemos que crear sólo una tabla para el Pedido junto con los campos para los atributos de la dirección de envío. + +```Sql +CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50) city VARCHAR(50), state VARCHAR(50), pincode CHAR(6) NOT NULL, PRIMARY KEY(Id)) +``` + +Mientras realizamos las consultas e inserciones en la base de datos, encasillamos y desencasillamos los detalles de las +direcciones de envío. + +```java +final String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES (?, ?, ?, ?, ?)"; + +public boolean insertOrder(Order order) throws Exception { + var insertOrder = new PreparedStatement(INSERT_ORDER); + var address = order.getShippingAddress(); + conn.setAutoCommit(false); + insertIntoOrders.setString(1, order.getItem()); + insertIntoOrders.setString(2, order.getOrderedBy()); + insertIntoOrders.setString(3, address.getCity()); + insertIntoOrders.setString(4, address.getState()); + insertIntoOrders.setString(5, address.getPincode()); + + var affectedRows = insertIntoOrders.executeUpdate(); + if(affectedRows == 1){ + Logger.info("Inserted successfully"); + }else{ + Logger.info("Couldn't insert " + order); + } +} +``` + +## Diagrama de clases + +![alt text](./etc/embedded-value.urm.png "Embedded value class diagram") + +## Aplicabilidad + +Utilice el patrón Valor incrustado cuando + +* Muchos objetos pequeños tienen sentido en un sistema OO que no tienen sentido como tablas en una base de datos. +* Los casos más simples para Embedded Value son los claros y simples Value Objects como dinero y rango de fechas. +* Si está mapeando a un esquema existente, puede utilizar este patrón cuando una tabla contiene datos que desea dividir + en más de un objeto en memoria. Esto puede ocurrir cuando se desea factorizar algún comportamiento en el modelo de + objetos. +* En la mayoría de los casos, sólo utilizará el valor incrustado en un objeto de referencia cuando la asociación entre + ellos tenga un único valor en ambos extremos (una asociación uno a uno). + +## Tutoriales + +* [Dzone](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-3) +* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) +* [Five's Weblog](https://powerdream5.wordpress.com/2007/10/09/embedded-value/) + +## Consecuencias + +* La gran ventaja del valor incrustado es que permite realizar consultas SQL contra los valores del objeto dependiente. +* El objeto valor incrustado no tiene ningún comportamiento de persistencia. +* Al usar esto, tienes que tener cuidado de que cualquier cambio en el dependiente marque al propietario como sucio-lo + cual no es un problema con los Objetos de Valor que son reemplazados en el propietario. +* Otro problema es la carga y el guardado. Si sólo carga la memoria del objeto incrustado cuando carga el propietario, + eso es un argumento para guardar ambos en la misma tabla. +* Otra cuestión es si querrás acceder a los datos de los objetos incrustados por separado a través de SQL. Esto puede + ser importante si estás haciendo informes a través de SQL y no tienes una base de datos separada para los informes. + +## Créditos + +* [Fowler, Martin - Patterns of enterprise application architecture-Addison-Wesley](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) +* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) \ No newline at end of file diff --git a/localization/es/embedded-value/etc/embedded-value.urm.png b/localization/es/embedded-value/etc/embedded-value.urm.png new file mode 100644 index 000000000000..52d3ffb62352 Binary files /dev/null and b/localization/es/embedded-value/etc/embedded-value.urm.png differ diff --git a/localization/es/event-aggregator/README.md b/localization/es/event-aggregator/README.md new file mode 100644 index 000000000000..83e40117dd4d --- /dev/null +++ b/localization/es/event-aggregator/README.md @@ -0,0 +1,171 @@ +--- +title: Event Aggregator +shortTitle: Event Aggregator +category: Structural +language: es +tag: + - Reactive +--- + +## Nombre + +Agregador de eventos + +## Intención + +Un sistema con muchos objetos puede resultar complejo cuando un cliente quiere suscribirse a eventos. El cliente tiene +que encontrar y registrarse para +cada objeto individualmente, si cada objeto tiene múltiples eventos entonces cada evento requiere una suscripción +separada. Un Agregador de Eventos actúa como una única fuente +de eventos para muchos objetos. Se registra para todos los eventos de los muchos objetos permitiendo a los clientes +registrarse sólo con el agregador. + +## Explicación + +Un ejemplo real + +> El rey Joffrey se sienta en el trono de hierro y gobierna los siete reinos de Poniente. Recibe la mayor parte de su +> información crítica de Mano del Rey, el segundo al mando. La Mano del Rey tiene muchos consejeros cercanos que le +> proporcionan información relevante sobre los acontecimientos que ocurren en el reino. + +En pocas palabras + +> El Agregador de Eventos es un mediador de eventos que recoge eventos de múltiples fuentes y los entrega a los +> observadores registrados. + +**Ejemplo programático** + +En nuestro ejemplo programático, demostramos la implementación de un patrón agregador de eventos. Algunos de los objetos +son escuchadores de eventos, otros son emisores de eventos, y el agregador de eventos hace ambas cosas. + +```java +public interface EventObserver { + void onEvent(Event e); +} + +public abstract class EventEmitter { + + private final Map> observerLists; + + public EventEmitter() { + observerLists = new HashMap<>(); + } + + public final void registerObserver(EventObserver obs, Event e) { + ... + } + + protected void notifyObservers(Event e) { + ... + } +} +``` + +`KingJoffrey` está escuchando eventos de `KingsHand`. + +```java +@Slf4j +public class KingJoffrey implements EventObserver { + @Override + public void onEvent(Event e) { + LOGGER.info("Received event from the King's Hand: {}", e.toString()); + } +} +``` + +El `ReyMano` está escuchando los acontecimientos de sus subordinados `LordBaelish`, `LordVarys` y `Scout`. +Lo que escuche de ellos, se lo entrega al "Rey Joffrey". + +```java +public class KingsHand extends EventEmitter implements EventObserver { + + public KingsHand() { + } + + public KingsHand(EventObserver obs, Event e) { + super(obs, e); + } + + @Override + public void onEvent(Event e) { + notifyObservers(e); + } +} +``` + +Por ejemplo, `LordVarys` encuentra un traidor cada domingo y lo notifica al `KingsHand`. + +```java +@Slf4j +public class LordVarys extends EventEmitter implements EventObserver { + @Override + public void timePasses(Weekday day) { + if (day == Weekday.SATURDAY) { + notifyObservers(Event.TRAITOR_DETECTED); + } + } +} +``` + +El siguiente fragmento muestra cómo se construyen y conectan los objetos. + +```java + var kingJoffrey = new KingJoffrey(); + + var kingsHand = new KingsHand(); + kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED); + kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED); + kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING); + kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED); + + var varys = new LordVarys(); + varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED); + varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED); + + var scout = new Scout(); + scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING); + scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED); + + var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); + + var emitters = List.of( + kingsHand, + baelish, + varys, + scout + ); + + Arrays.stream(Weekday.values()) + .>map(day -> emitter -> emitter.timePasses(day)) + .forEachOrdered(emitters::forEach); +``` + +La salida de la consola después de ejecutar el ejemplo. + +``` +18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected +``` + +## Diagrama de clases + +![alt text](etc/classes.png "Event Aggregator") + +## Aplicabilidad + +Utilice el patrón Agregador de eventos cuando + +* El Agregador de Eventos es una buena opción cuando tienes muchos objetos que son fuentes potenciales de eventos. En + lugar de hacer que el observador se ocupe de registrarse con todos ellos, puedes centralizar la lógica de registro en + el Agregador de Eventos. Además de simplificar el registro, un Agregador de Eventos también simplifica los problemas + de gestión de memoria en el uso de observadores. + +## Patrones relacionados + +* [Observer](https://java-design-patterns.com/patterns/observer/) + +## Créditos + +* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html) diff --git a/localization/es/event-aggregator/etc/classes.png b/localization/es/event-aggregator/etc/classes.png new file mode 100644 index 000000000000..295719ea3712 Binary files /dev/null and b/localization/es/event-aggregator/etc/classes.png differ diff --git a/localization/es/extension-objects/README.md b/localization/es/extension-objects/README.md new file mode 100644 index 000000000000..5e01d85c8740 --- /dev/null +++ b/localization/es/extension-objects/README.md @@ -0,0 +1,143 @@ +--- +title: Extension objects +shortTitle: Extension objects +category: Behavioral +language: es +tag: + - Extensibility +--- + +# Extention Objects Pattern + +## Propósito +Anticipar que la interfaz de un objeto debe ampliarse en el futuro. Las interfaces adicionales se definen mediante objetos de extensión (Extension objects). + +## Explicación +Ejemplo real + +> Suponga que está desarrollando un juego basado en Java para un cliente y, en mitad del proceso de desarrollo, le sugieren nuevas funcionalidades. El patrón Extension Objects permite a su programa adaptarse a cambios imprevistos con una refactorización mínima, especialmente al integrar funcionalidades adicionales en su proyecto. + +En palabras sencillas + +> El patrón Extension Objects se utiliza para añadir dinámicamente funcionalidad a los objetos sin modificar sus clases principales. Es un patrón de diseño de comportamiento utilizado para añadir nuevas funcionalidades a clases y objetos existentes dentro de un programa. Este patrón proporciona a los programadores la capacidad de extender/modificar la funcionalidad de las clases sin tener que refactorizar el código fuente existente. + +Wikipedia dice + +> En la programación informática orientada a objetos, un patrón de objetos de extensión es un patrón de diseño añadido a un objeto después de que el objeto original fue compilado. El objeto modificado es a menudo una clase, un prototipo o un tipo. Los patrones de objetos de extensión son características de algunos lenguajes de programación orientados a objetos. No hay diferencia sintáctica entre llamar a un método de extensión y llamar a un método declarado en la definición del tipo. + +**Ejemplo programático** + +El objetivo de utilizar el patrón de objetos de extensión (Extension objects) es implementar nuevas características/funcionalidades sin tener que refactorizar cada clase. +Los siguientes ejemplos muestran la utilización de este patrón para una clase Enemigo que extiende Entidad dentro de un juego: + +Clase App primaria desde la que ejecutar nuestro programa. + +```java +public class App { + public static void main(String[] args) { + Entity enemy = new Enemy("Enemy"); + checkExtensionsForEntity(enemy); + } + + private static void checkExtensionsForEntity(Entity entity) { + Logger logger = Logger.getLogger(App.class.getName()); + String name = entity.getName(); + Function func = (e) -> () -> logger.info(name + " without " + e); + + String extension = "EnemyExtension"; + Optional.ofNullable(entity.getEntityExtension(extension)) + .map(e -> (EnemyExtension) e) + .ifPresentOrElse(EnemyExtension::extendedAction, func.apply(extension)); + } +} +``` +Clase de enemigo con acciones iniciales y extensiones. + +```java +class Enemy extends Entity { + public Enemy(String name) { + super(name); + } + + @Override + protected void performInitialAction() { + super.performInitialAction(); + System.out.println("Enemy wants to attack you."); + } + + @Override + public EntityExtension getEntityExtension(String extensionName) { + if (extensionName.equals("EnemyExtension")) { + return Optional.ofNullable(entityExtension).orElseGet(EnemyExtension::new); + } + return super.getEntityExtension(extensionName); + } +} +``` + +Clase EnemyExtension con sobreescritura del método extendAction(). + +```java +class EnemyExtension implements EntityExtension { + @Override + public void extendedAction() { + System.out.println("Enemy has advanced towards you!"); + } +} +``` + +Clase de entidad que será extendida por Enemy. + +```java +class Entity { + private String name; + protected EntityExtension entityExtension; + + public Entity(String name) { + this.name = name; + performInitialAction(); + } + + protected void performInitialAction() { + System.out.println(name + " performs the initial action."); + } + + public EntityExtension getEntityExtension(String extensionName) { + return null; + } + + public String getName() { + return name; + } +} +``` +Interfaz EntityExtension que utilizará EnemyExtension. + +```java +interface EntityExtension { + void extendedAction(); +} +``` +Salida del programa: + +```markdown +Enemy performs the initial action. +Enemy wants to attack you. +Enemy has advanced towards you! +``` +En este ejemplo, el patrón de Objetos de Extensión permite a la entidad enemiga realizar acciones iniciales únicas y acciones avanzadas cuando se aplican extensiones específicas. Este patrón proporciona flexibilidad y extensibilidad a la base de código a la vez que minimiza la necesidad de realizar cambios importantes en el código. + +## Diagrama de clases +![Extension_objects](./etc/extension_obj.png "Extension objects") + +## Aplicabilidad +Utilice el patrón de Objetos de Extensión (Extension objects) cuando: + +* Necesita soportar la adición de interfaces nuevas o imprevistas a clases existentes y no quieres impactar a clientes que no necesitan esta nueva interfaz. Los objetos de extensión te permiten mantener juntas operaciones relacionadas definiéndolas en una clase separada +* Una clase que representa una abstracción clave desempeña diferentes funciones para diferentes clientes. El número de funciones que puede desempeñar la clase debe ser ilimitado. Es necesario preservar la propia abstracción clave. Por ejemplo, un objeto cliente sigue siendo un objeto cliente aunque distintos subsistemas lo vean de forma diferente. +* Una clase debe ser extensible con nuevos comportamientos sin necesidad de subclasificar a partir de ella. + +## Ejemplos del mundo real + +* [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc) +* [Object Linking and Embedding](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding) diff --git a/localization/es/extension-objects/etc/extension_obj.png b/localization/es/extension-objects/etc/extension_obj.png new file mode 100644 index 000000000000..a2b750e9dedd Binary files /dev/null and b/localization/es/extension-objects/etc/extension_obj.png differ diff --git a/localization/es/facade/README.md b/localization/es/facade/README.md new file mode 100644 index 000000000000..75f59817aaea --- /dev/null +++ b/localization/es/facade/README.md @@ -0,0 +1,225 @@ +--- +title: Facade +shortTitle: Facade +category: Structural +language: es +tag: + - Gang Of Four + - Decoupling +--- + +## Propósito + +Proporcionar una interfaz unificada a un conjunto de interfaces de un subsistema. La fachada define una interfaz que +facilita el uso del subsistema. + +## Explicación + +Un ejemplo real + +> ¿Cómo funciona una mina de oro? "¡Bueno, los mineros bajan y sacan oro!" dices. Eso es lo que crees porque estás +> usando una interfaz simple que la mina de oro proporciona en el exterior, internamente tiene que hacer un montón de +> cosas para que suceda. Esta interfaz simple al subsistema complejo es una fachada. + +En pocas palabras + +> El patrón Facade proporciona una interfaz simplificada a un subsistema complejo. + +Wikipedia dice + +> Una fachada es un objeto que proporciona una interfaz simplificada a un cuerpo de código mayor, como una biblioteca de +> clases. + +**Ejemplo programático** + +Tomemos el ejemplo de la mina de oro. Aquí tenemos la jerarquía de los trabajadores enanos de la mina. Primero hay una +clase base `DwarvenMineWorker`: + +```java + +@Slf4j +public abstract class DwarvenMineWorker { + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP -> goToSleep(); + case WAKE_UP -> wakeUp(); + case GO_HOME -> goHome(); + case GO_TO_MINE -> goToMine(); + case WORK -> work(); + default -> LOGGER.info("Undefined action"); + } + } + + public void action(Action... actions) { + Arrays.stream(actions).forEach(this::action); + } + + public abstract void work(); + + public abstract String name(); + + enum Action { + GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK + } +} +``` + +Luego tenemos las clases concretas enanas `DwarvenTunnelDigger`, `DwarvenGoldDigger` y `DwarvenCartOperator`: + +```java +@Slf4j +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} + +@Slf4j +public class DwarvenGoldDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} + +@Slf4j +public class DwarvenCartOperator extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} + +``` + +Para manejar a todos estos trabajadores de la mina de oro tenemos la `FachadaDwarvenGoldmine`: + +```java +public class DwarvenGoldmineFacade { + + private final List workers; + + public DwarvenGoldmineFacade() { + workers = List.of( + new DwarvenGoldDigger(), + new DwarvenCartOperator(), + new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions(Collection workers, + DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } +} +``` + +Ahora vamos a utilizar la fachada: + +```java +var facade = new DwarvenGoldmineFacade(); +facade.startNewDay(); +facade.digOutGold(); +facade.endDay(); +``` + +Salida del programa: + +```java +// Dwarf gold digger wakes up. +// Dwarf gold digger goes to the mine. +// Dwarf cart operator wakes up. +// Dwarf cart operator goes to the mine. +// Dwarven tunnel digger wakes up. +// Dwarven tunnel digger goes to the mine. +// Dwarf gold digger digs for gold. +// Dwarf cart operator moves gold chunks out of the mine. +// Dwarven tunnel digger creates another promising tunnel. +// Dwarf gold digger goes home. +// Dwarf gold digger goes to sleep. +// Dwarf cart operator goes home. +// Dwarf cart operator goes to sleep. +// Dwarven tunnel digger goes home. +// Dwarven tunnel digger goes to sleep. +``` + +## Diagrama de clases + +![alt text](./etc/facade.urm.png "Facade pattern class diagram") + +## Aplicabilidad + +Utilice el patrón Fachada cuando + +* Quieres proporcionar una interfaz sencilla a un subsistema complejo. Los subsistemas a menudo se vuelven más complejos + a medida que evolucionan. La mayoría de los patrones, cuando se aplican, dan como resultado más clases y más pequeñas. + Esto hace que el subsistema sea más reutilizable y más fácil de personalizar, pero también se vuelve más difícil de + usar para los clientes que no necesitan personalizarlo. Una fachada puede proporcionar una simple vista por defecto + del subsistema que es lo suficiente para la mayoría de los clientes. Sólo los clientes que necesiten más + personalización tendrán que mirar más allá de la fachada. +* Hay muchas dependencias entre los clientes y las clases de implementación de una abstracción. + Introducir una fachada para desacoplar el subsistema de los clientes y otros subsistemas, promoviendo así + independencia y portabilidad del subsistema. +* Quieres estratificar tus subsistemas. Utiliza una fachada para definir un punto de entrada a cada nivel de subsistema. + Si los subsistemas son dependientes, puedes simplificar las dependencias entre ellos haciéndolos que se comuniquen + entre sí únicamente a través de sus fachadas. + +## Tutoriales + +*[DigitalOcean](https://www.digitalocean.com/community/tutorials/facade-design-pattern-in-java) + +* [Refactoring Guru](https://refactoring.guru/design-patterns/facade) +* [GeekforGeeks](https://www.geeksforgeeks.org/facade-design-pattern-introduction/) +* [Tutorialspoint](https://www.tutorialspoint.com/design_pattern/facade_pattern.htm) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/es/facade/etc/facade.urm.png b/localization/es/facade/etc/facade.urm.png new file mode 100644 index 000000000000..8e3ec7aca45e Binary files /dev/null and b/localization/es/facade/etc/facade.urm.png differ diff --git a/localization/es/factory-kit/README.md b/localization/es/factory-kit/README.md index 26d8bcb142a3..cd5fca8f5b50 100644 --- a/localization/es/factory-kit/README.md +++ b/localization/es/factory-kit/README.md @@ -1,5 +1,6 @@ --- title: Factory Kit +shortTitle: Factory Kit category: Creational language: es tag: diff --git a/localization/es/factory-method/README.md b/localization/es/factory-method/README.md index 730734fbb055..b4e9144ac247 100644 --- a/localization/es/factory-method/README.md +++ b/localization/es/factory-method/README.md @@ -1,5 +1,6 @@ --- title: Factory Method +shortTitle: Factory Method category: Creational language: es tag: diff --git a/localization/es/factory/README.md b/localization/es/factory/README.md index 8ec8e38146c8..d27076e45148 100644 --- a/localization/es/factory/README.md +++ b/localization/es/factory/README.md @@ -1,5 +1,6 @@ --- title: Factory +shortTitle: Factory category: Creational language: es tag: diff --git a/localization/es/feature-toggle/README.md b/localization/es/feature-toggle/README.md new file mode 100644 index 000000000000..3e5a2134d55b --- /dev/null +++ b/localization/es/feature-toggle/README.md @@ -0,0 +1,76 @@ +--- +title: Feature Toggle +shortTitle: Feature Toggle +category: Behavioral +language: es +tag: + - Extensibility +--- + +## También conocido como +Feature Flag + +## Propósito +Técnica utilizada en el desarrollo de software para controlar y gestionar el despliegue de características o funcionalidades específicas en un programa sin cambiar el código. Puede actuar como un interruptor de encendido/apagado de funciones en función del estado o las propiedades de otros valores del programa. Esto es similar a las pruebas A/B, en las que las funciones se despliegan en función de propiedades como la ubicación o el dispositivo. La implementación de este patrón de diseño puede aumentar la complejidad del código, y es importante recordar eliminar el código redundante si este patrón de diseño se utiliza para eliminar gradualmente un sistema o característica. + +## Explicación +Ejemplo del mundo real +> Este patrón de diseño funciona realmente bien en cualquier tipo de desarrollo, en particular en el desarrollo móvil. Digamos que quieres introducir una característica como el modo oscuro, pero quieres asegurarte de que la característica funciona correctamente y no quieres desplegar la característica a todo el mundo inmediatamente. Escribes el código y lo desactivas por defecto. A partir de aquí, es fácil activar el código para usuarios específicos basándose en criterios de selección, o aleatoriamente. Esto también permitirá que la función se desactive fácilmente sin ningún cambio drástico en el código, o cualquier necesidad de redistribución o actualizaciones. + +En pocas palabras +> Feature Toggle es una forma de introducir nuevas funciones gradualmente en lugar de desplegarlas todas a la vez. + +Wikipedia dice +> Una función de conmutación en el desarrollo de software proporciona una alternativa al mantenimiento de múltiples ramas de características en el código fuente. Una condición dentro del código activa o desactiva una característica durante el tiempo de ejecución. En un entorno ágil, el conmutador se utiliza en producción, para activar la función bajo demanda, para algunos o todos los usuarios. + +**Ejemplo programático** +Este ejemplo muestra código Java que permite mostrar una funcionalidad cuando es activada por el desarrollador, y cuando un usuario es miembro Premium de la aplicación. Esto es útil para características bloqueadas por suscripción. + +```java +public class FeatureToggleExample { + // Bool for feature enabled or disabled + private static boolean isNewFeatureEnabled = false; + + public static void main(String[] args) { + boolean userIsPremium = true; // Example: Check if the user is a premium user + + // Check if the new feature should be enabled for the user + if (userIsPremium && isNewFeatureEnabled) { + // User is premium and the new feature is enabled + showNewFeature(); + } + } + + private static void showNewFeature() { + // If user is allowed to see locked feature, this is where the code would go + } +} +``` +El código muestra lo sencillo que es aplicar este patrón de diseño, y los criterios pueden refinarse o ampliarse aún más si los desarrolladores así lo deciden. + +## Diagrama de clases +![alt text](./etc/feature-toggle.png "Feature Toggle") + +## Aplicabilidad +Utilice el patrón de alternancia de funciones cuando + +* Dar diferentes características a diferentes usuarios. +* Desplegar una nueva característica de forma incremental. +* Cambiar entre entornos de desarrollo y producción. +* Desactivar rápidamente funciones problemáticas. +* Gestión externa del despliegue de características. +* Capacidad de mantener múltiples versiones de una característica. +* Despliegue "oculto", liberando una característica en código para pruebas designadas, pero sin ponerla a disposición del público. + +## Consecuencias +Consecuencias del uso del patrón de alternancia de funciones + +* Aumenta la complejidad del código +* Probar múltiples estados es más difícil y consume más tiempo +* Confusión entre amigos sobre por qué faltan características +* Mantener la documentación actualizada con todas las características puede ser difícil. + +## Créditos + +* [Martin Fowler 29 October 2010 (2010-10-29).](http://martinfowler.com/bliki/FeatureToggle.html) +* [Feature Toggle - Java Design Patterns](https://java-design-patterns.com/patterns/feature-toggle/) diff --git a/localization/es/feature-toggle/etc/feature-toggle.png b/localization/es/feature-toggle/etc/feature-toggle.png new file mode 100644 index 000000000000..5c118e57e44e Binary files /dev/null and b/localization/es/feature-toggle/etc/feature-toggle.png differ diff --git a/localization/es/flux/README.md b/localization/es/flux/README.md new file mode 100644 index 000000000000..c54e6b662cd4 --- /dev/null +++ b/localization/es/flux/README.md @@ -0,0 +1,26 @@ +--- +title: Flux +shortTitle: Flux +category: Structural +language: es +tag: + - Decoupling +--- + +## Propósito +Flux evita MVC en favor de un flujo de datos unidireccional. Cuando un usuario +usuario interactúa con una vista, ésta propaga una acción a través de un +central, a los distintos almacenes que contienen los datos de la aplicación y la +que actualiza todas las vistas afectadas. + +## Diagrama de clases +![alt text](./etc/flux.png "Flux") + +## Aplicabilidad +Utilice el patrón Flux cuando + +* Debes centrarte en crear rutas de actualización explícitas y comprensibles para los datos de tu aplicación, lo que simplifica el seguimiento de los cambios durante el desarrollo y facilita la localización y corrección de errores. + +## Créditos + +* [Flux - Application architecture for building user interfaces](http://facebook.github.io/flux/) diff --git a/localization/es/flux/etc/flux.png b/localization/es/flux/etc/flux.png new file mode 100644 index 000000000000..9cb596eaf03d Binary files /dev/null and b/localization/es/flux/etc/flux.png differ diff --git a/localization/es/flyweight/README.md b/localization/es/flyweight/README.md new file mode 100644 index 000000000000..8ef93943aa82 --- /dev/null +++ b/localization/es/flyweight/README.md @@ -0,0 +1,209 @@ +--- +title: Flyweight +shortTitle: Flyweight +category: Structural +language: es +tag: + - Gang of Four + - Performance +--- + +## Propósito + +Utilice la compartición para dar soporte a un gran número de objetos finos de forma eficiente. + +## Explicación + +Un ejemplo real + +> La tienda del alquimista tiene estanterías llenas de pociones mágicas. Muchas de las pociones son las mismas, por lo +> que no es necesario crear un nuevo objeto para cada una de ellas. En su lugar, una instancia de objeto puede +> representar +> múltiples elementos de la estantería para que la huella de memoria siga siendo pequeña. + +En pocas palabras + +> Se utiliza para minimizar el uso de memoria o los gastos computacionales compartiendo todo lo posible con objetos +> similares. + +Wikipedia dice + +> En programación informática, flyweight es un patrón de diseño de software. Un flyweight es un objeto que minimiza el +> uso de memoria compartiendo tantos datos como sea posible con otros objetos similares; es una forma de utilizar +> objetos +> en grandes cantidades cuando una simple representación repetida utilizaría una cantidad inaceptable de memoria. + +**Ejemplo programático** + +Traduciendo nuestro ejemplo de la tienda de alquimia de arriba. En primer lugar, tenemos diferentes tipos de +pociones `Potion`: `HealingPotion`, `HolyWaterPotion` e `InvisibilityPotion`: + +```java +public interface Potion { + void drink(); +} + +@Slf4j +public class HealingPotion implements Potion { + @Override + public void drink() { + LOGGER.info("You feel healed. (Potion={})", System.identityHashCode(this)); + } +} + +@Slf4j +public class HolyWaterPotion implements Potion { + @Override + public void drink() { + LOGGER.info("You feel blessed. (Potion={})", System.identityHashCode(this)); + } +} + +@Slf4j +public class InvisibilityPotion implements Potion { + @Override + public void drink() { + LOGGER.info("You become invisible. (Potion={})", System.identityHashCode(this)); + } +} +``` + +Luego la clase Flyweight `PotionFactory`, que es la fábrica para crear pociones. + +```java +public class PotionFactory { + + private final Map potions; + + public PotionFactory() { + potions = new EnumMap<>(PotionType.class); + } + + Potion createPotion(PotionType type) { + var potion = potions.get(type); + if (potion == null) { + switch (type) { + case HEALING -> { + potion = new HealingPotion(); + potions.put(type, potion); + } + case HOLY_WATER -> { + potion = new HolyWaterPotion(); + potions.put(type, potion); + } + case INVISIBILITY -> { + potion = new InvisibilityPotion(); + potions.put(type, potion); + } + default -> { + } + } + } + return potion; + } +} +``` + +`AlchemistShop` contiene dos estantes de pociones mágicas. Las pociones se crean utilizando la antes +mencionada `PotionFactory`. + +```java +@Slf4j +public class AlchemistShop { + + private final List topShelf; + private final List bottomShelf; + + public AlchemistShop() { + var factory = new PotionFactory(); + topShelf = List.of( + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.STRENGTH), + factory.createPotion(PotionType.HEALING), + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.STRENGTH), + factory.createPotion(PotionType.HEALING), + factory.createPotion(PotionType.HEALING) + ); + bottomShelf = List.of( + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.HOLY_WATER), + factory.createPotion(PotionType.HOLY_WATER) + ); + } + + public final List getTopShelf() { + return List.copyOf(this.topShelf); + } + + public final List getBottomShelf() { + return List.copyOf(this.bottomShelf); + } + + public void drinkPotions() { + LOGGER.info("Drinking top shelf potions\n"); + topShelf.forEach(Potion::drink); + LOGGER.info("Drinking bottom shelf potions\n"); + bottomShelf.forEach(Potion::drink); + } +} +``` + +En nuestro escenario, un valiente visitante entra en la tienda del alquimista y se bebe todas las pociones. + +```java +// create the alchemist shop with the potions +var alchemistShop = new AlchemistShop(); +// a brave visitor enters the alchemist shop and drinks all the potions +alchemistShop.drinkPotions(); +``` + +Salida del programa: + +```java +Drinking top shelf potions +You become invisible. (Potion=1509514333) +You become invisible. (Potion=1509514333) +You feel strong. (Potion=739498517) +You feel healed. (Potion=125130493) +You become invisible. (Potion=1509514333) +You feel strong. (Potion=739498517) +You feel healed. (Potion=125130493) +You feel healed. (Potion=125130493) +Drinking bottom shelf potions +Urgh! This is poisonous. (Potion=166239592) +Urgh! This is poisonous. (Potion=166239592) +Urgh! This is poisonous. (Potion=166239592) +You feel blessed. (Potion=991505714) +You feel blessed. (Potion=991505714) +``` + +## Diagrama de clases + +![alt text](./etc/flyweight.urm.png "Flyweight pattern class diagram") + +## Aplicabilidad + +La eficacia del patrón Flyweight depende en gran medida de cómo y dónde se utilice. Aplique el patrón +Flyweight cuando se cumplan todas las condiciones siguientes: + +* Una aplicación utiliza un gran número de objetos. +* Los costes de almacenamiento son altos debido a la gran cantidad de objetos. +* La mayor parte del estado de los objetos puede hacerse extrínseco. +* Muchos grupos de objetos pueden ser reemplazados por relativamente pocos objetos compartidos una vez que el estado extrínseco + extrínseco. +* La aplicación no depende de la identidad de los objetos. Dado que los objetos flyweight pueden ser compartidos, las pruebas de identidad + devolverán verdadero para objetos conceptualmente distintos. + +## Usos conocidos + +* [java.lang.Integer#valueOf(int)](http://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#valueOf%28int%29) y + de forma similar para Byte, Carácter y otros tipos envueltos (Wrappers). + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/es/flyweight/etc/flyweight.urm.png b/localization/es/flyweight/etc/flyweight.urm.png new file mode 100644 index 000000000000..299cdb7bdf86 Binary files /dev/null and b/localization/es/flyweight/etc/flyweight.urm.png differ diff --git a/localization/es/front-controller/README.md b/localization/es/front-controller/README.md new file mode 100644 index 000000000000..58997f265cea --- /dev/null +++ b/localization/es/front-controller/README.md @@ -0,0 +1,34 @@ +--- +title: Front Controller +shortTitle: Front Controller +category: Structural +language: es +tag: + - Decoupling +--- + +## Propósito +Introducir un gestor común para todas las solicitudes de un sitio web. De esta +manera podemos encapsular funcionalidad común como la seguridad, +internacionalización, enrutamiento y registro en un solo lugar. + +## Diagrama de clases +![alt text](./etc/front-controller.png "Front Controller") + +## Aplicabilidad +Utilice el patrón del controlador frontal cuando + +* Desea encapsular la funcionalidad común de gestión de peticiones en un único lugar. +* Desea implementar la gestión dinámica de peticiones, es decir, cambiar el enrutamiento sin modificar el código. +* hacer portable la configuración del servidor web, sólo necesitas registrar el manejador de forma específica para el servidor web + +## Ejemplos del mundo real + +* [Apache Struts](https://struts.apache.org/) + +## Créditos + +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Presentation Tier Patterns](http://www.javagyan.com/tutorials/corej2eepatterns/presentation-tier-patterns) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/localization/es/front-controller/etc/front-controller.png b/localization/es/front-controller/etc/front-controller.png new file mode 100644 index 000000000000..77c14ef019cc Binary files /dev/null and b/localization/es/front-controller/etc/front-controller.png differ diff --git a/localization/es/game-loop/README.md b/localization/es/game-loop/README.md new file mode 100644 index 000000000000..3333c4bbad19 --- /dev/null +++ b/localization/es/game-loop/README.md @@ -0,0 +1,246 @@ +--- +title: Game Loop +shortTitle: Game Loop +category: Behavioral +language: es +tag: + - Game programming +--- + +## Propósito + +Un bucle de juego se ejecuta continuamente durante la partida. En cada vuelta del bucle, procesa las entradas del usuario sin bloquearse, actualiza el estado del juego y lo renderiza. Realiza un seguimiento del paso del tiempo para controlar el ritmo de juego. + +Este patrón desvincula la progresión del tiempo de juego de la entrada del usuario y de la velocidad del procesador. + +## Aplicabilidad + +Este patrón se utiliza en todos los motores de juego. + +## Explicación + +Ejemplo del mundo real + +> El bucle de juego es el proceso principal de todos los hilos de renderizado del juego. Está presente en todos los juegos modernos. Controla el proceso de entrada, la actualización del estado interno, el renderizado, la IA y todos los demás procesos. + +En pocas palabras + +> El patrón de bucle de juego garantiza que el tiempo de juego progrese a la misma velocidad en todas las configuraciones de hardware diferentes. + +Wikipedia dice + +> El componente central de cualquier juego, desde el punto de vista de la programación, es el bucle de juego. El bucle de juego permite que el juego se ejecute sin problemas, independientemente de la entrada de un usuario, o la falta de ella. + +**Ejemplo programático** + +Empecemos con algo sencillo. Aquí está la clase `Bullet`. Las balas se moverán en nuestro juego. Para propósitos de demostración es suficiente que tenga una posición unidimensional. + +```java +public class Bullet { + + private float position; + + public Bullet() { + position = 0.0f; + } + + public float getPosition() { + return position; + } + + public void setPosition(float position) { + this.position = position; + } +} +``` + +El `GameController` es el responsable de mover los objetos del juego, incluida la mencionada bala. + +```java +public class GameController { + + protected final Bullet bullet; + + public GameController() { + bullet = new Bullet(); + } + + public void moveBullet(float offset) { + var currentPosition = bullet.getPosition(); + bullet.setPosition(currentPosition + offset); + } + + public float getBulletPosition() { + return bullet.getPosition(); + } +} +``` + +Ahora introducimos el bucle de juego. O en realidad en esta demo tenemos 3 bucles de juego diferentes. Veamos primero la clase base `GameLoop`. + +```java +public enum GameStatus { + + RUNNING, STOPPED +} + +public abstract class GameLoop { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected volatile GameStatus status; + + protected GameController controller; + + private Thread gameThread; + + public GameLoop() { + controller = new GameController(); + status = GameStatus.STOPPED; + } + + public void run() { + status = GameStatus.RUNNING; + gameThread = new Thread(this::processGameLoop); + gameThread.start(); + } + + public void stop() { + status = GameStatus.STOPPED; + } + + public boolean isGameRunning() { + return status == GameStatus.RUNNING; + } + + protected void processInput() { + try { + var lag = new Random().nextInt(200) + 50; + Thread.sleep(lag); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + + protected void render() { + var position = controller.getBulletPosition(); + logger.info("Current bullet position: " + position); + } + + protected abstract void processGameLoop(); +} +``` + +Aquí está la primera implementación del bucle de juego, `FrameBasedGameLoop`: + +```java +public class FrameBasedGameLoop extends GameLoop { + + @Override + protected void processGameLoop() { + while (isGameRunning()) { + processInput(); + update(); + render(); + } + } + + protected void update() { + controller.moveBullet(0.5f); + } +} +``` + +Por último, mostramos todos los bucles del juego en acción. + +```java + try { + LOGGER.info("Start frame-based game loop:"); + var frameBasedGameLoop = new FrameBasedGameLoop(); + frameBasedGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + frameBasedGameLoop.stop(); + LOGGER.info("Stop frame-based game loop."); + + LOGGER.info("Start variable-step game loop:"); + var variableStepGameLoop = new VariableStepGameLoop(); + variableStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + variableStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + + LOGGER.info("Start fixed-step game loop:"); + var fixedStepGameLoop = new FixedStepGameLoop(); + fixedStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + fixedStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } +``` + +Salida del programa: + +```java +Start frame-based game loop: +Current bullet position: 0.5 +Current bullet position: 1.0 +Current bullet position: 1.5 +Current bullet position: 2.0 +Current bullet position: 2.5 +Current bullet position: 3.0 +Current bullet position: 3.5 +Current bullet position: 4.0 +Current bullet position: 4.5 +Current bullet position: 5.0 +Current bullet position: 5.5 +Current bullet position: 6.0 +Stop frame-based game loop. +Start variable-step game loop: +Current bullet position: 6.5 +Current bullet position: 0.038 +Current bullet position: 0.084 +Current bullet position: 0.145 +Current bullet position: 0.1805 +Current bullet position: 0.28 +Current bullet position: 0.32 +Current bullet position: 0.42549998 +Current bullet position: 0.52849996 +Current bullet position: 0.57799995 +Current bullet position: 0.63199997 +Current bullet position: 0.672 +Current bullet position: 0.778 +Current bullet position: 0.848 +Current bullet position: 0.8955 +Current bullet position: 0.9635 +Stop variable-step game loop. +Start fixed-step game loop: +Current bullet position: 0.0 +Current bullet position: 1.086 +Current bullet position: 0.059999995 +Current bullet position: 0.12999998 +Current bullet position: 0.24000004 +Current bullet position: 0.33999994 +Current bullet position: 0.36999992 +Current bullet position: 0.43999985 +Current bullet position: 0.5399998 +Current bullet position: 0.65999967 +Current bullet position: 0.68999964 +Current bullet position: 0.7299996 +Current bullet position: 0.79999954 +Current bullet position: 0.89999944 +Current bullet position: 0.98999935 +Stop variable-step game loop. +``` + +## Diagrama de clases + +![alt text](./etc/game-loop.urm.png "Game Loop pattern class diagram") + +## Créditos + +* [Game Programming Patterns - Game Loop](http://gameprogrammingpatterns.com/game-loop.html) +* [Game Programming Patterns](https://www.amazon.com/gp/product/0990582906/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0990582906&linkId=1289749a703b3fe0e24cd8d604d7c40b) +* [Game Engine Architecture, Third Edition](https://www.amazon.com/gp/product/1138035459/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1138035459&linkId=94502746617211bc40e0ef49d29333ac) diff --git a/localization/es/game-loop/etc/game-loop.urm.png b/localization/es/game-loop/etc/game-loop.urm.png new file mode 100644 index 000000000000..b7ffc11d2659 Binary files /dev/null and b/localization/es/game-loop/etc/game-loop.urm.png differ diff --git a/localization/es/gateway/README.md b/localization/es/gateway/README.md new file mode 100644 index 000000000000..2bef6e4f785c --- /dev/null +++ b/localization/es/gateway/README.md @@ -0,0 +1,147 @@ +--- +title: Gateway +shortTitle: Gateway +category: Structural +language: es +tag: +- Decoupling + +--- + +## Propósito + +Proporcionar una interfaz de acceso a un conjunto de sistemas o funcionalidades externas. Gateway proporciona una vista simple y uniforme de +recursos externos a los internos de una aplicación. + +## Explicación + +Un ejemplo real + +> Gateway actúa como una verdadera puerta de entrada de una ciudad determinada. Las personas dentro de la ciudad se llaman sistema interno, y las diferentes ciudades externas se llaman servicios externos. La puerta de enlace está aquí para proporcionar acceso al sistema interno a diferentes servicios externos. + +En pocas palabras + +> La pasarela puede proporcionar una interfaz que permita al sistema interno utilizar un servicio externo. + +Wikipedia dice + +> Un servidor que actúa como front-end de la API, recibe solicitudes de la API, aplica políticas de estrangulamiento y seguridad, pasa las solicitudes al servicio back-end y, a continuación, devuelve la respuesta al solicitante. + +**Ejemplo programático** + +La clase principal de nuestro ejemplo es el `ExternalService` que contiene elementos. + +```java +class ExternalServiceA implements Gateway { + @Override + public void execute() throws Exception { + LOGGER.info("Executing Service A"); + // Simulate a time-consuming task + Thread.sleep(1000); + } +} + +/** + * ExternalServiceB is one of external services. + */ +class ExternalServiceB implements Gateway { + @Override + public void execute() throws Exception { + LOGGER.info("Executing Service B"); + // Simulate a time-consuming task + Thread.sleep(1000); + } +} + +/** + * ExternalServiceC is one of external services. + */ +class ExternalServiceC implements Gateway { + @Override + public void execute() throws Exception { + LOGGER.info("Executing Service C"); + // Simulate a time-consuming task + Thread.sleep(1000); + } + + public void error() throws Exception { + // Simulate an exception + throw new RuntimeException("Service C encountered an error"); + } +} +``` + +Para operar estos servicios externos, aquí está la clase `App`: + +```java +public class App { + /** + * Simulate an application calling external services. + */ + public static void main(String[] args) throws Exception { + GatewayFactory gatewayFactory = new GatewayFactory(); + + // Register different gateways + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + + // Use an executor service for asynchronous execution + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + + // Execute external services + try { + serviceA.execute(); + serviceB.execute(); + serviceC.execute(); + } catch (ThreadDeath e) { + LOGGER.info("Interrupted!" + e); + throw e; + } + } +} +``` + +La interfaz `Gateway` es extremadamente sencilla. + +```java +interface Gateway { + void execute() throws Exception; +} +``` + +Salida del programa: + +```java + Executing Service A + Executing Service B + Executing Service C +``` + +## Diagrama de clases + +![alt text](./etc/gateway.urm.png "gateway") + +## Aplicabilidad + +Utilizar el patrón Gateway + +* Para acceder al contenido de un objeto agregado sin exponer su representación interna. +* Para la integración con múltiples servicios externos o APIs. +* Para proporcionar una interfaz uniforme para recorrer diferentes estructuras de agregados. + +## Tutoriales + +* [Pattern: API Gateway / Backends for Frontends](https://microservices.io/patterns/apigateway.html) + +## Usos conocidos + +* [API Gateway](https://java-design-patterns.com/patterns/microservices-api-gateway/) +* [10 most common use cases of an API Gateway](https://apisix.apache.org/blog/2022/10/27/ten-use-cases-api-gateway/) + +## Créditos + +* [Gateway](https://martinfowler.com/articles/gateway-pattern.html) +* [What is the difference between Facade and Gateway design patterns?](https://stackoverflow.com/questions/4422211/what-is-the-difference-between-facade-and-gateway-design-patterns) diff --git a/localization/es/gateway/etc/gateway.urm.png b/localization/es/gateway/etc/gateway.urm.png new file mode 100644 index 000000000000..2d8ad6c9a8f8 Binary files /dev/null and b/localization/es/gateway/etc/gateway.urm.png differ diff --git a/localization/es/health-check/README.md b/localization/es/health-check/README.md new file mode 100644 index 000000000000..a13f67542e3e --- /dev/null +++ b/localization/es/health-check/README.md @@ -0,0 +1,452 @@ +--- +title: Health Check Pattern +shortTitle: Health Check Pattern +category: Behavioral +language: es +tag: + - Performance + - Microservices + - Resilience + - Observability +--- + +# Health Check Pattern + +## También conocido como +Health Monitoring, Service Health Check + +## Propósito +Garantizar la estabilidad y resistencia de los servicios en una arquitectura de microservicios proporcionando una forma de supervisar y diagnosticar su estado. + +## Explicación +En la arquitectura de microservicios, es crítico comprobar continuamente la salud de los servicios individuales. El Health Check Pattern es un mecanismo para que los microservicios expongan su estado de salud. Este patrón se implementa incluyendo un punto final de comprobación de salud en los microservicios que devuelve el estado actual del servicio. Esto es vital para mantener la resistencia del sistema y la disponibilidad operativa. + +Para obtener más información, consulte el patrón API Health Check en [Microservices.io](https://microservices.io/patterns/observability/health-check-api.html). + + +## Ejemplo del mundo real +En un entorno nativo en la nube, como Kubernetes o AWS ECS, las comprobaciones de estado se utilizan para garantizar que los contenedores se ejecutan correctamente. Si un servicio falla su chequeo de salud, puede ser reiniciado o reemplazado automáticamente, asegurando alta disponibilidad y resiliencia. + +## En pocas palabras +El patrón de comprobación de la salud es como una visita periódica al médico para los servicios en una arquitectura de microservicios. Ayuda en la detección temprana de problemas y asegura que los servicios estén sanos y disponibles. + + +## Ejemplo Programático +Aquí, se proporcionan ejemplos detallados de implementaciones de chequeo de salud en un entorno de microservicios. + +### AsynchronousHealthChecker +Un componente de comprobación de salud asíncrono que ejecuta comprobaciones de salud en un hilo separado. + +```java + /** + * Performs a health check asynchronously using the provided health check logic with a specified + * timeout. + * + * @param healthCheck the health check logic supplied as a {@code Supplier} + * @param timeoutInSeconds the maximum time to wait for the health check to complete, in seconds + * @return a {@code CompletableFuture} object that represents the result of the health + * check + */ + public CompletableFuture performCheck( + Supplier healthCheck, long timeoutInSeconds) { + CompletableFuture future = + CompletableFuture.supplyAsync(healthCheck, healthCheckExecutor); + + // Schedule a task to enforce the timeout + healthCheckExecutor.schedule( + () -> { + if (!future.isDone()) { + LOGGER.error(HEALTH_CHECK_TIMEOUT_MESSAGE); + future.completeExceptionally(new TimeoutException(HEALTH_CHECK_TIMEOUT_MESSAGE)); + } + }, + timeoutInSeconds, + TimeUnit.SECONDS); + + return future.handle( + (result, throwable) -> { + if (throwable != null) { + LOGGER.error(HEALTH_CHECK_FAILED_MESSAGE, throwable); + // Check if the throwable is a TimeoutException or caused by a TimeoutException + Throwable rootCause = + throwable instanceof CompletionException ? throwable.getCause() : throwable; + if (!(rootCause instanceof TimeoutException)) { + LOGGER.error(HEALTH_CHECK_FAILED_MESSAGE, rootCause); + return Health.down().withException(rootCause).build(); + } else { + LOGGER.error(HEALTH_CHECK_TIMEOUT_MESSAGE, rootCause); + // If it is a TimeoutException, rethrow it wrapped in a CompletionException + throw new CompletionException(rootCause); + } + } else { + return result; + } + }); + } +``` + +### CpuHealthIndicator +Un indicador de salud que comprueba la salud de la CPU del sistema. + +```java + /** + * Checks the health of the system's CPU and returns a health indicator object. + * + * @return a health indicator object + */ + @Override + public Health health() { + if (!(osBean instanceof com.sun.management.OperatingSystemMXBean sunOsBean)) { + LOGGER.error("Unsupported operating system MXBean: {}", osBean.getClass().getName()); + return Health.unknown() + .withDetail(ERROR_MESSAGE, "Unsupported operating system MXBean") + .build(); + } + + double systemCpuLoad = sunOsBean.getCpuLoad() * 100; + double processCpuLoad = sunOsBean.getProcessCpuLoad() * 100; + int availableProcessors = sunOsBean.getAvailableProcessors(); + double loadAverage = sunOsBean.getSystemLoadAverage(); + + Map details = new HashMap<>(); + details.put("timestamp", Instant.now()); + details.put("systemCpuLoad", String.format("%.2f%%", systemCpuLoad)); + details.put("processCpuLoad", String.format("%.2f%%", processCpuLoad)); + details.put("availableProcessors", availableProcessors); + details.put("loadAverage", loadAverage); + + if (systemCpuLoad > systemCpuLoadThreshold) { + LOGGER.error(HIGH_SYSTEM_CPU_LOAD_MESSAGE, systemCpuLoad); + return Health.down() + .withDetails(details) + .withDetail(ERROR_MESSAGE, HIGH_SYSTEM_CPU_LOAD_MESSAGE_WITHOUT_PARAM) + .build(); + } else if (processCpuLoad > processCpuLoadThreshold) { + LOGGER.error(HIGH_PROCESS_CPU_LOAD_MESSAGE, processCpuLoad); + return Health.down() + .withDetails(details) + .withDetail(ERROR_MESSAGE, HIGH_PROCESS_CPU_LOAD_MESSAGE_WITHOUT_PARAM) + .build(); + } else if (loadAverage > (availableProcessors * loadAverageThreshold)) { + LOGGER.error(HIGH_LOAD_AVERAGE_MESSAGE, loadAverage); + return Health.up() + .withDetails(details) + .withDetail(ERROR_MESSAGE, HIGH_LOAD_AVERAGE_MESSAGE_WITHOUT_PARAM) + .build(); + } else { + return Health.up().withDetails(details).build(); + } + } + +``` + + + +### CustomHealthIndicator +Un indicador de estado personalizado que comprueba periódicamente el estado de una base de datos y almacena en caché el resultado. Aprovecha un comprobador de estado asíncrono para realizar las comprobaciones de estado. + +- `AsynchronousHealthChecker`: Un componente para realizar comprobaciones de estado de forma asíncrona. +- `CacheManager`: Gestiona el almacenamiento en caché de los resultados de los controles de salud. +- `HealthCheckRepository`: Un repositorio para consultar datos relacionados con la salud desde la base de datos. + +```java +/** + * Perform a health check and cache the result. + * + * @return the health status of the application + * @throws HealthCheckInterruptedException if the health check is interrupted + */ +@Override +@Cacheable(value = "health-check", unless = "#result.status == 'DOWN'") +public Health health() { + LOGGER.info("Performing health check"); + CompletableFuture healthFuture = + healthChecker.performCheck(this::check, timeoutInSeconds); + try { + return healthFuture.get(timeoutInSeconds, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.error("Health check interrupted", e); + throw new HealthCheckInterruptedException(e); + } catch (Exception e) { + LOGGER.error("Health check failed", e); + return Health.down(e).build(); + } +} + +/** + * Checks the health of the database by querying for a simple constant value expected from the + * database. + * + * @return Health indicating UP if the database returns the constant correctly, otherwise DOWN. + */ +private Health check() { + Integer result = healthCheckRepository.checkHealth(); + boolean databaseIsUp = result != null && result == 1; + LOGGER.info("Health check result: {}", databaseIsUp); + return databaseIsUp + ? Health.up().withDetail("database", "reachable").build() + : Health.down().withDetail("database", "unreachable").build(); +} + +/** + * Evicts all entries from the health check cache. This is scheduled to run at a fixed rate + * defined in the application properties. + */ +@Scheduled(fixedRateString = "${health.check.cache.evict.interval:60000}") +public void evictHealthCache() { + LOGGER.info("Evicting health check cache"); + try { + Cache healthCheckCache = cacheManager.getCache("health-check"); + LOGGER.info("Health check cache: {}", healthCheckCache); + if (healthCheckCache != null) { + healthCheckCache.clear(); + } + } catch (Exception e) { + LOGGER.error("Failed to evict health check cache", e); + } +} + +``` + +### DatabaseTransactionHealthIndicator +Un indicador de salud que comprueba la salud de las transacciones de la base de datos intentando realizar una transacción de prueba utilizando un mecanismo de reintento. + +- Repositorio de comprobaciones de estado**: Un repositorio para realizar comprobaciones de salud en la base de datos. +- AsynchronousHealthChecker**: Un comprobador de salud asíncrono utilizado para ejecutar comprobaciones de salud en un hilo independiente. +- Plantilla de reintento**: Una plantilla de reintento utilizada para reintentar la transacción de prueba si falla debido a un error transitorio. + +```java +/** + * Performs a health check by attempting to perform a test transaction with retry support. + * + * @return the health status of the database transactions + */ +@Override +public Health health() { + LOGGER.info("Calling performCheck with timeout {}", timeoutInSeconds); + Supplier dbTransactionCheck = + () -> { + try { + healthCheckRepository.performTestTransaction(); + return Health.up().build(); + } catch (Exception e) { + LOGGER.error("Database transaction health check failed", e); + return Health.down(e).build(); + } + }; + try { + return asynchronousHealthChecker.performCheck(dbTransactionCheck, timeoutInSeconds).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Database transaction health check timed out or was interrupted", e); + Thread.currentThread().interrupt(); + return Health.down(e).build(); + } +} +``` + + +### GarbageCollectionHealthIndicator +Un indicador de salud personalizado que comprueba el estado de recogida de basura de la aplicación e informa del estado de salud en consecuencia. + +```java + /** + * Performs a health check by gathering garbage collection metrics and evaluating the overall + * health of the garbage collection system. + * + * @return a {@link Health} object representing the health status of the garbage collection system + */ + @Override + public Health health() { + List gcBeans = getGarbageCollectorMxBeans(); + List memoryPoolMxBeans = getMemoryPoolMxBeans(); + Map> gcDetails = new HashMap<>(); + + for (GarbageCollectorMXBean gcBean : gcBeans) { + Map collectorDetails = createCollectorDetails(gcBean, memoryPoolMxBeans); + gcDetails.put(gcBean.getName(), collectorDetails); + } + return Health.up().withDetails(gcDetails).build(); + } + +``` + +### MemoryHealthIndicator +Un indicador de salud personalizado que comprueba el uso de memoria de la aplicación e informa del estado de salud en consecuencia. + +```java + /** + * Performs a health check by checking the memory usage of the application. + * + * @return the health status of the application + */ + public Health checkMemory() { + Supplier memoryCheck = + () -> { + MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage heapMemoryUsage = memoryMxBean.getHeapMemoryUsage(); + long maxMemory = heapMemoryUsage.getMax(); + long usedMemory = heapMemoryUsage.getUsed(); + + double memoryUsage = (double) usedMemory / maxMemory; + String format = String.format("%.2f%% of %d max", memoryUsage * 100, maxMemory); + + if (memoryUsage < memoryThreshold) { + LOGGER.info("Memory usage is below threshold: {}", format); + return Health.up().withDetail("memory usage", format).build(); + } else { + return Health.down().withDetail("memory usage", format).build(); + } + }; + + try { + CompletableFuture future = + asynchronousHealthChecker.performCheck(memoryCheck, timeoutInSeconds); + return future.get(); + } catch (InterruptedException e) { + LOGGER.error("Health check interrupted", e); + Thread.currentThread().interrupt(); + return Health.down().withDetail("error", "Health check interrupted").build(); + } catch (ExecutionException e) { + LOGGER.error("Health check failed", e); + Throwable cause = e.getCause() == null ? e : e.getCause(); + return Health.down().withDetail("error", cause.toString()).build(); + } + } + + /** + * Retrieves the health status of the application by checking the memory usage. + * + * @return the health status of the application + */ + @Override + public Health health() { + return checkMemory(); + } +} +``` + + + +## Usando Spring Boot Actuator para Comprobaciones de Salud +Spring Boot Actuator proporciona una funcionalidad de comprobación de salud incorporada que puede integrarse fácilmente en su aplicación. Añadiendo la dependencia Spring Boot Actuator, puede exponer la información de comprobación de estado a través de un punto final predefinido, normalmente `/actuator/health`. + +## Salida +Esto muestra la salida del patrón de comprobación de salud utilizando una petición GET al punto final de salud de Actuator. + +### HTTP GET Request +``` +curl -X GET "http://localhost:6161/actuator/health" +``` + +### Output +```json +{ + "status": "UP", + "components": { + "cpu": { + "status": "UP", + "details": { + "processCpuLoad": "0.03%", + "availableProcessors": 10, + "systemCpuLoad": "21.40%", + "loadAverage": 3.3916015625, + "timestamp": "2023-12-03T08:44:19.488422Z" + } + }, + "custom": { + "status": "UP", + "details": { + "database": "reachable" + } + }, + "databaseTransaction": { + "status": "UP" + }, + "db": { + "status": "UP", + "details": { + "database": "H2", + "validationQuery": "isValid()" + } + }, + "diskSpace": { + "status": "UP", + "details": { + "total": 994662584320, + "free": 377635827712, + "threshold": 10485760, + "exists": true + } + }, + "garbageCollection": { + "status": "UP", + "details": { + "G1 Young Generation": { + "count": "11", + "time": "30ms", + "memoryPools": "G1 Old Gen: 0.005056262016296387%" + }, + "G1 Old Generation": { + "count": "0", + "time": "0ms", + "memoryPools": "G1 Old Gen: 0.005056262016296387%" + } + } + }, + "livenessState": { + "status": "UP" + }, + "memory": { + "status": "UP", + "details": { + "memory usage": "1.36% of 4294967296 max" + } + }, + "ping": { + "status": "UP" + }, + "readinessState": { + "status": "UP" + } + }, + "groups": [ + "liveness", + "readiness" + ] +} +``` + +## Diagrama de clases +![Health Check Pattern](./etc/health-check.png) + +## Aplicabilidad +Utilice el Patrón de Comprobación de Salud cuando: +- Tienes una aplicación compuesta por múltiples servicios y necesitas monitorizar la salud de cada servicio individualmente. +- Desea implementar la recuperación o sustitución automática de servicios basándose en el estado de salud. +- Está empleando herramientas de orquestación o automatización que se basan en comprobaciones de estado para gestionar instancias de servicio. + +## Tutoriales +- Implementación de Health Checks en Java usando Spring Boot Actuator. + +## Usos conocidos +- Kubernetes Liveness y Readiness Probes +- Comprobaciones de estado de AWS Elastic Load Balancing +- Actuador Spring Boot + +## Consecuencias +**Pros:** +- Mejora la tolerancia a fallos del sistema detectando fallos y permitiendo una rápida recuperación. +- Mejora la visibilidad del estado del sistema para la supervisión operativa y las alertas. + +**Contras:** +- Añade complejidad a la implementación del servicio. +- Requiere una estrategia para gestionar los fallos en cascada cuando los servicios dependientes no están en buen estado. + +## Patrones relacionados +- Circuit Breaker +- Retry Pattern +- Timeout Pattern + +## Créditos +Inspirado en el patrón Health Check API de [microservices.io](https://microservices.io/patterns/observability/health-check-api.html), y el tema [#2695](https://github.com/iluwatar/java-design-patterns/issues/2695) en el repositorio de patrones de diseño Java de iluwatar. \ No newline at end of file diff --git a/localization/es/health-check/etc/health-check.png b/localization/es/health-check/etc/health-check.png new file mode 100644 index 000000000000..89966c464cb5 Binary files /dev/null and b/localization/es/health-check/etc/health-check.png differ diff --git a/localization/es/identity-map/README.md b/localization/es/identity-map/README.md new file mode 100644 index 000000000000..8ff37811ea34 --- /dev/null +++ b/localization/es/identity-map/README.md @@ -0,0 +1,192 @@ +--- +title: Identity Map +shortTitle: Identity Map +category: Behavioral +language: es +tag: +- Performance +- Data access +--- + +## Propósito + +Garantiza que cada objeto se cargue una sola vez guardando cada objeto cargado en un mapa. +Busca objetos utilizando el mapa cuando se refiere a ellos. + +## Explicación + +Ejemplo del mundo real + +> Estamos escribiendo un programa que el usuario puede utilizar para encontrar los registros de una persona determinada en una base de datos. + +En palabras simples + +> Construir un mapa de identidades que almacene los registros de los elementos buscados recientemente en la base de datos. Cuando busquemos el mismo registro la próxima vez lo cargaremos desde el mapa no iremos a la base de datos. + +Wikipedia dice + +> En el diseño de DBMS, el patrón de mapa de identidad es un patrón de diseño de acceso a base de datos utilizado para mejorar el rendimiento, proporcionando un contexto específico, en la memoria caché para evitar la recuperación duplicada de los mismos datos de objetos de la base de datos + +**Ejemplo programático** + +* Para el propósito de esta demostración supongamos que ya hemos creado una instancia de base de datos **db**. +* Veamos primero la implementación de una entidad persona y sus campos: + +```java +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Getter +@Setter +@AllArgsConstructor +public final class Person implements Serializable { + + private static final long serialVersionUID = 1L; + + @EqualsAndHashCode.Include + private int personNationalId; + private String name; + private long phoneNum; + + @Override + public String toString() { + + return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum; + + } + +} + +``` + +* La siguiente es la implementación del personFinder que es la entidad que el usuario utilizará para buscar un registro en nuestra base de datos. Tiene adjunta la BD correspondiente. También mantiene un IdentityMap para almacenar los registros leídos recientemente. + +```java +@Slf4j +@Getter +@Setter +public class PersonFinder { + private static final long serialVersionUID = 1L; + // Access to the Identity Map + private IdentityMap identityMap = new IdentityMap(); + private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + /** + * get person corresponding to input ID. + * + * @param key : personNationalId to look for. + */ + public Person getPerson(int key) { + // Try to find person in the identity map + Person person = this.identityMap.getPerson(key); + if (person != null) { + LOGGER.info("Person found in the Map"); + return person; + } else { + // Try to find person in the database + person = this.db.find(key); + if (person != null) { + this.identityMap.addPerson(person); + LOGGER.info("Person found in DB."); + return person; + } + LOGGER.info("Person with this ID does not exist."); + return null; + } + } +} + +``` + +* El campo de mapa de identidad en la clase anterior es simplemente una abstracción de un hashMap con **personNationalId** como claves y el objeto persona correspondiente como valor. Aquí está su implementación: + +```java +@Slf4j +@Getter +public class IdentityMap { + private Map personMap = new HashMap<>(); + /** + * Add person to the map. + */ + public void addPerson(Person person) { + if (!personMap.containsKey(person.getPersonNationalId())) { + personMap.put(person.getPersonNationalId(), person); + } else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes. + LOGGER.info("Key already in Map"); + } + } + + /** + * Get Person with given id. + * + * @param id : personNationalId as requested by user. + */ + public Person getPerson(int id) { + Person person = personMap.get(id); + if (person == null) { + LOGGER.info("ID not in Map."); + } + return person; + } + + /** + * Get the size of the map. + */ + public int size() { + if (personMap == null) { + return 0; + } + return personMap.size(); + } + +} + +``` + +* Ahora debemos construir una persona ficticia para fines de demostración y poner a esa persona en nuestra base de datos. + +```java + Person person1 = new Person(1, "John", 27304159); + db.insert(person1); +``` + +* Ahora vamos a crear un objeto person finder y buscar a la persona con personNationalId = 1(supongamos que el objeto personFinder ya tiene la db y un IdentityMap adjunto): + +```java + PersonFinder finder = new PersonFinder(); + finder.getPerson(1); +``` + +* En esta etapa este registro se cargará desde la base de datos y la salida sería: + +```java + ID not in Map. + Person ID is:1;Person Name is:John;Phone Number is:27304159 + Person found in DB. +``` + +* Sin embargo, la próxima vez que busquemos de nuevo este registro lo encontraremos en el mapa, por lo que no necesitaremos ir a la base de datos. + +```java + Person ID is:1;Person Name is:John;Phone Number is:27304159 + Person found in Map. +``` + +* Si el registro correspondiente no está en la base de datos, se lanza una excepción. Esta es su implementación. + +```java +public class IdNotFoundException extends RuntimeException { + public IdNotFoundException(final String message) { + super(message); + } +} +``` +## Diagrama de clases + +![alt text](./etc/IdentityMap.png "Identity Map Pattern") + +## Aplicabilidad + +* La idea detrás del patrón Identity Map es que cada vez que leemos un registro de la base de datos, primero comprobamos el Identity Map para ver si el registro ya ha sido recuperado. Esto nos permite simplemente devolver una nueva referencia al registro en memoria en lugar de crear un nuevo objeto, manteniendo la integridad referencial. +* Una ventaja secundaria del Mapa de Identidad es que, al actuar como caché, reduce el número de llamadas a la base de datos necesarias para recuperar objetos, lo que supone una mejora del rendimiento. + +## Créditos + +* [Identity Map](https://www.sourcecodeexamples.net/2018/04/identity-map-pattern.html) diff --git a/localization/es/identity-map/etc/IdentityMap.png b/localization/es/identity-map/etc/IdentityMap.png new file mode 100644 index 000000000000..1bac10ebf294 Binary files /dev/null and b/localization/es/identity-map/etc/IdentityMap.png differ diff --git a/localization/es/intercepting-filter/README.md b/localization/es/intercepting-filter/README.md new file mode 100644 index 000000000000..3322a79156b9 --- /dev/null +++ b/localization/es/intercepting-filter/README.md @@ -0,0 +1,166 @@ +--- +title: Intercepting Filter +shortTitle: Intercepting Filter +category: Behavioral +language: es +tag: + - Decoupling +--- + +## Propósito +Un filtro de intercepción es un útil patrón de diseño Java que se utiliza cuando se desea preprocesar o postprocesar una petición en una aplicación. Estos filtros se crean y se aplican a la solicitud antes de que se le da a la aplicación de destino. Algunos ejemplos de uso incluyen la autenticación, que es necesario procesar antes de que la solicitud se entregue a la aplicación. + +## Explicación +Ejemplo del mundo real +> Un ejemplo de uso del patrón de diseño Filtro Interceptor es relevante a la hora de realizar una plataforma de comercio electrónico. Es importante implementar varios filtros para la autenticación de la cuenta, la autenticación del pago, el registro y el almacenamiento en caché. Los tipos de filtros importantes en este ejemplo son los filtros de autenticación, registro, seguridad y almacenamiento en caché. + +En palabras sencillas +> Un filtro de intercepción en Java es como una serie de puntos de control de seguridad para peticiones y respuestas en una aplicación de software. Comprueba y procesa los datos a medida que entran y salen, ayudando con tareas como la autenticación, el registro y la seguridad, mientras mantiene el núcleo de la aplicación seguro y limpio. + +Wikipedia dice +> El filtro de intercepción es un patrón de Java que crea filtros conectables para procesar servicios comunes de una manera estándar sin necesidad de realizar cambios en el código de procesamiento de solicitudes. + +## Ejemplo Programático +Como ejemplo, podemos crear una clase Filtro básica y definir un Filtro de Autenticación. Al filtro le falta lógica, pero + +```java +// 1. Define a Filter interface +interface Filter { + void runFilter(String request); +} +// 2. Create a Authentication filter +class AuthenticationFilter implements Filter { + public void runFilter(String request) { + // Authentication logic would be passed in here + if (request.contains("authenticated=true")) { + System.out.println("Authentication successful for request: " + request); + } else { + System.out.println("Authentication failed for request: " + request); + } + } +} +// 3. Create a Client to send requests and activate the filter +class Client { + // create an instance of the filter in the Client class + private Filter filter; + + // create constructor + public Client(Filter filter) { + this.filter = filter; + } + + // send the String request to the filter, the request does not have to be a string + // it can be anything + public void sendRequest(String request) { + filter.runFilter(request); + } +} +// 4. Demonstrate the Authentication Filter +public class AuthenticationFilterExample { + public static void main(String[] args) { + Filter authenticationFilter = new AuthenticationFilter(); + Client client = new Client(authenticationFilter); + + // Simulate requests for false + client.sendRequest("GET /public-page"); + // this request would come back as true as the link includes an argument + // for successful authentication + client.sendRequest("GET /private-page?authenticated=true"); + } +} +``` +Este es un ejemplo básico de cómo implementar el esqueleto de un filtro. Falta la lógica de autenticación en AuthenticationFilterExample, pero se puede rellenar en los huecos. + +Además, el cliente puede ser configurado para ejecutar múltiples filtros en su solicitud utilizando un bucle For poblado con filtros como se puede ver a continuación: + +```java +// 1. Define a Filter interface +interface Filter { + void runFilter(String request); +} + +// 2. Create an Authentication filter +class AuthenticationFilter implements Filter { + public void runFilter(String request) { + // Authentication logic would be placed here + if (request.contains("authenticated=true")) { + System.out.println("Authentication successful for request: " + request); + } else { + System.out.println("Authentication failed for request: " + request); + } + } +} + +// 3. Create a Client to send requests and activate multiple filters +class Client { + // create a list of filters in the Client class + private List filters = new ArrayList<>(); + + // add filters to the list + public void addFilter(Filter filter) { + filters.add(filter); + } + + // send the request through all the filters + public void sendRequest(String request) { + for (Filter filter : filters) { + filter.runFilter(request); + } + } +} + +// 4. Demonstrate multiple filters +public class MultipleFiltersExample { + public static void main(String[] args) { + // Create a client + Client client = new Client(); + + // Add filters to the client + Filter authenticationFilter = new AuthenticationFilter(); + client.addFilter(authenticationFilter); + + // Add more filters as needed + // Filter anotherFilter = new AnotherFilter(); + // client.addFilter(anotherFilter); + + // Simulate requests + client.sendRequest("GET /public-page"); + client.sendRequest("GET /private-page?authenticated=true"); + } +} +``` +Este método permite manipular y comprobar los datos de forma rápida y sencilla antes de autenticar un inicio de sesión o finalizar otro tipo de acción. + +## Diagrama de clases +![alt text](./etc/intercepting-filter.png "Intercepting Filter") + +## Aplicabilidad +Utilice el patrón Filtro interceptor cuando + +* Un programa necesita preprocesar o postprocesar datos +* Un sistema necesita servicios de autorización/autenticación para acceder a datos sensibles +* Desea registrar/auditar peticiones o respuestas con fines de depuración o almacenamiento, como marcas de tiempo y acciones del usuario +* Desea transformar datos de un tipo a otro antes de entregarlos al proceso final. +* Desea implementar un manejo específico de excepciones + +## Consecuencias +Consecuencias de la aplicación del filtro de interceptación + +* Aumento de la complejidad del código, disminuyendo la facilidad de lectura +* Puede haber problemas en el orden en que se aplican los filtros si el orden es importante +* Aplicar múltiples filtros a una petición puede crear un retraso en el tiempo de respuesta +* Probar los efectos de múltiples filtros en una petición puede ser difícil. +* La compatibilidad y la gestión de versiones pueden ser difíciles si se tienen muchos filtros. + +## Tutoriales + +* [Introduction to Intercepting Filter Pattern in Java](https://www.baeldung.com/intercepting-filter-pattern-in-java) + +## Ejemplos del mundo real + +* [javax.servlet.FilterChain](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/FilterChain.html) and [javax.servlet.Filter](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/Filter.html) +* [Struts 2 - Interceptors](https://struts.apache.org/core-developers/interceptors.html) + +## Créditos + +* [TutorialsPoint - Intercepting Filter](http://www.tutorialspoint.com/design_pattern/intercepting_filter_pattern.htm) diff --git a/localization/es/intercepting-filter/etc/intercepting-filter.png b/localization/es/intercepting-filter/etc/intercepting-filter.png new file mode 100644 index 000000000000..ec792639eab5 Binary files /dev/null and b/localization/es/intercepting-filter/etc/intercepting-filter.png differ diff --git a/localization/es/interpreter/README.md b/localization/es/interpreter/README.md new file mode 100644 index 000000000000..a2cfacf9c3be --- /dev/null +++ b/localization/es/interpreter/README.md @@ -0,0 +1,170 @@ +--- +title: Interpreter +shortTitle: Interpreter +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## Propósito + +Dado un lenguaje, defina una representación para su gramática junto con un intérprete que utilice la +representación para interpretar las frases del lenguaje. + +## Explicación + +Ejemplo real + +> Los niños enanos están aprendiendo matemáticas básicas en la escuela. Empiezan por lo más básico: "1 + 1", "4 - 2", "5 + 5", etcétera. + +En palabras simples + +> El patrón intérprete interpreta las frases en el idioma deseado. + +Wikipedia dice + +> En programación informática, el patrón intérprete es un patrón de diseño que especifica cómo evaluar sentencias en un lenguaje. La idea básica es tener una clase para cada símbolo (terminal o no terminal) en un lenguaje informático especializado. El árbol sintáctico de una sentencia en el lenguaje es una instancia del patrón compuesto y se utiliza para evaluar (interpretar) la sentencia para un cliente. + +**Ejemplo programático** + +Para poder interpretar matemáticas básicas, necesitamos una jerarquía de expresiones. La abstracción básica para ello es la clase `Expression`. + +```java +public abstract class Expression { + + public abstract int interpret(); + + @Override + public abstract String toString(); +} +``` + +La más sencilla de las expresiones es la `NumberExpression` que contiene un único número entero. + +```java +public class NumberExpression extends Expression { + + private final int number; + + public NumberExpression(int number) { + this.number = number; + } + + public NumberExpression(String s) { + this.number = Integer.parseInt(s); + } + + @Override + public int interpret() { + return number; + } + + @Override + public String toString() { + return "number"; + } +} +``` + +Las expresiones más complejas son operaciones como `PlusExpression`, `MinusExpression` y +`MultiplicarExpresión`. Aquí está la primera de ellas, las otras son similares. + +```java +public class PlusExpression extends Expression { + + private final Expression leftExpression; + private final Expression rightExpression; + + public PlusExpression(Expression leftExpression, Expression rightExpression) { + this.leftExpression = leftExpression; + this.rightExpression = rightExpression; + } + + @Override + public int interpret() { + return leftExpression.interpret() + rightExpression.interpret(); + } + + @Override + public String toString() { + return "+"; + } +} +``` + +Ahora podemos mostrar el patrón del intérprete en acción analizando algunas matemáticas sencillas. + +```java + // the halfling kids are learning some basic math at school + // define the math string we want to parse + final var tokenString = "4 3 2 - 1 + *"; + + // the stack holds the parsed expressions + var stack = new Stack(); + + // tokenize the string and go through them one by one + var tokenList = tokenString.split(" "); + for (var s : tokenList) { + if (isOperator(s)) { + // when an operator is encountered we expect that the numbers can be popped from the top of + // the stack + var rightExpression = stack.pop(); + var leftExpression = stack.pop(); + LOGGER.info("popped from stack left: {} right: {}", + leftExpression.interpret(), rightExpression.interpret()); + var operator = getOperatorInstance(s, leftExpression, rightExpression); + LOGGER.info("operator: {}", operator); + var result = operator.interpret(); + // the operation result is pushed on top of the stack + var resultExpression = new NumberExpression(result); + stack.push(resultExpression); + LOGGER.info("push result to stack: {}", resultExpression.interpret()); + } else { + // numbers are pushed on top of the stack + var i = new NumberExpression(s); + stack.push(i); + LOGGER.info("push to stack: {}", i.interpret()); + } + } + // in the end, the final result lies on top of the stack + LOGGER.info("result: {}", stack.pop().interpret()); +``` + +La ejecución del programa produce la siguiente salida de consola. + +``` +popped from stack left: 1 right: 1 +operator: + +push result to stack: 2 +popped from stack left: 4 right: 2 +operator: * +push result to stack: 8 +result: 8 +``` + +## Diagrama de clases + +![alt text](./etc/interpreter_1.png "Interpreter") + +## Aplicabilidad + +Utilice el patrón Intérprete cuando exista un lenguaje que interpretar, y pueda representar las sentencias +del lenguaje como árboles sintácticos abstractos. El patrón Intérprete funciona mejor cuando + +* La gramática es simple. Para gramáticas complejas, la jerarquía de clases para la gramática se hace grande e inmanejable. Herramientas como los generadores de analizadores sintácticos son una mejor alternativa en estos casos. Pueden interpretar expresiones sin construir árboles sintácticos abstractos, lo que puede ahorrar espacio y posiblemente tiempo. +* La eficiencia no es una preocupación crítica. Por lo general, los intérpretes más eficientes no se implementan interpretando directamente los árboles de análisis sintáctico, sino traduciéndolos primero a otra forma. Por ejemplo, las expresiones regulares suelen transformarse en máquinas de estados. Pero incluso entonces, el traductor puede ser implementado por el patrón Intérprete, por lo que el patrón sigue siendo aplicable. + +## Usos conocidos + +* [java.util.Pattern](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) +* [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html) +* Todas las subclases de [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html) +* [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html) + + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/es/interpreter/etc/interpreter_1.png b/localization/es/interpreter/etc/interpreter_1.png new file mode 100644 index 000000000000..f10342a1df90 Binary files /dev/null and b/localization/es/interpreter/etc/interpreter_1.png differ diff --git a/localization/es/iterator/README.md b/localization/es/iterator/README.md new file mode 100644 index 000000000000..7a9be6ef016a --- /dev/null +++ b/localization/es/iterator/README.md @@ -0,0 +1,154 @@ +--- +title: Iterator +shortTitle: Iterator +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## También conocido como + +Cursor + +## Propósito +Proporcionar una forma de acceder a los elementos de un objeto agregado secuencialmente sin exponer su +representación subyacente. + +## Explicación + +Ejemplo del mundo real + +> El cofre del tesoro contiene un conjunto de objetos mágicos. Hay múltiples tipos de artículos tales como anillos, +> Los objetos pueden buscarse por tipo utilizando un iterador que proporciona +> el cofre del tesoro. + +En palabras sencillas + +> Los contenedores pueden proporcionar una interfaz de iterador agnóstica de representación +> para proporcionar acceso a los elementos. + +Wikipedia dice + +> En programación orientada a objetos, el patrón iterador es un patrón de diseño en el que +> se utiliza un iterador para recorrer un contenedor y acceder a sus elementos. + +**Ejemplo programático** + +La clase principal de nuestro ejemplo es `TreasureChest` que contiene ítems. + +```java +public class TreasureChest { + + private final List items; + + public TreasureChest() { + items = List.of( + new Item(ItemType.POTION, "Potion of courage"), + new Item(ItemType.RING, "Ring of shadows"), + new Item(ItemType.POTION, "Potion of wisdom"), + new Item(ItemType.POTION, "Potion of blood"), + new Item(ItemType.WEAPON, "Sword of silver +1"), + new Item(ItemType.POTION, "Potion of rust"), + new Item(ItemType.POTION, "Potion of healing"), + new Item(ItemType.RING, "Ring of armor"), + new Item(ItemType.WEAPON, "Steel halberd"), + new Item(ItemType.WEAPON, "Dagger of poison")); + } + + public Iterator iterator(ItemType itemType) { + return new TreasureChestItemIterator(this, itemType); + } + + public List getItems() { + return new ArrayList<>(items); + } +} +``` + +Esta es la clase `Item`: + +```java +public class Item { + + private ItemType type; + private final String name; + + public Item(ItemType type, String name) { + this.setType(type); + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public ItemType getType() { + return type; + } + + public final void setType(ItemType type) { + this.type = type; + } +} + +public enum ItemType { + + ANY, WEAPON, RING, POTION + +} +``` + +La interfaz `Iterator` es extremadamente sencilla. + +```java +public interface Iterator { + + boolean hasNext(); + + T next(); +} +``` + +En el siguiente ejemplo, iteramos a través de los objetos de tipo anillo encontrados en el cofre. + +```java +var itemIterator = TREASURE_CHEST.iterator(ItemType.RING); +while (itemIterator.hasNext()) { + LOGGER.info(itemIterator.next().toString()); +} +``` + +Salida del programa: + +```java +Ring of shadows +Ring of armor +``` + +## Diagrama de clases + +![alt text](./etc/iterator_1.png "Iterator") + +## Aplicabilidad + +Utilizar el patrón Iterator + +* Para acceder al contenido de un objeto agregado sin exponer su representación interna. +* Para soportar múltiples recorridos de objetos agregados. +* Proporcionar una interfaz uniforme para recorrer diferentes estructuras de agregados. + +## Tutoriales + +* [How to Use Iterator?](http://www.tutorialspoint.com/java/java_using_iterator.htm) + +## Usos conocidos + +* [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html) +* [java.util.Enumeration](http://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/es/iterator/etc/iterator_1.png b/localization/es/iterator/etc/iterator_1.png new file mode 100644 index 000000000000..d8313bc5881f Binary files /dev/null and b/localization/es/iterator/etc/iterator_1.png differ diff --git a/localization/es/layers/README.md b/localization/es/layers/README.md index 8fdd76ca89a9..f6101b077948 100644 --- a/localization/es/layers/README.md +++ b/localization/es/layers/README.md @@ -1,5 +1,6 @@ --- title: Layers +shortTitle: Layers category: Architectural language: es tag: diff --git a/localization/es/lazy-loading/README.md b/localization/es/lazy-loading/README.md index 611868f33df5..8120732f607d 100644 --- a/localization/es/lazy-loading/README.md +++ b/localization/es/lazy-loading/README.md @@ -1,5 +1,6 @@ --- title: Lazy Loading +shortTitle: Lazy Loading category: Idiom language: es tag: diff --git a/localization/es/leader-election/README.md b/localization/es/leader-election/README.md new file mode 100644 index 000000000000..424b50731c8d --- /dev/null +++ b/localization/es/leader-election/README.md @@ -0,0 +1,836 @@ +--- +title: Leader Election +shortTitle: Leader Election +category: Behavioral +language: es +tag: + - Cloud distributed +--- + +## Propósito +El patrón de elección (Leader pattern) del líder se utiliza habitualmente en el diseño de sistemas en la nube. Puede ayudar a garantizar que las instancias de tarea seleccionen la instancia líder correctamente y no entren en conflicto entre sí, causen contención por recursos compartidos o interfieran inadvertidamente con el trabajo que otras instancias de tarea están realizando. + +## Explicación + +Ejemplo del mundo real +> En un sistema basado en la nube de escalado horizontal, múltiples instancias de la misma tarea podrían estar ejecutándose al mismo tiempo con cada instancia sirviendo a un usuario diferente. Si estas instancias escriben en un recurso compartido, es necesario coordinar sus acciones para evitar que cada instancia sobrescriba los cambios realizados por las demás. En otro escenario, si las tareas están realizando elementos individuales de un cálculo complejo en paralelo, los resultados necesitan ser agregados cuando todos ellos se completan. + +En palabras sencillas +> este patrón se utiliza en sistemas distribuidos basados en la nube en los que el líder debe actuar como coordinador/agregador entre instancias de escalado horizontal para evitar conflictos en los recursos compartidos. + +Wikipedia dice +> En computación distribuida, la elección del líder es el proceso de designar un único proceso como organizador de alguna tarea distribuida entre varios ordenadores (nodos). + +**Ejemplo programático** + +El algoritmo Ring y el algoritmo Bully implementan el patrón de elección de líder. +Cada algoritmo requiere que cada instancia participante tenga un ID único. + +```java +public interface Instance { + + /** + * Comprueba si la instancia está viva o no. + * + * @return {@code true} si la instancia está viva. + */ + boolean isAlive(); + + /** + * Establece el estado de salud de la instancia en cuestión. + * + * @param alive {@code true} por vivo. + */ + void setAlive(boolean alive); + + /** + * Consumir mensajes de otras instancias. + * + * @param message Mensaje enviado por otras instancias + */ + void onMessage(Message message); + +} +``` + +Clase abstracta que implementa las interfaces Instance y Runnable. + +```java +public abstract class AbstractInstance implements Instance, Runnable { + + protected static final int HEARTBEAT_INTERVAL = 5000; + private static final String INSTANCE = "Instance "; + + protected MessageManager messageManager; + protected Queue messageQueue; + protected final int localId; + protected int leaderId; + protected boolean alive; + + /** + * Constructor de BullyInstance. + */ + public AbstractInstance(MessageManager messageManager, int localId, int leaderId) { + this.messageManager = messageManager; + this.messageQueue = new ConcurrentLinkedQueue<>(); + this.localId = localId; + this.leaderId = leaderId; + this.alive = true; + } + + /** + * La instancia ejecutará el mensaje en su cola de mensajes periódicamente una vez que esté viva. + */ + @Override + @SuppressWarnings("squid:S2189") + public void run() { + while (true) { + if (!this.messageQueue.isEmpty()) { + this.processMessage(this.messageQueue.remove()); + } + } + } + + /** + * Una vez que los mensajes son enviados a una instancia determinada, se añaden a la cola y esperan a ser ejecutados. + * + * @param message Mensaje enviado por otras instancias + */ + @Override + public void onMessage(Message message) { + messageQueue.offer(message); + } + + /** + * Comprueba si la instancia está viva o no. + * + * @return {@code true} si la instancia está viva. + */ + @Override + public boolean isAlive() { + return alive; + } + + /** + * Establece el estado de salud de la instancia en cuestión. + * + * @param alive {@code true} por vivo. + */ + @Override + public void setAlive(boolean alive) { + this.alive = alive; + } + + /** + * Procesa el mensaje según su tipo. + * + * @param message Mensaje sondeado desde la cola. + */ + private void processMessage(Message message) { + switch (message.getType()) { + case ELECTION -> { + LOGGER.info(INSTANCE + localId + " - Election Message handling..."); + handleElectionMessage(message); + } + case LEADER -> { + LOGGER.info(INSTANCE + localId + " - Leader Message handling..."); + handleLeaderMessage(message); + } + case HEARTBEAT -> { + LOGGER.info(INSTANCE + localId + " - Heartbeat Message handling..."); + handleHeartbeatMessage(message); + } + case ELECTION_INVOKE -> { + LOGGER.info(INSTANCE + localId + " - Election Invoke Message handling..."); + handleElectionInvokeMessage(); + } + case LEADER_INVOKE -> { + LOGGER.info(INSTANCE + localId + " - Leader Invoke Message handling..."); + handleLeaderInvokeMessage(); + } + case HEARTBEAT_INVOKE -> { + LOGGER.info(INSTANCE + localId + " - Heartbeat Invoke Message handling..."); + handleHeartbeatInvokeMessage(); + } + default -> { + } + } + } + + /** + * Métodos abstractos para manejar diferentes tipos de mensajes. + * Estos métodos deben implementarse en la clase de instancia concreta para implementar el patrón de selección de líder correspondiente. + */ + protected abstract void handleElectionMessage(Message message); + + protected abstract void handleElectionInvokeMessage(); + + protected abstract void handleLeaderMessage(Message message); + + protected abstract void handleLeaderInvokeMessage(); + + protected abstract void handleHeartbeatMessage(Message message); + + protected abstract void handleHeartbeatInvokeMessage(); + +} +``` + +```java +/** + * Mensaje utilizado para transportar datos entre instancias. + */ +@Setter +@Getter +@EqualsAndHashCode +@AllArgsConstructor +@NoArgsConstructor +public class Message { + + private MessageType type; + private String content; + +} +``` + +```java +public interface MessageManager { + + /** + * Envía un mensaje heartbeat a la instancia líder para comprobar si la instancia líder está viva. + * + * @param leaderId ID de instancia de la instancia líder. + * @return {@code true} si la instancia líder está viva, o {@code false} si no. + */ + boolean sendHeartbeatMessage(int leaderId); + + /** + * Enviar mensaje electoral a otras instancias. + * + * @param currentId ID de la instancia que envía este mensaje. + * @param content Contenido del mensaje electoral. + * @return {@code true} si el mensaje es aceptado por las instancias de destino. + */ + boolean sendElectionMessage(int currentId, String content); + + /** + * Enviar mensaje de notificación de nuevo líder a otras instancias. + * + * @param currentId ID de la instancia que envía este mensaje. + * @param leaderId Contenido del mensaje del líder. + * @return {@code true} si el mensaje es aceptado por las instancias de destino. + */ + boolean sendLeaderMessage(int currentId, int leaderId); + + /** + * Enviar mensaje de invocación de heartbeat. Esto invocará la tarea heartbeat en la instancia de destino. + * + * @param currentId ID de la instancia que envía este mensaje. + */ + void sendHeartbeatInvokeMessage(int currentId); + +} +``` +These type of messages are used to pass among instances. +```java +/** + * Enum de tipo de mensaje. + */ +public enum MessageType { + + /** + * Comienza la elección. El contenido del mensaje almacena ID(s) de la(s) instancia(s) candidata(s). + */ + ELECTION, + + /** + * Nodifica al nuevo líder. El contenido del mensaje debe ser el ID del líder. + */ + LEADER, + + /** + * Comprueba la salud de la instancia de líder actual. + */ + HEARTBEAT, + + /** + * Informar a la instancia de destino para iniciar la elección. + */ + ELECTION_INVOKE, + + /** + * Informar a la instancia de destino para que notifique a todas las demás instancias que es el nuevo líder. + */ + LEADER_INVOKE, + + /** + * Informar a la instancia de destino para que inicie el heartbeat. + */ + HEARTBEAT_INVOKE + +} +``` + +Clase abstracta que implementa la interfaz MessageManager. Ayuda a gestionar mensajes entre instancias. + +```java +/** + * Clase abstracta de todas las clases de gestores de mensajes. + */ +public abstract class AbstractMessageManager implements MessageManager { + + /** + * Contiene todas las instancias del sistema. La clave es su ID y el valor es la propia instancia. + */ + + protected Map instanceMap; + + /** + * Constructor de AbstractMessageManager. + */ + public AbstractMessageManager(Map instanceMap) { + this.instanceMap = instanceMap; + } + + /** + * Encuentra la siguiente instancia con el ID más pequeño. + * + * @return La siguiente instancia. + */ + protected Instance findNextInstance(int currentId) { + Instance result = null; + var candidateList = instanceMap.keySet() + .stream() + .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) + .sorted() + .toList(); + if (candidateList.isEmpty()) { + var index = instanceMap.keySet() + .stream() + .filter((i) -> instanceMap.get(i).isAlive()) + .sorted() + .toList() + .get(0); + result = instanceMap.get(index); + } else { + var index = candidateList.get(0); + result = instanceMap.get(index); + } + return result; + } + +} +``` + +Here the implementation of Ring Algorithm +```java +/** + * Implementación con algoritmo token ring. Las instancias en el sistema se organizan como un anillo. + * Cada instancia debe tener un id secuencial y la instancia con el id más pequeño (o más grande) debe + * ser el líder inicial. Todas las demás instancias envían un mensaje heartbeat al líder periódicamente para + * comprobar su salud. Si una instancia determinada encuentra que el servidor ha terminado, enviará un mensaje de elección + * a la siguiente instancia viva en el anillo, que contiene su propio ID. Entonces la siguiente instancia añade su + * ID en el mensaje y se lo pasa a la siguiente. Después de que todos los ID de las instancias vivas se añaden al * mensaje, el mensaje se devuelve. + * mensaje, el mensaje es enviado de vuelta a la primera instancia y ésta elegirá la instancia con + * menor ID para ser el nuevo líder, y entonces enviará un mensaje de líder a otras instancias para informar del + * resultado. + */ +@Slf4j +public class RingInstance extends AbstractInstance { + private static final String INSTANCE = "Instance "; + + /** + * Constructor de RingInstance. + */ + public RingInstance(MessageManager messageManager, int localId, int leaderId) { + super(messageManager, localId, leaderId); + } + + /** + * Procesar el mensaje de invocación heartbeat. Después de recibir el mensaje, la instancia enviará un + * heartbeat (latido) al líder para comprobar su salud. Si está vivo, informará a la siguiente instancia para hacer el + * heartbeat. Si no, iniciará el proceso de elección. + */ + @Override + protected void handleHeartbeatInvokeMessage() { + try { + var isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); + if (isLeaderAlive) { + LOGGER.info(INSTANCE + localId + "- Leader is alive. Start next heartbeat in 5 second."); + Thread.sleep(HEARTBEAT_INTERVAL); + messageManager.sendHeartbeatInvokeMessage(this.localId); + } else { + LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election."); + messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId)); + } + } catch (InterruptedException e) { + LOGGER.info(INSTANCE + localId + "- Interrupted."); + } + } + + /** + * Procesar mensaje de elección. Si el ID local está contenido en la lista de ID, la instancia seleccionará + * la instancia viva con menor ID para ser el nuevo líder, y enviará el mensaje de información al líder. + * Si no, añadirá su ID local a la lista y enviará el mensaje a la siguiente instancia en el anillo. + * anillo. + */ + @Override + protected void handleElectionMessage(Message message) { + var content = message.getContent(); + LOGGER.info(INSTANCE + localId + " - Election Message: " + content); + var candidateList = Arrays.stream(content.trim().split(",")) + .map(Integer::valueOf) + .sorted() + .toList(); + if (candidateList.contains(localId)) { + var newLeaderId = candidateList.get(0); + LOGGER.info(INSTANCE + localId + " - New leader should be " + newLeaderId + "."); + messageManager.sendLeaderMessage(localId, newLeaderId); + } else { + content += "," + localId; + messageManager.sendElectionMessage(localId, content); + } + } + + /** + * Procesar mensaje de líder. La instancia establecerá el ID de líder para que sea el nuevo y enviará el + * mensaje a la siguiente instancia hasta que todas las instancias vivas del anillo estén informadas. + */ + @Override + protected void handleLeaderMessage(Message message) { + var newLeaderId = Integer.valueOf(message.getContent()); + if (this.leaderId != newLeaderId) { + LOGGER.info(INSTANCE + localId + " - Update leaderID"); + this.leaderId = newLeaderId; + messageManager.sendLeaderMessage(localId, newLeaderId); + } else { + LOGGER.info(INSTANCE + localId + " - Leader update done. Start heartbeat."); + messageManager.sendHeartbeatInvokeMessage(localId); + } + } + + /** + * No se utiliza en la instancia Ring. + */ + @Override + protected void handleLeaderInvokeMessage() { + // No se utiliza en la instancia Ring. + } + + @Override + protected void handleHeartbeatMessage(Message message) { + // No se utiliza en la instancia Ring. + } + + @Override + protected void handleElectionInvokeMessage() { + // No se utiliza en la instancia Ring. + } + +} +``` + +```java +/** + * Implementación de RingMessageManager. + */ +public class RingMessageManager extends AbstractMessageManager { + + /** + * Constructor de RingMessageManager. + */ + public RingMessageManager(Map instanceMap) { + super(instanceMap); + } + + /** + * Envía un mensaje de heartbeat a la instancia líder actual para comprobar su estado. + * + * @param leaderId leaderID + * @return {@code true} si el líder está vivo. + */ + @Override + public boolean sendHeartbeatMessage(int leaderId) { + var leaderInstance = instanceMap.get(leaderId); + var alive = leaderInstance.isAlive(); + return alive; + } + + /** + * Enviar mensaje de elección a la siguiente instancia. + * + * @param currentId currentID + * @param content contiene todos los ID de las instancias que han recibido este mensaje de elección + * mensaje. + * @return {@code true} si el mensaje de elección es aceptado por la instancia de destino. + */ + @Override + public boolean sendElectionMessage(int currentId, String content) { + var nextInstance = this.findNextInstance(currentId); + var electionMessage = new Message(MessageType.ELECTION, content); + nextInstance.onMessage(electionMessage); + return true; + } + + /** + * Enviar mensaje de líder a la siguiente instancia. + * + * @param currentId ID de la instancia que envía este mensaje. + * @param leaderId Contenido del mensaje del líder. + * @return {@code true} si el mensaje de líder es aceptado por la instancia de destino. + */ + @Override + public boolean sendLeaderMessage(int currentId, int leaderId) { + var nextInstance = this.findNextInstance(currentId); + var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); + nextInstance.onMessage(leaderMessage); + return true; + } + + /** + * Enviar mensaje de invocación de heartbeat a la siguiente instancia. + * + * @param currentId ID de la instancia que envía este mensaje. + */ + @Override + public void sendHeartbeatInvokeMessage(int currentId) { + var nextInstance = this.findNextInstance(currentId); + var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); + nextInstance.onMessage(heartbeatInvokeMessage); + } + +} +``` +```java +public static void main(String[] args) { + + Map instanceMap = new HashMap<>(); + var messageManager = new RingMessageManager(instanceMap); + + var instance1 = new RingInstance(messageManager, 1, 1); + var instance2 = new RingInstance(messageManager, 2, 1); + var instance3 = new RingInstance(messageManager, 3, 1); + var instance4 = new RingInstance(messageManager, 4, 1); + var instance5 = new RingInstance(messageManager, 5, 1); + + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instanceMap.put(5, instance5); + + instance2.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); + + final var thread1 = new Thread(instance1); + final var thread2 = new Thread(instance2); + final var thread3 = new Thread(instance3); + final var thread4 = new Thread(instance4); + final var thread5 = new Thread(instance5); + + thread1.start(); + thread2.start(); + thread3.start(); + thread4.start(); + thread5.start(); + + instance1.setAlive(false); + } +``` + +The console output +``` +[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Heartbeat Invoke Message handling... +[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2- Leader is not alive. Start election. +[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Message handling... +[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Election Message: 2 +[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Election Message handling... +[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4 - Election Message: 2,3 +[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Election Message handling... +[Thread-4] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 5 - Election Message: 2,3,4 +[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Message handling... +[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - Election Message: 2,3,4,5 +[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - New leader should be 2. +[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling... +[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Update leaderID +[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Leader Message handling... +[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4 - Update leaderID +[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Leader Message handling... +[Thread-4] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 5 - Update leaderID +[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Leader Message handling... +[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - Update leaderID +[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling... +[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Leader update done. Start heartbeat. +[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Heartbeat Invoke Message handling... +[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4- Leader is alive. Start next heartbeat in 5 second. +``` +Here the implementation of Bully algorithm +```java +/** + * Impelemetación con algoritmo bully. Cada instancia debe tener un id secuencial y es capaz de + * comunicarse con otras instancias del sistema. Inicialmente la instancia con menor (o mayor) + * ID más pequeño (o más grande) es seleccionado para ser el líder. Todas las demás instancias envían un mensaje heartbeat al líder + * periódicamente para comprobar su salud. Si una instancia determinada considera que el servidor ha terminado, enviará un mensaje de elección a todas las instancias del sistema. + * mensaje de elección a todas las instancias cuyo ID sea mayor. Si la instancia objetivo está viva, + * devolverá un mensaje alive (en este ejemplo devolverá true) y entonces enviará un mensaje de elección con + * su ID. Si no, la instancia original enviará un mensaje de líder a todas las demás instancias. + */ +@Slf4j +public class BullyInstance extends AbstractInstance { + private static final String INSTANCE = "Instance "; + + /** + * Constructor de BullyInstance. + */ + public BullyInstance(MessageManager messageManager, int localId, int leaderId) { + super(messageManager, localId, leaderId); + } + + /** + * Procesar el mensaje de invocación heartbeat. Después de recibir el mensaje, la instancia enviará un + * heartbeat al líder para comprobar su salud. Si está vivo, informará a la siguiente instancia para hacer el + * heartbeat. Si no, iniciará el proceso de elección. + */ + @Override + protected void handleHeartbeatInvokeMessage() { + try { + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); + if (isLeaderAlive) { + LOGGER.info(INSTANCE + localId + "- Leader is alive."); + Thread.sleep(HEARTBEAT_INTERVAL); + messageManager.sendHeartbeatInvokeMessage(localId); + } else { + LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election."); + boolean electionResult = + messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification."); + messageManager.sendLeaderMessage(localId, localId); + } + } + } catch (InterruptedException e) { + LOGGER.info(INSTANCE + localId + "- Interrupted."); + } + } + + /** + * Procesar mensaje de invocación de elección. Enviar mensaje de elección a todas las instancias con menor ID. Si + * alguna de ellas está viva, no hacer nada. Si ninguna instancia está viva, enviar mensaje de líder a todas las instancias vivas y reiniciar el heartbeat. + * instancia viva y reiniciar heartbeat. + */ + @Override + protected void handleElectionInvokeMessage() { + if (!isLeader()) { + LOGGER.info(INSTANCE + localId + "- Start election."); + boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification."); + leaderId = localId; + messageManager.sendLeaderMessage(localId, localId); + messageManager.sendHeartbeatInvokeMessage(localId); + } + } + } + + /** + * Procesar mensaje de líder. Actualizar la información del líder local. + */ + @Override + protected void handleLeaderMessage(Message message) { + leaderId = Integer.valueOf(message.getContent()); + LOGGER.info(INSTANCE + localId + " - Leader update done."); + } + + private boolean isLeader() { + return localId == leaderId; + } + + @Override + protected void handleLeaderInvokeMessage() { + // Not used in Bully Instance + } + + @Override + protected void handleHeartbeatMessage(Message message) { + // Not used in Bully Instance + } + + @Override + protected void handleElectionMessage(Message message) { + // Not used in Bully Instance + } +} +``` + +```java +/** + * Implementación de BullyMessageManager. + */ +public class BullyMessageManager extends AbstractMessageManager { + + /** + * Constructor de BullyMessageManager. + */ + public BullyMessageManager(Map instanceMap) { + super(instanceMap); + } + + /** + * Envía un mensaje de heartbeat a la instancia líder actual para comprobar su estado. + * + * @param leaderId leaderID + * @return {@code true} si el líder está vivo. + */ + @Override + public boolean sendHeartbeatMessage(int leaderId) { + var leaderInstance = instanceMap.get(leaderId); + var alive = leaderInstance.isAlive(); + return alive; + } + + /** + * Enviar mensaje electoral a todas las instancias con menor ID. + * + * @param currentId ID de la instancia que envía este mensaje. + * @param content Contenido del mensaje electoral. + * @return {@code true} si ninguna instancia viva tiene menor ID, para que se acepte la elección. + */ + @Override + public boolean sendElectionMessage(int currentId, String content) { + var candidateList = findElectionCandidateInstanceList(currentId); + if (candidateList.isEmpty()) { + return true; + } else { + var electionMessage = new Message(MessageType.ELECTION_INVOKE, ""); + candidateList.stream().forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); + return false; + } + } + + /** + * Enviar mensaje de líder a todas las instancias para notificar el nuevo líder. + * + * @param currentId ID de la instancia que envía este mensaje. + * @param leaderId Contenido del mensaje del líder. + * @return {@code true} si se acepta el mensaje. + */ + @Override + public boolean sendLeaderMessage(int currentId, int leaderId) { + var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); + instanceMap.keySet() + .stream() + .filter((i) -> i != currentId) + .forEach((i) -> instanceMap.get(i).onMessage(leaderMessage)); + return false; + } + + /** + * Enviar mensaje de invocación de heartbeat a la siguiente instancia. + * + * @param currentId ID de la instancia que envía este mensaje. + */ + @Override + public void sendHeartbeatInvokeMessage(int currentId) { + var nextInstance = this.findNextInstance(currentId); + var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); + nextInstance.onMessage(heartbeatInvokeMessage); + } + + /** + * Encuentra todas las instancias vivas con un ID menor que la instancia actual. + * + * @param currentId ID de la instancia actual. + * @return Lista de ID de todas las instancias candidatas. + */ + private List findElectionCandidateInstanceList(int currentId) { + return instanceMap.keySet() + .stream() + .filter((i) -> i < currentId && instanceMap.get(i).isAlive()) + .toList(); + } + +} +``` + +```java +public static void main(String[] args) { + + Map instanceMap = new HashMap<>(); + var messageManager = new BullyMessageManager(instanceMap); + + var instance1 = new BullyInstance(messageManager, 1, 1); + var instance2 = new BullyInstance(messageManager, 2, 1); + var instance3 = new BullyInstance(messageManager, 3, 1); + var instance4 = new BullyInstance(messageManager, 4, 1); + var instance5 = new BullyInstance(messageManager, 5, 1); + + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instanceMap.put(5, instance5); + + instance4.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); + + final var thread1 = new Thread(instance1); + final var thread2 = new Thread(instance2); + final var thread3 = new Thread(instance3); + final var thread4 = new Thread(instance4); + final var thread5 = new Thread(instance5); + + thread1.start(); + thread2.start(); + thread3.start(); + thread4.start(); + thread5.start(); + + instance1.setAlive(false); + } +``` + +La salida de la consola +``` +[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Heartbeat Invoke Message handling... +[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4- Leader is alive. +[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Heartbeat Invoke Message handling... +[Thread-4] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 5- Leader is not alive. Start election. +[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Election Invoke Message handling... +[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Invoke Message handling... +[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4- Start election. +[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Start election. +[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Invoke Message handling... +[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Start election. +[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... +[Thread-1] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 2- Start election. +[Thread-1] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 2- Succeed in election. Start leader notification. +[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... +[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Leader Message handling... +[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling... +[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... +[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Leader Message handling... +[Thread-0] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 1 - Leader Message handling... +[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling... +[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4 - Leader update done. +[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3 - Leader update done. +[Thread-0] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 1 - Leader update done. +[Thread-4] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 5 - Leader update done. +[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Heartbeat Invoke Message handling... +[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Leader is alive. +``` + +## Diagrama de clases +![alt text](./etc/leader-election.urm.png "Leader Election pattern class diagram") + +## Aplicabilidad +Utilice este patrón cuando + +* las tareas en una aplicación distribuida, como una solución alojada en la nube, requieren una coordinación cuidadosa y no hay un líder natural. + +No utilice este patrón cuando + +* exista un líder natural o un proceso dedicado que pueda actuar siempre como líder. Por ejemplo, puede ser posible implementar un proceso singleton que coordine las instancias de tareas. Si este proceso falla o se vuelve insalubre, el sistema puede apagarlo y reiniciarlo. +* la coordinación entre tareas puede lograrse fácilmente utilizando un mecanismo más ligero. Por ejemplo, si varias instancias de tareas simplemente requieren un acceso coordinado a un recurso compartido, una solución preferible podría ser utilizar un bloqueo optimista o pesimista para controlar el acceso a ese recurso. + +## Créditos + +* [Leader Election pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/leader-election) +* [Raft Leader Election](https://github.com/ronenhamias/raft-leader-election) diff --git a/localization/es/leader-election/etc/leader-election.urm.png b/localization/es/leader-election/etc/leader-election.urm.png new file mode 100644 index 000000000000..85a90b75b636 Binary files /dev/null and b/localization/es/leader-election/etc/leader-election.urm.png differ diff --git a/localization/es/map-reduce/README.md b/localization/es/map-reduce/README.md new file mode 100644 index 000000000000..28a77f0062a1 --- /dev/null +++ b/localization/es/map-reduce/README.md @@ -0,0 +1,256 @@ +--- +title: "Patrón de Diseño MapReduce en Java" +shortTitle: MapReduce +description: "Aprende el patrón de diseño MapReduce en Java con ejemplos de la vida real, diagramas de clases y tutoriales. Entiende su intención, aplicación, beneficios y usos conocidos para mejorar tu conocimiento sobre patrones de diseño." +category: Optimización de Rendimiento +language: es +tag: + - Data processing + - Code simplification + - Delegation + - Performance + - Procesamiento de datos + - Simplificación de coódigo + - Delegación + - Rendimiento +--- + +## También conocido como: + +- Estrategia Separa-Aplica-Combina (Split-Apply-Combine Strategy) +- Patrón Dispersa-Recolecta (Scatter-Gather Pattern) + +## Intención del Patrón de Diseño Map Reduce + +MapReduce pretende procesar y generar grandes conjuntos de datos con un algoritmo en paralelo y distribuido en un grupo. Divide la carga de trabajo en dos fases principales: Mapear (Map) y Reducir (Reduce), permitiendo así una mayor eficiencia en procesamiento de datos en paralelo. + +## Explicación Detallada del Patrón de Diseño Map Reduce con Ejemplos del Mundo Real + +Ejemplo Práctico + +> Imagina una compañía de comercio en línea (e-commerce) que quiere analizar sus datos de ventas a lo largo de múltplies regiones. Tienen terabytes de datos de transacciones almacenados en cientos de servidores. Usando MapReduce, pueden procesar estos datos eficientemente para calcular las ventas totales por categoría de producto. La función Map procesaría registros de ventas individuales, regresando pares clave-valor de (categoría, cantidad de venta). La función Reduce entonces sumaría todas las cantidades de ventas por cada categoría, produciendo el resultado final. + +En palabras sencillas + +> MapReduce divide un gran problema en partes más pequeñas, las procesa en paralelo y después combina los resultados. + +En Wikipedia dice: + +> "MapReduce es un modelo de programación e implementación asociada para procesar y generar grandes conjuntos de información con un algoritmo en paralelo y distribuido en un grupo". +> MapReduce consiste de dos funciones principales: +> El nodo principal toma la entrada de información, la divide en problemas más pequeños (sub-problemas) y los distribuye a los nodos restantes (de trabajo). Un nodo de trabajo puede repetir este proceso llevando a una estructura de árbol multinivel. El nodo de trabajo procesa el sub-problema y le regresa la respuesta al nodo principal. +> El nodo principal recolecta todas las respuestas de todos los sub-problemas y las combina de cierta manera para regresar la salida de información - la respuesta del principal problema que estaba resolviendo. +> Este acercamiento permite un procesamiento eficiente de grandes cantidades de datos através de múltiples máquinas, convirtiéndola en una técnica fundamental en análisis de cantidades enormes de datos y computo distribuido. + +## Ejemplo Programático de Map Reduce en Java + +### 1. Fase de Map (Mapeador; División & Procesamiento de Datos) + +- El Mapper toma una entrada de texto (string), lo divide en palabras y cuenta las ocurrencias. +- Salida: Un mapa {palabra → conteo} por cada línea de entrada. + +#### `Mapper.java` + +```java +public class Mapper { + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} +``` + +Ejemplo de Entrada: `"Hello world hello"` +Salida: `{hello=2, world=1}` + +### 2. Fase de Shuffle (Combinación; Agrupar Datos por Clave) + +- La Combinación recolecta pares clave-valor de múltiples mapeadores (mappers) y valores de grupo por clave. + +#### `Shuffler.java` + +```java +public class Shuffler { + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} +``` + +Ejemplo de Entrada: + +``` +[ + {"hello": 2, "world": 1}, + {"hello": 1, "java": 1} +] +``` + +Salida: + +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +### 3. Fase de Reduce (Reductor; Agregar Resultados) + +- El Reductor suma todas las ocurrencias de cada palabra. + +#### `Reducer.java` + +```java +public class Reducer { + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} +``` + +Ejemplo de Entrada: + +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +Salida: + +``` +[ + {"hello": 3}, + {"world": 1}, + {"java": 1} +] +``` + +### 4. Ejecutar el Proceso Completo de MapReduce + +- La clase MapReduce coordina los tres pasos. + +#### `MapReduce.java` + +```java +public class MapReduce { + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } + + Map> grouped = Shuffler.shuffleAndSort(mapped); + + return Reducer.reduce(grouped); + } +} +``` + +### 5. Ejecución Principal (Llamar a MapReduce) + +- La clase Main ejecuta el "pipeline" de MapReduce y regresa el conteo final de palabras. + +#### `Main.java` + +```java + public static void main(String[] args) { + List inputs = Arrays.asList( + "Hello world hello", + "MapReduce is fun", + "Hello from the other side", + "Hello world" + ); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } +} +``` + +Salida: + +``` +hello: 4 +world: 2 +the: 1 +other: 1 +side: 1 +mapreduce: 1 +is: 1 +from: 1 +fun: 1 +``` + +## Cuándo Usar el Patrón de Diseño MapReduce en Java + +Usa MapReduce cuando se están: + +- Procesando grandes conjuntos de información que no quepa en la memoria de una sola máquina. +- Realizando cálculos que se puedan desarrollar en paralelo. +- Trabajando en escenarios tolerante a fallos y computación distribuida. +- Analizando archivos de registro, datos de rastreo web o datos científicos. + +## Tutoriales del Patrón de Diseño MapReduce en Java + +- [Tutorial MapReduce (Apache Hadoop)](https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html) +- [Ejemplo MapReduce (Simplilearn)](https://www.youtube.com/watch?v=l2clwKnrtO8) + +## Ventajas y Desventajas del Patrón de Diseño MapReduce + +Ventajas: + +- Escalabilidad: Puede procesar grandes cantidades de datos através de múltiples máquinas +- Tolerancia a Fallos: Maneja los fallos elegantemente +- Simplicidad: Resume detalles complejos de distribución computacional + +Desventajas: + +- Costo-Beneficio: Ineficiente para conjuntos de datos chicos por la preparación y coordinación necesaria +- Flexibilidad Limitada: Inadecuado para todos los tipos de computación o algoritmos +- Latencia: En relación a lotes de información podría ser inadecaudo para necesidades de procesamiento en tiempo real + +## Aplicaciones Reales para el Patrón de Diseño MapReduce en Java + +- Implementación original de Google para indexar páginas web +- Hadoop MapReduce para procesamiento de información extensa +- Sistemas de análisis de registros a gran escala +- Secuencia genómica en análisis de bio-informática + +## Patrones de Diseño relacionados a Java + +- Patrón de Encadenamiento (Chaining Pattern) +- Patrón de Maestro-Trabajador (Master-Worker Pattern) +- Patrón de Tubería (Pipeline Pattern) + +## Referencias y Creditos + +- [¿Qué es MapReduce?](https://www.ibm.com/think/topics/mapreduce) +- [¿Por qué MapReduce no ha muerto?](https://www.codemotion.com/magazine/ai-ml/big-data/mapreduce-not-dead-heres-why-its-still-ruling-in-the-cloud/) +- [Soluciones Escalables de Procesamiento de Datos Distribuidos](https://tcpp.cs.gsu.edu/curriculum/?q=system%2Ffiles%2Fch07.pdf) +- [Patrones de Diseño en Java: Experiencia Práctica con Ejemplos del Mundo Real](https://amzn.to/3HWNf4U) diff --git a/marker/README.md b/localization/es/marker/README.md similarity index 54% rename from marker/README.md rename to localization/es/marker/README.md index 1a2198b5cab3..0073f398a6f6 100644 --- a/marker/README.md +++ b/localization/es/marker/README.md @@ -1,28 +1,29 @@ --- title: Marker Interface +shortTitle: Marker Interface category: Structural -language: en +language: es tag: - Decoupling --- -## Intent -Using empty interfaces as markers to distinguish special treated objects. +## Propósito +Utilización de interfaces vacías como marcadores para distinguir objetos con un tratamiento especial. -## Class diagram +## Diagrama de clases ![alt text](./etc/MarkerDiagram.png "Marker Interface") -## Applicability -Use the Marker Interface pattern when +## Aplicabilidad +Utilice el patrón de interfaz de marcador cuando -* you want to identify the special objects from normal objects (to treat them differently) -* you want to mark that some object is available for certain sort of operations +* Desea identificar los objetos especiales de los objetos normales (para tratarlos de forma diferente) +* Desea indicar que un objeto está disponible para cierto tipo de operaciones. -## Real world examples +## Ejemplos del mundo real * [javase.8.docs.api.java.io.Serializable](https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html) * [javase.8.docs.api.java.lang.Cloneable](https://docs.oracle.com/javase/8/docs/api/java/lang/Cloneable.html) -## Credits +## Créditos * [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) diff --git a/marker/etc/MarkerDiagram.png b/localization/es/marker/etc/MarkerDiagram.png similarity index 100% rename from marker/etc/MarkerDiagram.png rename to localization/es/marker/etc/MarkerDiagram.png diff --git a/localization/es/mediator/README.md b/localization/es/mediator/README.md new file mode 100644 index 000000000000..c70384cc9a0e --- /dev/null +++ b/localization/es/mediator/README.md @@ -0,0 +1,191 @@ +--- +title: Mediator +shortTitle: Mediator +category: Behavioral +language: es +tag: + - Gang Of Four + - Decoupling +--- + +## Propósito + +Define un objeto que encapsula cómo interactúa un conjunto de objetos. Mediator fomenta el acoplamiento flexible al evitar que los objetos se refieran entre sí de forma explícita, y te permite variar su interacción de forma independiente. + +## Explicación + +Un ejemplo real + +> Pícaro, mago, hobbit y cazador han decidido unir sus fuerzas y viajar en la misma party. Para evitar acoplar a cada miembro entre sí, utilizan la interfaz de la party para comunicarse entre ellos. + +En pocas palabras + +> Mediator desacopla un conjunto de clases forzando su flujo de comunicaciones a través de un objeto mediador. + +Wikipedia dice + +> En ingeniería de software, el patrón mediador define un objeto que encapsula cómo interactúa un conjunto de objetos. Este patrón se considera un patrón de comportamiento debido a la forma en que puede alterar el comportamiento de ejecución del programa. En la programación orientada a objetos, los programas suelen constar de muchas clases. La lógica de negocio y la computación se distribuyen entre estas clases. Sin embargo, a medida que se añaden más clases a un programa, especialmente durante el mantenimiento y/o la refactorización, el problema de la comunicación entre estas clases puede volverse más complejo. Esto hace que el programa sea más difícil de leer y mantener. Además, puede resultar difícil modificar el programa, ya que cualquier cambio puede afectar al código de otras clases. Con el patrón mediador, la comunicación entre objetos se encapsula en un objeto mediador. Los objetos ya no se comunican directamente entre sí, sino a través del mediador. Esto reduce las dependencias entre los objetos que se comunican, reduciendo así el acoplamiento. + +**Ejemplo programático** + +En este ejemplo, el mediador encapsula cómo interactúan un conjunto de objetos. En lugar de referirse unos a otros directamente, utilizan la interfaz del mediador. + +Los miembros de la party `Rogue`, `Wizard`, `Hobbit`, y `Hunter` heredan del `PartyMemberBase` implementando la interfaz `PartyMember`. + +```java +public interface PartyMember { + + void joinedParty(Party party); + + void partyAction(Action action); + + void act(Action action); +} + +@Slf4j +public abstract class PartyMemberBase implements PartyMember { + + protected Party party; + + @Override + public void joinedParty(Party party) { + LOGGER.info("{} joins the party", this); + this.party = party; + } + + @Override + public void partyAction(Action action) { + LOGGER.info("{} {}", this, action.getDescription()); + } + + @Override + public void act(Action action) { + if (party != null) { + LOGGER.info("{} {}", this, action); + party.act(this, action); + } + } + + @Override + public abstract String toString(); +} + +public class Rogue extends PartyMemberBase { + + @Override + public String toString() { + return "Rogue"; + } +} + +// Wizard, Hobbit, and Hunter are implemented similarly +``` + +Nuestro sistema mediador consta de la interfaz `Party` y su implementación. + +```java +public interface Party { + + void addMember(PartyMember member); + + void act(PartyMember actor, Action action); +} + +public class PartyImpl implements Party { + + private final List members; + + public PartyImpl() { + members = new ArrayList<>(); + } + + @Override + public void act(PartyMember actor, Action action) { + for (var member : members) { + if (!member.equals(actor)) { + member.partyAction(action); + } + } + } + + @Override + public void addMember(PartyMember member) { + members.add(member); + member.joinedParty(this); + } +} +``` + +Aquí tienes una demo que muestra el patrón mediador (Mediator) en acción. + +```java + // create party and members + Party party = new PartyImpl(); + var hobbit = new Hobbit(); + var wizard = new Wizard(); + var rogue = new Rogue(); + var hunter = new Hunter(); + + // add party members + party.addMember(hobbit); + party.addMember(wizard); + party.addMember(rogue); + party.addMember(hunter); + + // perform actions -> the other party members + // are notified by the party + hobbit.act(Action.ENEMY); + wizard.act(Action.TALE); + rogue.act(Action.GOLD); + hunter.act(Action.HUNT); +``` + +Esta es la salida de la consola al ejecutar el ejemplo. + +``` +Hobbit joins the party +Wizard joins the party +Rogue joins the party +Hunter joins the party +Hobbit spotted enemies +Wizard runs for cover +Rogue runs for cover +Hunter runs for cover +Wizard tells a tale +Hobbit comes to listen +Rogue comes to listen +Hunter comes to listen +Rogue found gold +Hobbit takes his share of the gold +Wizard takes his share of the gold +Hunter takes his share of the gold +Hunter hunted a rabbit +Hobbit arrives for dinner +Wizard arrives for dinner +Rogue arrives for dinner +``` + +## Diagrama de clases + +![alt text](./etc/mediator_1.png "Mediator") + +## Aplicabilidad + +Utilice el patrón Mediator cuando: + +* Un conjunto de objetos se comunican de formas bien definidas pero complejas. Las interdependencias resultantes no están estructuradas y son difíciles de entender +* La reutilización de un objeto es difícil porque hace referencia y se comunica con muchos otros objetos. +* Un comportamiento que se distribuye entre varias clases debe ser personalizable sin muchas subclases. + +## Usos conocidos + +* Todos los métodos scheduleXXX() de [java.util.Timer](http://docs.oracle.com/javase/8/docs/api/java/util/Timer.html) +* [java.util.concurrent.Executor#execute()](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable-) +* Métodos submit() e invokeXXX() de [java.util.concurrent.ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) +* Método scheduleXXX() de [java.util.concurrent.ScheduledExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html) +* [java.lang.reflect.Method#invoke()](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/es/mediator/etc/mediator_1.png b/localization/es/mediator/etc/mediator_1.png new file mode 100644 index 000000000000..0b48ffdbaee7 Binary files /dev/null and b/localization/es/mediator/etc/mediator_1.png differ diff --git a/localization/es/memento/README.md b/localization/es/memento/README.md new file mode 100644 index 000000000000..9b0e44ee7c09 --- /dev/null +++ b/localization/es/memento/README.md @@ -0,0 +1,170 @@ +--- +title: Memento +shortTitle: Memento +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## También conocido como + +Token + +## Propósito + +Sin violar la encapsulación, capturar y externalizar el estado interno de un objeto para que el objeto pueda ser restaurado a este estado más tarde. + +## Explicación + +Ejemplo del mundo real + +> Estamos trabajando en una aplicación de astrología en la que necesitamos analizar las propiedades de las estrellas a lo largo del tiempo. Estamos creando instantáneas de los estados de las estrellas utilizando el patrón Memento. + +En palabras sencillas + +> El patrón Memento captura el estado interno de los objetos facilitando su almacenamiento y restauración en cualquier punto del tiempo. + +Wikipedia dice + +> El patrón memento es un patrón de diseño de software que proporciona la capacidad de restaurar un objeto a su estado anterior (deshacer vía rollback). + +**Ejemplo programático** + +Definamos primero los tipos de estrellas que somos capaces de manejar. + +```java +public enum StarType { + SUN("sun"), + RED_GIANT("red giant"), + WHITE_DWARF("white dwarf"), + SUPERNOVA("supernova"), + DEAD("dead star"); + ... +} +``` + +A continuación, vayamos directamente a lo esencial. Aquí está la clase `Star` junto con los mementos que necesitamos manipular. Presta especial atención a los métodos `getMemento` y `setMemento`. + +```java +public interface StarMemento { +} + +public class Star { + + private StarType type; + private int ageYears; + private int massTons; + + public Star(StarType startType, int startAge, int startMass) { + this.type = startType; + this.ageYears = startAge; + this.massTons = startMass; + } + + public void timePasses() { + ageYears *= 2; + massTons *= 8; + switch (type) { + case RED_GIANT -> type = StarType.WHITE_DWARF; + case SUN -> type = StarType.RED_GIANT; + case SUPERNOVA -> type = StarType.DEAD; + case WHITE_DWARF -> type = StarType.SUPERNOVA; + case DEAD -> { + ageYears *= 2; + massTons = 0; + } + default -> { + } + } + } + + StarMemento getMemento() { + var state = new StarMementoInternal(); + state.setAgeYears(ageYears); + state.setMassTons(massTons); + state.setType(type); + return state; + } + + void setMemento(StarMemento memento) { + var state = (StarMementoInternal) memento; + this.type = state.getType(); + this.ageYears = state.getAgeYears(); + this.massTons = state.getMassTons(); + } + + @Override + public String toString() { + return String.format("%s age: %d years mass: %d tons", type.toString(), ageYears, massTons); + } + + private static class StarMementoInternal implements StarMemento { + + private StarType type; + private int ageYears; + private int massTons; + + // setters and getters -> + ... + } +} +``` + +Y, por último, así es como utilizamos los recuerdos para almacenar y restaurar estados estelares. + +```java + var states = new Stack<>(); + var star = new Star(StarType.SUN, 10000000, 500000); + LOGGER.info(star.toString()); + states.add(star.getMemento()); + star.timePasses(); + LOGGER.info(star.toString()); + states.add(star.getMemento()); + star.timePasses(); + LOGGER.info(star.toString()); + states.add(star.getMemento()); + star.timePasses(); + LOGGER.info(star.toString()); + states.add(star.getMemento()); + star.timePasses(); + LOGGER.info(star.toString()); + while (states.size() > 0) { + star.setMemento(states.pop()); + LOGGER.info(star.toString()); + } +``` + +Salida del programa: + +``` +sun age: 10000000 years mass: 500000 tons +red giant age: 20000000 years mass: 4000000 tons +white dwarf age: 40000000 years mass: 32000000 tons +supernova age: 80000000 years mass: 256000000 tons +dead star age: 160000000 years mass: 2048000000 tons +supernova age: 80000000 years mass: 256000000 tons +white dwarf age: 40000000 years mass: 32000000 tons +red giant age: 20000000 years mass: 4000000 tons +sun age: 10000000 years mass: 500000 tons +``` + +## Diagrama de clases + +![alt text](./etc/memento.png "Memento") + +## Aplicabilidad + +Utilice el patrón Memento cuando: + +* Es necesario guardar una instantánea del estado de un objeto para poder restaurarlo más tarde, y +* Una interfaz directa para obtener el estado expondría detalles de implementación y rompería la encapsulación del objeto. + +## Usos conocidos + +* [java.util.Date](http://docs.oracle.com/javase/8/docs/api/java/util/Date.html) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/es/memento/etc/memento.png b/localization/es/memento/etc/memento.png new file mode 100644 index 000000000000..dcfe7a749b04 Binary files /dev/null and b/localization/es/memento/etc/memento.png differ diff --git a/module/README.md b/localization/es/module/README.md similarity index 52% rename from module/README.md rename to localization/es/module/README.md index 2372445512cd..23a0452ed7a6 100644 --- a/module/README.md +++ b/localization/es/module/README.md @@ -1,34 +1,35 @@ --- title: Module +shortTitle: Module category: Structural -language: en +language: es tag: - Decoupling --- -## Intent -Module pattern is used to implement the concept of software modules, defined by modular programming, in a programming language with incomplete direct support for the concept. +## Propósito +El patrón de módulos se utiliza para implementar el concepto de módulos de software, definido por la programación modular, en un lenguaje de programación con soporte directo incompleto para el concepto. -## Explanation +## Explicación -Real-world example +Ejemplo real -> In a bustling software city, different software components such as Database, UI, and API often need to collaborate. Instead of each component directly talking with every other, they rely on the module manager. This module manager acts like a central marketplace, where each component registers its services and requests for others. This ensures that components remain decoupled, and changes to one don't ripple throughout the system. +> En una bulliciosa ciudad del software, distintos componentes de software, como la base de datos, la interfaz de usuario y la API, a menudo necesitan colaborar. En lugar de que cada componente hable directamente con los demás, confían en el gestor de módulos. Este gestor de módulos actúa como un mercado central, donde cada componente registra sus servicios y solicitudes para los demás. Esto garantiza que los componentes permanezcan desacoplados y que los cambios en uno de ellos no afecten a todo el sistema. -> Imagine a modern smartphone. It has different apps like messaging, camera, and music player. While each app functions independently, they sometimes need shared resources like access to contacts or storage. Instead of every app having its unique way to access these resources, they use the phone's built-in modules, like the Contacts module or the Storage module. This ensures a consistent experience for the user and avoids potential clashes between apps. +> Imagina un smartphone moderno. Tiene distintas aplicaciones, como mensajería, cámara y reproductor de música. Aunque cada una funciona de forma independiente, a veces necesitan compartir recursos como el acceso a los contactos o el almacenamiento. En lugar de que cada aplicación tenga su propia forma de acceder a estos recursos, utilizan los módulos integrados del teléfono, como el módulo de contactos o el de almacenamiento. Esto garantiza una experiencia coherente para el usuario y evita posibles conflictos entre aplicaciones. -In plain words +En pocas palabras -> The Module pattern encapsulates related functions and data into a single unit, allowing for organized and manageable software components. +> El patrón Módulo encapsula funciones y datos relacionados en una sola unidad, lo que permite tener componentes de software organizados y manejables. -Wikipedia says +Wikipedia dice -> In software engineering, the module pattern is a design pattern used to implement the concept of software modules, defined by modular programming, in a programming language with incomplete direct support for the concept. +> En ingeniería de software, el patrón módulo es un patrón de diseño utilizado para implementar el concepto de módulos de software, definido por la programación modular, en un lenguaje de programación con soporte directo incompleto para el concepto. -> This pattern can be implemented in several ways depending on the host programming language, such as the singleton design pattern, object-oriented static members in a class and procedural global functions. In Python, the pattern is built into the language, and each .py file is automatically a module. The same applies to Ada, where the package can be considered a module (similar to a static class). +> Este patrón se puede implementar de varias maneras dependiendo del lenguaje de programación anfitrión, como el patrón de diseño singleton, miembros estáticos orientados a objetos en una clase y funciones globales de procedimiento. En Python, el patrón está integrado en el lenguaje, y cada archivo .py es automáticamente un módulo. Lo mismo ocurre en Ada, donde el paquete puede considerarse un módulo (similar a una clase estática). -**Programmatic Example** +**Ejemplo programático** ```java //Define Logger abstract class @@ -144,20 +145,21 @@ public class App { } ``` -Programme outputs: +Resultados del programa: + ``` Writing to output.log: Hello, Module Pattern! Console Output: Hello, Module Pattern! ``` -## Class diagram +## Diagrama de clases ![alt text](./etc/module.png "Module") -## Applicability -The Module pattern can be considered a creational pattern and a structural pattern. It manages the creation and organization of other elements, and groups them as the structural pattern does. +## Aplicabilidad +El patrón Módulo puede considerarse un patrón de creación y un patrón estructural. Gestiona la creación y organización de otros elementos, y los agrupa como lo hace el patrón estructural. -An object that applies this pattern can provide the equivalent of a namespace, providing the initialization and finalization process of a static class or a class with static members with cleaner, more concise syntax and semantics. +Un objeto que aplica este patrón puede proporcionar el equivalente de un espacio de nombres, proporcionando el proceso de inicialización y finalización de una clase estática o de una clase con miembros estáticos con una sintaxis y una semántica más limpias y concisas. -## Credits +## Créditos * [Module](https://en.wikipedia.org/wiki/Module_pattern) diff --git a/module/etc/module.png b/localization/es/module/etc/module.png similarity index 100% rename from module/etc/module.png rename to localization/es/module/etc/module.png diff --git a/localization/es/monostate/README.md b/localization/es/monostate/README.md index 5ac695e88987..17a32b8beb51 100644 --- a/localization/es/monostate/README.md +++ b/localization/es/monostate/README.md @@ -1,5 +1,6 @@ --- title: MonoState +shortTitle: MonoState category: Creational language: es tag: diff --git a/localization/es/multiton/README.md b/localization/es/multiton/README.md index 720e31b70f85..032075cb9bb2 100644 --- a/localization/es/multiton/README.md +++ b/localization/es/multiton/README.md @@ -1,5 +1,6 @@ --- title: Multiton +shortTitle: Multiton category: Creational language: es tag: diff --git a/localization/es/null-object/README.md b/localization/es/null-object/README.md new file mode 100644 index 000000000000..10196f8763a3 --- /dev/null +++ b/localization/es/null-object/README.md @@ -0,0 +1,175 @@ +--- +title: Null Object +shortTitle: Null Object +category: Behavioral +language: es +tag: + - Extensibility +--- + +## Propósito + +En la mayoría de los lenguajes orientados a objetos, como Java o C#, las referencias pueden ser nulas. Estas referencias deben comprobarse para asegurarse de que no son nulas antes de invocar cualquier método, porque normalmente los métodos no pueden invocarse sobre referencias nulas. En lugar de utilizar una referencia nula para indicar la ausencia de un objeto (por ejemplo, un cliente inexistente), se utiliza un objeto que implementa la interfaz esperada, pero cuyo cuerpo de método está vacío. La ventaja de este enfoque sobre una implementación por defecto es que un objeto nulo es muy predecible y no tiene efectos secundarios: no hace nada. + +## Explicación + +Ejemplo del mundo real + +> Estamos construyendo un árbol binario a partir de nodos. Hay nodos normales y nodos "vacíos". Recorrer el árbol normalmente no debería causar errores, por lo que utilizamos el patrón de objetos nulos cuando es necesario. + +En palabras sencillas + +> El patrón de objetos nulos maneja los objetos "vacíos" con elegancia. + +Wikipedia dice + +> En programación informática orientada a objetos, un objeto nulo es un objeto sin valor referenciado o con un comportamiento neutro ("nulo") definido. El patrón de diseño de objetos nulos describe los usos de tales objetos y su comportamiento (o la falta del mismo). + +**Ejemplo programático** + +Esta es la definición de la interfaz `Node`. + +```java +public interface Node { + + String getName(); + + int getTreeSize(); + + Node getLeft(); + + Node getRight(); + + void walk(); +} +``` + +Tenemos dos implementaciones de `Node`. La implementación normal `NodeImpl` y `NullNode` para nodos vacíos. + +```java +@Slf4j +public class NodeImpl implements Node { + + private final String name; + private final Node left; + private final Node right; + + /** + * Constructor. + */ + public NodeImpl(String name, Node left, Node right) { + this.name = name; + this.left = left; + this.right = right; + } + + @Override + public int getTreeSize() { + return 1 + left.getTreeSize() + right.getTreeSize(); + } + + @Override + public Node getLeft() { + return left; + } + + @Override + public Node getRight() { + return right; + } + + @Override + public String getName() { + return name; + } + + @Override + public void walk() { + LOGGER.info(name); + if (left.getTreeSize() > 0) { + left.walk(); + } + if (right.getTreeSize() > 0) { + right.walk(); + } + } +} + +public final class NullNode implements Node { + + private static final NullNode instance = new NullNode(); + + private NullNode() { + } + + public static NullNode getInstance() { + return instance; + } + + @Override + public int getTreeSize() { + return 0; + } + + @Override + public Node getLeft() { + return null; + } + + @Override + public Node getRight() { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public void walk() { + // Do nothing + } +} +``` + +Entonces podemos construir y recorrer el árbol binario sin errores de la siguiente manera. + +```java + var root = new NodeImpl("1", + new NodeImpl("11", + new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), + NullNode.getInstance() + ), + new NodeImpl("12", + NullNode.getInstance(), + new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()) + ) + ); + root.walk(); +``` + +Salida del programa: + +``` +1 +11 +111 +12 +122 +``` + +## Diagrama de clases + +![alt text](./etc/null-object.png "Null Object") + +## Aplicabilidad + +Utilice el patrón Objeto nulo cuando + +* Desea evitar la comprobación explícita de nulos y mantener el algoritmo elegante y fácil de leer. + +## Créditos + +* [Pattern Languages of Program Design 3](https://www.amazon.com/gp/product/0201310112/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201310112&linkCode=as2&tag=javadesignpat-20&linkId=7372ffb8a4e39a3bb10f199b89aef921) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/es/null-object/etc/null-object.png b/localization/es/null-object/etc/null-object.png new file mode 100644 index 000000000000..cb0a5409c735 Binary files /dev/null and b/localization/es/null-object/etc/null-object.png differ diff --git a/localization/es/object-mother/README.md b/localization/es/object-mother/README.md index 8c775c13dde5..f9bdf69f998b 100644 --- a/localization/es/object-mother/README.md +++ b/localization/es/object-mother/README.md @@ -1,5 +1,6 @@ --- title: Object Mother +shortTitle: Object Mother category: Creational language: es tag: diff --git a/localization/es/object-pool/README.md b/localization/es/object-pool/README.md index de69b12445ea..dfcb02b1b6af 100644 --- a/localization/es/object-pool/README.md +++ b/localization/es/object-pool/README.md @@ -1,5 +1,6 @@ --- title: Object Pool +shortTitle: Object Pool category: Creational language: es tag: diff --git a/localization/es/observer/README.md b/localization/es/observer/README.md new file mode 100644 index 000000000000..2c4d09c213f6 --- /dev/null +++ b/localization/es/observer/README.md @@ -0,0 +1,156 @@ +--- +title: Observer +shortTitle: Observer +category: Behavioral +language: en +tag: + - Gang Of Four + - Reactive +--- + +## También conocido como + +Dependents, Publish-Subscribe + +## Propósito + +Defina una dependencia de uno a muchos entre objetos para que cuando un objeto cambie de estado, todos sus dependientes sean notificados y actualizados automáticamente. + +## Explicación + +Ejemplo real + +> En una tierra muy lejana viven las razas de los hobbits y los orcos. Ambos viven principalmente al aire libre, por lo que siguen de cerca los cambios meteorológicos. Se podría decir que observan constantemente el tiempo. + +En palabras simples + +> Se registran como observadores para recibir los cambios de estado del objeto. + +Wikipedia dice + +> El patrón observador es un patrón de diseño de software en el que un objeto, llamado sujeto, mantiene una lista de sus dependientes, llamados observadores, y les notifica automáticamente cualquier cambio de estado, normalmente llamando a uno de sus métodos. + +**Ejemplo programático** + +Presentemos primero la interfaz `WeatherObserver` y nuestras razas, `Orcs` y `Hobbits`. + +```java +public interface WeatherObserver { + + void update(WeatherType currentWeather); +} + +@Slf4j +public class Orcs implements WeatherObserver { + + @Override + public void update(WeatherType currentWeather) { + LOGGER.info("The orcs are facing " + currentWeather.getDescription() + " weather now"); + } +} + +@Slf4j +public class Hobbits implements WeatherObserver { + + @Override + public void update(WeatherType currentWeather) { + switch (currentWeather) { + LOGGER.info("The hobbits are facing " + currentWeather.getDescription() + " weather now"); + } + } +} +``` + +Luego está el clima `Weather`, que cambia constantemente. + +```java +@Slf4j +public class Weather { + + private WeatherType currentWeather; + private final List observers; + + public Weather() { + observers = new ArrayList<>(); + currentWeather = WeatherType.SUNNY; + } + + public void addObserver(WeatherObserver obs) { + observers.add(obs); + } + + public void removeObserver(WeatherObserver obs) { + observers.remove(obs); + } + + /** + * Makes time pass for weather. + */ + public void timePasses() { + var enumValues = WeatherType.values(); + currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; + LOGGER.info("The weather changed to {}.", currentWeather); + notifyObservers(); + } + + private void notifyObservers() { + for (var obs : observers) { + obs.update(currentWeather); + } + } +} +``` +Aquí está el ejemplo completo en acción. + +```java + var weather = new Weather(); + weather.addObserver(new Orcs()); + weather.addObserver(new Hobbits()); + weather.timePasses(); + weather.timePasses(); + weather.timePasses(); + weather.timePasses(); +``` + +Salida del programa: + +``` +The weather changed to rainy. +The orcs are facing rainy weather now +The hobbits are facing rainy weather now +The weather changed to windy. +The orcs are facing windy weather now +The hobbits are facing windy weather now +The weather changed to cold. +The orcs are facing cold weather now +The hobbits are facing cold weather now +The weather changed to sunny. +The orcs are facing sunny weather now +The hobbits are facing sunny weather now +``` + +## Diagrama de clases + +![alt text](./etc/observer.png "Observer") + +## Aplicabilidad + +Utilice el patrón Observador en cualquiera de las siguientes situaciones: + +* Cuando una abstracción tiene dos aspectos, uno dependiente del otro. Encapsular estos aspectos en objetos separados te permite variarlos y reutilizarlos independientemente. +* Cuando un cambio en un objeto requiere cambiar otros, y no sabes cuántos objetos necesitan ser cambiados. +* Cuando un objeto debe ser capaz de notificar a otros objetos sin hacer suposiciones sobre quiénes son estos objetos. En otras palabras, no quieres que estos objetos estén estrechamente acoplados. + +## Usos conocidos + +* [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) +* [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) +* [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) +* [RxJava](https://github.com/ReactiveX/RxJava) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Java Generics and Collections](https://www.amazon.com/gp/product/0596527756/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596527756&linkCode=as2&tag=javadesignpat-20&linkId=246e5e2c26fe1c3ada6a70b15afcb195) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/es/observer/etc/observer.png b/localization/es/observer/etc/observer.png new file mode 100644 index 000000000000..f2ab0edfeb21 Binary files /dev/null and b/localization/es/observer/etc/observer.png differ diff --git a/localization/es/page-controller/README.md b/localization/es/page-controller/README.md new file mode 100644 index 000000000000..1464c3dfc9ba --- /dev/null +++ b/localization/es/page-controller/README.md @@ -0,0 +1,163 @@ +--- +title: Page Controller +shortTitle: Page Controller +category: Structural +language: es +tags: +- Decoupling +--- + +## Nombre / clasificación + +Page Controller + +## Propósito + +Se trata de un enfoque de una página que conduce a un archivo lógico que gestiona acciones o peticiones en un sitio web. + +## Explicación + +Un ejemplo real + +> En un sitio web de compras, hay una página de registro para dar de alta un perfil de usuario. Una vez finalizado el registro, la página de registro se redirigirá a una página de usuario para mostrar la información registrada del usuario. + +En pocas palabras + +> El controlador de página gestiona las peticiones HTTP y los datos en una página específica usando la idea MVC. +> La idea es que una página contiene un controlador que maneja el modelo y la vista. + +**Ejemplo programático** + +Aquí está el controlador Signup cuando un usuario registra su información para un sitio web. + +```java +@Slf4j +@Controller +@Component +public class SignupController { + SignupView view = new SignupView(); + /** + * Signup Controller can handle http request and decide which model and view use. + */ + SignupController() { + } + + /** + * Handle http GET request. + */ + @GetMapping("/signup") + public String getSignup() { + return view.display(); + } + + /** + * Handle http POST request and access model and view. + */ + @PostMapping("/signup") + public String create(SignupModel form, RedirectAttributes redirectAttributes) { + LOGGER.info(form.getName()); + LOGGER.info(form.getEmail()); + redirectAttributes.addAttribute("name", form.getName()); + redirectAttributes.addAttribute("email", form.getEmail()); + redirectAttributes.addFlashAttribute("userInfo", form); + return view.redirect(form); + } +} +``` +Aquí está el modelo y la vista de Signup que son manejados por el controlador de Signup. + +```java +@Component +@Getter +@Setter +public class SignupModel { + private String name; + private String email; + private String password; + + public SignupModel() { + } +} +``` + +```java +@Slf4j +public class SignupView { + public SignupView() { + } + + public String display() { + LOGGER.info("display signup front page"); + return "/signup"; + } + + /** + * redirect to user page. + */ + public String redirect(SignupModel form) { + LOGGER.info("Redirect to user page with " + "name " + form.getName() + " email " + form.getEmail()); + return "redirect:/user"; + } +} +``` + +Este es el Controlador de Usuario para manejar la petición Get en una página de usuario. + +```java +@Slf4j +@Controller +public class UserController { + UserView view = new UserView(); + + public UserController() {} + + /** + * Handle http GET request and access view and model. + */ + @GetMapping("/user") + public String getUserPath(SignupModel form, Model model) { + model.addAttribute("name", form.getName()); + model.addAttribute("email", form.getEmail()); + return view.display(form); + } +} +``` + +Aquí están el Modelo de Usuario y la Vista que son manejados por el controlador de Usuario. + +```java +@Getter +@Setter +public class UserModel { + private String name; + private String email; + + public UserModel() {} +} +``` + +```java +@Slf4j +public class UserView { + /** + * displaying command to generate html. + * @param user model content. + */ + public String display(SignupModel user) { + LOGGER.info("display user html" + " name " + user.getName() + " email " + user.getEmail()); + return "/user"; + } +} +``` + +## Diagrama de clases +![alt text](./etc/page-controller.urm.png) + +## Aplicabilidad +Utilice el patrón Page Controller cuando +- implementas un sitio donde la mayor parte de la lógica del controlador es simple +- implementa un sitio en el que determinadas acciones se gestionan con una página de servidor concreta + +## Créditos +- [Page Controller](https://www.martinfowler.com/eaaCatalog/pageController.html) +- [Pattern of Enterprise Application Architecture](https://www.martinfowler.com/books/eaa.html) \ No newline at end of file diff --git a/localization/es/page-controller/etc/page-controller.urm.png b/localization/es/page-controller/etc/page-controller.urm.png new file mode 100644 index 000000000000..d46bb018755b Binary files /dev/null and b/localization/es/page-controller/etc/page-controller.urm.png differ diff --git a/localization/es/page-object/README.md b/localization/es/page-object/README.md new file mode 100644 index 000000000000..d945bfb1f3bf --- /dev/null +++ b/localization/es/page-object/README.md @@ -0,0 +1,87 @@ +--- +title: Page Object +shortTitle: Page Object +category: Structural +language: es +tag: +- Decoupling +--- + +# Patrón de objetos de página en Java + +## Ejemplo del mundo real + +Considera un escenario de automatización web donde necesitas interactuar con una página web usando un framework de pruebas como Selenium. El patrón Page Object puede aplicarse para modelar cada página web como una clase Java. Cada clase encapsula la estructura y el comportamiento de la página web correspondiente, facilitando la gestión y actualización del código de automatización. + +## Intención + +Page Object encapsula la interfaz de usuario, ocultando el widget de interfaz de usuario subyacente de una aplicación (normalmente una aplicación web) y proporcionando una API específica de la aplicación para permitir la manipulación de los componentes de interfaz de usuario necesarios para las pruebas. De este modo, permite a la clase de prueba centrarse en la lógica de la prueba. + +## En pocas palabras + +El patrón Page Object en Java es un patrón de diseño utilizado en la automatización de pruebas para representar páginas web como clases Java. Cada clase corresponde a una página web específica y contiene métodos para interactuar con los elementos de esa página. Este patrón mejora el mantenimiento y la legibilidad del código en las pruebas automatizadas. + +## Wikipedia dice + +Aunque no hay una entrada específica en Wikipedia para el patrón Page Object, se utiliza ampliamente en las pruebas de software, en particular en el contexto de la automatización de la interfaz de usuario. El patrón Page Object ayuda a abstraer los detalles de una página web, proporcionando una forma más limpia y fácil de mantener para interactuar con elementos web en pruebas automatizadas. + +## Ejemplo programático + +Vamos a crear un ejemplo programático simple del patrón Page Object para una página de login utilizando Selenium en Java: + +```java +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +public class LoginPage { + private final WebDriver driver; + + // Web elements on the login page + private final By usernameInput = By.id("username"); + private final By passwordInput = By.id("password"); + private final By loginButton = By.id("login-button"); + + public LoginPage(WebDriver driver) { + this.driver = driver; + } + + // Methods to interact with the login page + + public void enterUsername(String username) { + WebElement usernameElement = driver.findElement(usernameInput); + usernameElement.sendKeys(username); + } + + public void enterPassword(String password) { + WebElement passwordElement = driver.findElement(passwordInput); + passwordElement.sendKeys(password); + } + + public void clickLoginButton() { + WebElement loginButtonElement = driver.findElement(loginButton); + loginButtonElement.click(); + } + + // Other methods specific to the login page if needed +} +``` + +En este ejemplo, la clase `LoginPage` representa la página de login de una aplicación web. Encapsula los elementos web de la página y proporciona métodos para interactuar con esos elementos. La instancia real de Selenium WebDriver se pasa al constructor, permitiendo a los métodos realizar acciones en la página web. + +Este objeto de página se puede utilizar en scripts de prueba para interactuar con la página de inicio de sesión sin exponer los detalles de la estructura de la página en el código de prueba, lo que favorece el mantenimiento y la reutilización. + +## Aplicabilidad + +Utilice el patrón Page Object cuando + +* Estás escribiendo pruebas automatizadas para tu aplicación web y quieres separar la manipulación de la interfaz de usuario necesaria para las pruebas de la lógica de prueba real. +* Haga sus pruebas menos frágiles, y más legibles y robustas. + +## Otro ejemplo con diagrama de clases +![alt text](./etc/page-object.png "Page Object") + +## Créditos + +* [Martin Fowler - PageObject](http://martinfowler.com/bliki/PageObject.html) +* [Selenium - Page Objects](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) diff --git a/localization/es/page-object/etc/page-object.png b/localization/es/page-object/etc/page-object.png new file mode 100644 index 000000000000..4240b438efbb Binary files /dev/null and b/localization/es/page-object/etc/page-object.png differ diff --git a/localization/es/parameter-object/README.md b/localization/es/parameter-object/README.md new file mode 100644 index 000000000000..73092e90b705 --- /dev/null +++ b/localization/es/parameter-object/README.md @@ -0,0 +1,125 @@ +--- +title: Parameter Object +shortTitle: Parameter Object +category: Behavioral +language: es +tag: + - Extensibility +--- + +## Propósito + +La sintaxis del lenguaje Java no permite declarar un método con un valor predefinido para un parámetro. Probablemente la mejor opción para conseguir parámetros de método predefinidos en Java es utilizar la sobrecarga de métodos. La sobrecarga de métodos permite declarar varios métodos con el mismo nombre pero con un número diferente de parámetros. Pero el principal problema con la sobrecarga de métodos como solución para los valores de los parámetros por defecto se revela cuando un método acepta múltiples parámetros. Crear un método sobrecargado para cada posible combinación de parámetros puede resultar engorroso. Para solucionar este problema, se utiliza el patrón Objeto Parámetro. + +## Explicación + +El Objeto Parámetro (Parameter Object) es simplemente un objeto envoltorio para todos los parámetros de un método. No es más que un POJO normal. La ventaja del Objeto Parámetro sobre una lista regular de parámetros de método es el hecho de que los campos de clase pueden tener valores por defecto. Una vez creada la clase envoltorio para la lista de parámetros del método, se crea también una clase constructora correspondiente. Normalmente es una clase estática interna. El paso final es utilizar el constructor para construir un nuevo objeto parámetro. Para aquellos parámetros que se omitan, se utilizarán sus valores por defecto. + + +**Ejemplo programático** + +Aquí está la simple clase `SearchService` donde se utiliza la sobrecarga de métodos para los valores por defecto aquí. Para utilizar la sobrecarga de métodos, el número de argumentos o el tipo de argumento tiene que ser diferente. + +```java +public class SearchService { + //Method Overloading example. SortOrder is defaulted in this method + public String search(String type, String sortBy) { + return getQuerySummary(type, sortBy, SortOrder.DESC); + } + + /* Method Overloading example. SortBy is defaulted in this method. Note that the type has to be + different here to overload the method */ + public String search(String type, SortOrder sortOrder) { + return getQuerySummary(type, "price", sortOrder); + } + + private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) { + return "Requesting shoes of type \"" + type + "\" sorted by \"" + sortBy + "\" in \"" + + sortOrder.getValue() + "ending\" order..."; + } +} + +``` + +A continuación presentamos el `SearchService` con `ParameterObject` creado con el patrón Builder. + +```java +public class SearchService { + + /* Parameter Object example. Default values are abstracted into the Parameter Object + at the time of Object creation */ + public String search(ParameterObject parameterObject) { + return getQuerySummary(parameterObject.getType(), parameterObject.getSortBy(), + parameterObject.getSortOrder()); + } + + private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) { + return "Requesting shoes of type \"" + type + "\" sorted by \"" + sortBy + "\" in \"" + + sortOrder.getValue() + "ending\" order..."; + } +} + +public class ParameterObject { + public static final String DEFAULT_SORT_BY = "price"; + public static final SortOrder DEFAULT_SORT_ORDER = SortOrder.ASC; + + private String type; + private String sortBy = DEFAULT_SORT_BY; + private SortOrder sortOrder = DEFAULT_SORT_ORDER; + + private ParameterObject(Builder builder) { + type = builder.type; + sortBy = builder.sortBy != null && !builder.sortBy.isBlank() ? builder.sortBy : sortBy; + sortOrder = builder.sortOrder != null ? builder.sortOrder : sortOrder; + } + + public static Builder newBuilder() { + return new Builder(); + } + + //Getters and Setters... + + public static final class Builder { + + private String type; + private String sortBy; + private SortOrder sortOrder; + + private Builder() { + } + + public Builder withType(String type) { + this.type = type; + return this; + } + + public Builder sortBy(String sortBy) { + this.sortBy = sortBy; + return this; + } + + public Builder sortOrder(SortOrder sortOrder) { + this.sortOrder = sortOrder; + return this; + } + + public ParameterObject build() { + return new ParameterObject(this); + } + } +} + + +``` + +## Diagrama de clases + +![alt text](./etc/parameter-object.png "Parameter Object") + +## Aplicabilidad + +Este patrón nos muestra la forma de tener parámetros por defecto para un método en Java ya que el lenguaje no tiene la característica de parámetros por defecto fuera de la caja. + +## Créditos + +- [Does Java have default parameters?](http://dolszewski.com/java/java-default-parameters) diff --git a/localization/es/parameter-object/etc/parameter-object.png b/localization/es/parameter-object/etc/parameter-object.png new file mode 100644 index 000000000000..661c3488ac49 Binary files /dev/null and b/localization/es/parameter-object/etc/parameter-object.png differ diff --git a/localization/es/partial-response/README.md b/localization/es/partial-response/README.md new file mode 100644 index 000000000000..78cd9ce6d400 --- /dev/null +++ b/localization/es/partial-response/README.md @@ -0,0 +1,24 @@ +--- +title: Partial Response +shortTitle: Partial Response +category: Behavioral +language: es +tag: + - Decoupling +--- + +## Propósito +Enviar una respuesta parcial del servidor al cliente en función de sus necesidades. El cliente especificará los campos que necesita al servidor, en lugar de servir todos los detalles del recurso. + +## Diagrama de clases +![alt text](./etc/partial-response.urm.png "Partial Response") + +## Aplicabilidad +Utilice el patrón de respuesta parcial cuando + +* El cliente sólo necesita un subconjunto de datos del recurso. +* Para evitar demasiada transferencia de datos por cable + +## Créditos + +* [Common Design Patterns](https://cloud.google.com/apis/design/design_patterns) diff --git a/localization/es/partial-response/etc/partial-response.urm.png b/localization/es/partial-response/etc/partial-response.urm.png new file mode 100644 index 000000000000..17dbd5f1b60d Binary files /dev/null and b/localization/es/partial-response/etc/partial-response.urm.png differ diff --git a/localization/es/pipeline/README.md b/localization/es/pipeline/README.md new file mode 100644 index 000000000000..f50a9ab1c5c9 --- /dev/null +++ b/localization/es/pipeline/README.md @@ -0,0 +1,111 @@ +--- +title: Pipeline +shortTitle: Pipeline +category: Behavioral +language: es +tag: + - Decoupling +--- + +## Propósito + +Permite procesar los datos en una serie de etapas, introduciendo una entrada inicial y pasando la salida procesada para que la utilicen las etapas siguientes. + +## Explicación + +El patrón Pipeline utiliza etapas ordenadas para procesar una secuencia de valores de entrada. Cada tarea implementada está representada por una etapa de la tubería. Puede pensar en las canalizaciones como algo similar a las líneas de ensamblaje de una fábrica, donde cada elemento de la línea de ensamblaje se construye por etapas. El artículo parcialmente ensamblado pasa de una etapa de ensamblaje a otra. Las salidas de la cadena de montaje se producen en el mismo orden que las entradas. + +Ejemplo del mundo real + +> Supongamos que queremos pasar una cadena a una serie de etapas de filtrado y convertirla como una matriz char en la última etapa. + +En palabras sencillas + +> El patrón pipeline es una cadena de montaje donde los resultados parciales pasan de una etapa a otra. + +Wikipedia dice + +> En ingeniería de software, un pipeline consiste en una cadena de elementos de procesamiento (procesos, hilos, coroutines, funciones, etc.), dispuestos de forma que la salida de cada elemento es la entrada del siguiente; el nombre es por analogía a un pipeline físico. + +**Ejemplo programático** + +Las etapas de nuestro pipeline se llaman `Handler`s. +```java +interface Handler { + O process(I input); +} +``` + +En nuestro ejemplo de procesamiento de cadenas tenemos 3 `Handler`s concretos diferentes. + +```java +class RemoveAlphabetsHandler implements Handler { + ... +} + +class RemoveDigitsHandler implements Handler { + ... +} + +class ConvertToCharArrayHandler implements Handler { + ... +} +``` + +Aquí está el `Pipeline` que recogerá y ejecutará los handlers uno a uno. + +```java +class Pipeline { + + private final Handler currentHandler; + + Pipeline(Handler currentHandler) { + this.currentHandler = currentHandler; + } + + Pipeline addHandler(Handler newHandler) { + return new Pipeline<>(input -> newHandler.process(currentHandler.process(input))); + } + + O execute(I input) { + return currentHandler.process(input); + } +} +``` + +Y aquí está la `Pipeline` en acción procesando la cadena. + +```java + var filters = new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); + filters.execute("GoYankees123!"); +``` + +## Diagrama de clases + +![alt text](./etc/pipeline.urm.png "Diagrama de clases del patrón Pipeline ") + +## Aplicabilidad + +Utilice el patrón Pipeline cuando desee + +* Ejecutar etapas individuales que produzcan un valor final. +* Añadir legibilidad a secuencias complejas de operaciones proporcionando un constructor fluido como interfaz. +* Mejorar la comprobabilidad del código, ya que las etapas probablemente harán una sola cosa, cumpliendo con el [Principio de Responsabilidad Única (SRP)](https://java-design-patterns.com/principles/#single-responsibility-principle) + +## Usos conocidos + +* [java.util.Stream](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) +* [Maven Build Lifecycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html) +* [Functional Java](https://github.com/functionaljava/functionaljava) + +## Patrones relacionados + +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/) + +## Créditos + +* [The Pipeline Pattern — for fun and profit](https://medium.com/@aaronweatherall/the-pipeline-pattern-for-fun-and-profit-9b5f43a98130) +* [The Pipeline design pattern (in Java)](https://medium.com/@deepakbapat/the-pipeline-design-pattern-in-java-831d9ce2fe21) +* [Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff963548(v=pandp.10)) diff --git a/localization/es/pipeline/etc/pipeline.urm.png b/localization/es/pipeline/etc/pipeline.urm.png new file mode 100644 index 000000000000..4110f1624665 Binary files /dev/null and b/localization/es/pipeline/etc/pipeline.urm.png differ diff --git a/localization/es/poison-pill/README.md b/localization/es/poison-pill/README.md new file mode 100644 index 000000000000..6a71cb6705d1 --- /dev/null +++ b/localization/es/poison-pill/README.md @@ -0,0 +1,217 @@ +--- +title: Poison Pill +shortTitle: Poison Pill +category: Behavioral +language: es +tag: + - Cloud distributed + - Reactive +--- + +## Propósito + +La píldora venenosa es un elemento de datos predefinido conocido que permite proporcionar un cierre graceful para un proceso de consumo distribuido independiente. + +## Explicación + +Ejemplo del mundo real + +> Pensemos en una cola de mensajes con un productor y un consumidor. El productor sigue introduciendo nuevos mensajes en la cola y el consumidor sigue leyéndolos. Finalmente, cuando llega el momento de cerrar la cola, el productor envía el mensaje de píldora venenosa. + +En pocas palabras + +> Poison Pill es una estructura de mensaje conocida que pone fin al intercambio de mensajes. + +**Ejemplo programático** + +Definamos primero la estructura del mensaje. Hay una interfaz `Message` y una implementación `SimpleMessage`. + +```java +public interface Message { + + ... + + enum Headers { + DATE, SENDER + } + + void addHeader(Headers header, String value); + + String getHeader(Headers header); + + Map getHeaders(); + + void setBody(String body); + + String getBody(); +} + +public class SimpleMessage implements Message { + + private final Map headers = new HashMap<>(); + private String body; + + @Override + public void addHeader(Headers header, String value) { + headers.put(header, value); + } + + @Override + public String getHeader(Headers header) { + return headers.get(header); + } + + @Override + public Map getHeaders() { + return Collections.unmodifiableMap(headers); + } + + @Override + public void setBody(String body) { + this.body = body; + } + + @Override + public String getBody() { + return body; + } +} +``` + +Para pasar mensajes utilizamos colas de mensajes. Aquí definimos los tipos relacionados con la cola de mensajes: `MqPublishPoint`, `MqSubscribePoint` y `MessageQueue`. `SimpleMessageQueue` implementa todas estas interfaces. + +```java +public interface MqPublishPoint { + + void put(Message msg) throws InterruptedException; +} + +public interface MqSubscribePoint { + + Message take() throws InterruptedException; +} + +public interface MessageQueue extends MqPublishPoint, MqSubscribePoint { +} + +public class SimpleMessageQueue implements MessageQueue { + + private final BlockingQueue queue; + + public SimpleMessageQueue(int bound) { + queue = new ArrayBlockingQueue<>(bound); + } + + @Override + public void put(Message msg) throws InterruptedException { + queue.put(msg); + } + + @Override + public Message take() throws InterruptedException { + return queue.take(); + } +} +``` + +A continuación necesitamos los mensajes `Producer` y `Consumer`. Internamente utilizan las colas de mensajes de arriba. Es importante notar que cuando `Producer` se detiene, envía la píldora venenosa para informar a `Consumer` que la mensajería ha terminado. + +```java +public class Producer { + + ... + + public void send(String body) { + if (isStopped) { + throw new IllegalStateException(String.format( + "Producer %s was stopped and fail to deliver requested message [%s].", body, name)); + } + var msg = new SimpleMessage(); + msg.addHeader(Headers.DATE, new Date().toString()); + msg.addHeader(Headers.SENDER, name); + msg.setBody(body); + + try { + queue.put(msg); + } catch (InterruptedException e) { + // allow thread to exit + LOGGER.error("Exception caught.", e); + } + } + + public void stop() { + isStopped = true; + try { + queue.put(Message.POISON_PILL); + } catch (InterruptedException e) { + // allow thread to exit + LOGGER.error("Exception caught.", e); + } + } +} + +public class Consumer { + + ... + + public void consume() { + while (true) { + try { + var msg = queue.take(); + if (Message.POISON_PILL.equals(msg)) { + LOGGER.info("Consumer {} receive request to terminate.", name); + break; + } + var sender = msg.getHeader(Headers.SENDER); + var body = msg.getBody(); + LOGGER.info("Message [{}] from [{}] received by [{}]", body, sender, name); + } catch (InterruptedException e) { + // allow thread to exit + LOGGER.error("Exception caught.", e); + return; + } + } + } +} +``` + +Por último, estamos listos para presentar todo el ejemplo en acción. + +```java + var queue = new SimpleMessageQueue(10000); + + final var producer = new Producer("PRODUCER_1", queue); + final var consumer = new Consumer("CONSUMER_1", queue); + + new Thread(consumer::consume).start(); + + new Thread(() -> { + producer.send("hand shake"); + producer.send("some very important information"); + producer.send("bye!"); + producer.stop(); + }).start(); +``` + +Salida del programa: + +``` +Message [hand shake] from [PRODUCER_1] received by [CONSUMER_1] +Message [some very important information] from [PRODUCER_1] received by [CONSUMER_1] +Message [bye!] from [PRODUCER_1] received by [CONSUMER_1] +Consumer CONSUMER_1 receive request to terminate. +``` + +## Diagrama de clases + +![alt text](./etc/poison-pill.png "Poison Pill") + +## Aplicabilidad + +Utiliza la expresión "píldora envenenada" (Poison pill) cuando: + +* Hay una necesidad de enviar una señal de un hilo/proceso a otro para terminar. + +## Ejemplos del mundo real + +* [akka.actor.PoisonPill](http://doc.akka.io/docs/akka/2.1.4/java/untyped-actors.html) diff --git a/localization/es/poison-pill/etc/poison-pill.png b/localization/es/poison-pill/etc/poison-pill.png new file mode 100644 index 000000000000..bfca5848e082 Binary files /dev/null and b/localization/es/poison-pill/etc/poison-pill.png differ diff --git a/localization/es/presentation-model/README.md b/localization/es/presentation-model/README.md new file mode 100644 index 000000000000..fd88a8662f06 --- /dev/null +++ b/localization/es/presentation-model/README.md @@ -0,0 +1,193 @@ +--- +title: Presentation Model +shortTitle: Presentation Model +category: Behavioral +language: es +tag: + - Decoupling +--- + +## También conocido como +Application Model + +## Propósito +El modelo de presentación extrae el estado y el comportamiento de la vista a una clase modelo que forma parte de la presentación. + +## Explicación + +Ejemplo del mundo real + +> Cuando necesitamos escribir un programa con GUI, no es necesario que pongamos todo el comportamiento de la presentación en la clase view. Porque será más difícil de probar. Así que podemos utilizar Presentation Model Pattern para separar el comportamiento y la vista. La vista solo necesita cargar los datos y estados de otra clase y mostrar estos datos en la pantalla de acuerdo a los estados. + +En palabras simples + +> un patrón que divide la presentación y el control. + +Ejemplo de código + +La clase `view` es la GUI de los álbumes. Los métodos `saveToPMod` y `loadFromPMod` se utilizan para lograr la sincronización. + +```java +public class View { + /** + * the model that controls this view. + */ + private final PresentationModel model; + + private TextField txtTitle; + private TextField txtArtist; + private JCheckBox chkClassical; + private TextField txtComposer; + private JList albumList; + private JButton apply; + private JButton cancel; + + public View() { + model = new PresentationModel(PresentationModel.albumDataSet()); + } + + /** + * save the data to PresentationModel. + */ + public void saveToPMod() { + LOGGER.info("Save data to PresentationModel"); + model.setArtist(txtArtist.getText()); + model.setTitle(txtTitle.getText()); + model.setIsClassical(chkClassical.isSelected()); + model.setComposer(txtComposer.getText()); + } + + /** + * load the data from PresentationModel. + */ + public void loadFromPMod() { + LOGGER.info("Load data from PresentationModel"); + txtArtist.setText(model.getArtist()); + txtTitle.setText(model.getTitle()); + chkClassical.setSelected(model.getIsClassical()); + txtComposer.setEditable(model.getIsClassical()); + txtComposer.setText(model.getComposer()); + } + + public void createView() { + // the detail of GUI information like size, listenser and so on. + } +} +``` + +La clase `Album` sirve para almacenar información de un álbum. + +```java +public class Album { + + private String title; + private String artist; + private boolean isClassical; + /** + * only when the album is classical, + * composer can have content. + */ + private String composer; +} + +``` + +La clase `DisplatedAlbums` almacena la información de todos los álbumes que se mostrarán en la GUI. + +```java +public class DisplayedAlbums { + private final List albums; + + public DisplayedAlbums() { + this.albums = new ArrayList<>(); + } + + public void addAlbums(final String title, + final String artist, final boolean isClassical, + final String composer) { + if (isClassical) { + this.albums.add(new Album(title, artist, true, composer)); + } else { + this.albums.add(new Album(title, artist, false, "")); + } + } +} +``` + +Clase `PresentationMod` se utiliza para controlar toda la acción de GUI. + +```java +public class PresentationModel { + private final DisplayedAlbums data; + + private int selectedAlbumNumber; + private Album selectedAlbum; + + public PresentationModel(final DisplayedAlbums dataOfAlbums) { + this.data = dataOfAlbums; + this.selectedAlbumNumber = 1; + this.selectedAlbum = this.data.getAlbums().get(0); + } + + /** + * Changes the value of selectedAlbumNumber. + * + * @param albumNumber the number of album which is shown on the view. + */ + public void setSelectedAlbumNumber(final int albumNumber) { + LOGGER.info("Change select number from {} to {}", + this.selectedAlbumNumber, albumNumber); + this.selectedAlbumNumber = albumNumber; + this.selectedAlbum = data.getAlbums().get(this.selectedAlbumNumber - 1); + } + + public String getTitle() { + return selectedAlbum.getTitle(); + } + // other get methods are like this, which are used to get information of selected album. + + public void setTitle(final String value) { + LOGGER.info("Change album title from {} to {}", + selectedAlbum.getTitle(), value); + selectedAlbum.setTitle(value); + } + // other set methods are like this, which are used to get information of selected album. + + /** + * Gets a list of albums. + * + * @return the names of all the albums. + */ + public String[] getAlbumList() { + var result = new String[data.getAlbums().size()]; + for (var i = 0; i < result.length; i++) { + result[i] = data.getAlbums().get(i).getTitle(); + } + return result; + } +} +``` + +Podemos ejecutar la clase `App` para iniciar esta demo. la casilla de verificación es el álbum clásico; el primer campo de texto es el nombre del artista del álbum; el segundo es el nombre del título del álbum; el último es el nombre del compositor: + +![](./etc/result.png) + + +## Diagrama de clases +![](./etc/presentation-model.urm.png "presentation model") + +## Aplicabilidad +Utilice el patrón de modelo de presentación cuando + +* Probar una presentación a través de una ventana GUI es a menudo incómodo, y en algunos casos imposible. +* No determine qué GUI se utilizará. + +## Patrones relacionados + +- [Supervising Controller](https://martinfowler.com/eaaDev/SupervisingPresenter.html) +- [Passive View](https://martinfowler.com/eaaDev/PassiveScreen.html) + +## Créditos + +* [Presentation Model Patterns](https://martinfowler.com/eaaDev/PresentationModel.html) + diff --git a/localization/es/presentation-model/etc/presentation-model.urm.png b/localization/es/presentation-model/etc/presentation-model.urm.png new file mode 100644 index 000000000000..46690641cb82 Binary files /dev/null and b/localization/es/presentation-model/etc/presentation-model.urm.png differ diff --git a/presentation-model/etc/result.png b/localization/es/presentation-model/etc/result.png similarity index 100% rename from presentation-model/etc/result.png rename to localization/es/presentation-model/etc/result.png diff --git a/priority-queue/README.md b/localization/es/priority-queue/README.md similarity index 58% rename from priority-queue/README.md rename to localization/es/priority-queue/README.md index 28d632a13276..125f2a0abece 100644 --- a/priority-queue/README.md +++ b/localization/es/priority-queue/README.md @@ -1,46 +1,35 @@ --- title: Priority Queue Pattern +shortTitle: Priority Queue Pattern category: Behavioral -language: en +language: es tag: - Decoupling - Cloud distributed --- -## Intent +## Propósito +Priorizar las peticiones enviadas a los servicios de forma que las peticiones con mayor prioridad se reciban y procesen más rápidamente que las de menor prioridad. Este patrón es útil en aplicaciones que ofrecen diferentes garantías de nivel de servicio a clientes individuales. -Prioritize requests sent to services so that requests with a higher priority are received and -processed more quickly than those of a lower priority. This pattern is useful in applications that -offer different service level guarantees to individual clients. +## Explicación -## Explanation +Las aplicaciones pueden delegar tareas específicas en otros servicios; por ejemplo, para realizar procesamientos en segundo plano o para integrarse con otras aplicaciones o servicios. En la nube, se suele utilizar una cola de mensajes para delegar tareas al procesamiento en segundo plano. En muchos casos, el orden en que un servicio recibe las solicitudes no es importante. Sin embargo, en algunos casos puede ser necesario priorizar peticiones específicas. Estas peticiones deben ser procesadas antes que otras de menor prioridad que puedan haber sido enviadas previamente por la aplicación. -Applications may delegate specific tasks to other services; for example, to perform background -processing or to integrate with other applications or services. In the cloud, a message queue is -typically used to delegate tasks to background processing. In many cases the order in which requests -are received by a service is not important. However, in some cases it may be necessary to prioritize -specific requests. These requests should be processed earlier than others of a lower priority that -may have been sent previously by the application. +Ejemplo real -Real world example +> Imagine un servicio de procesamiento de vídeo con clientes gratuitos y premium. Las solicitudes procedentes de los clientes premium de pago deben tener prioridad sobre las demás. -> Imagine a video processing service with free and premium customers. The requests coming from the -> paying premium customers should be prioritized over the others. +En pocas palabras -In plain words +> La cola prioritaria permite procesar primero los mensajes de alta prioridad, independientemente del tamaño de la cola o de la antigüedad del mensaje. -> Priority Queue enables processing of high priority messages first, regardless of queue size or -> message age. +Wikipedia dice -Wikipedia says +> En informática, una cola prioritaria es un tipo de datos abstracto similar a una cola normal o a una estructura de datos de pila en la que cada elemento tiene además una "prioridad" asociada. En una cola de prioridad, un elemento con prioridad alta se sirve antes que un elemento con prioridad baja. -> In computer science, a priority queue is an abstract data type similar to regular queue or stack -> data structure in which each element additionally has a "priority" associated with it. In a -> priority queue, an element with high priority is served before an element with low priority. +**Ejemplo programático** -**Programmatic Example** - -Looking at the video processing example from above, let's first see the `Message` structure. +Si observamos el ejemplo anterior de procesamiento de vídeo, veamos primero la estructura `Message`. ```java public class Message implements Comparable { @@ -61,8 +50,7 @@ public class Message implements Comparable { } ``` -Here's `PriorityMessageQueue` that handles storing the messages and serving them in priority -order. +Aquí está `PriorityMessageQueue` que se encarga de almacenar los mensajes y servirlos en orden de prioridad. ```java public class PriorityMessageQueue { @@ -92,8 +80,7 @@ public class PriorityMessageQueue { } ``` -`QueueManager` has a `PriorityMessageQueue` and makes it easy to `publishMessage` and -`receiveMessage`. +El `QueueManager` tiene una `PriorityMessageQueue` y facilita la publicación de mensajes `publishMessage` y la recepción de mensajes `receiveMessage`. ```java public class QueueManager { @@ -117,7 +104,7 @@ public class QueueManager { } ``` -`Worker` constantly polls `QueueManager` for highest priority message and processes it. +El `Worker` sondea constantemente el `QueueManager` en busca del mensaje de mayor prioridad y lo procesa. ```java @Slf4j @@ -147,8 +134,7 @@ public class Worker { } ``` -Here's the full example how we create an instance of `QueueManager` and process messages using -`Worker`. +Aquí está el ejemplo completo de cómo creamos una instancia de `QueueManager` y procesamos mensajes usando `Worker`. ```java var queueManager = new QueueManager(100); @@ -165,7 +151,7 @@ Here's the full example how we create an instance of `QueueManager` and process worker.run(); ``` -Program output: +Salida del programa: ``` Message{message='High Message Priority', priority=1} @@ -194,17 +180,17 @@ No Message ... waiting ``` -## Class diagram +## Diagrama de clases ![alt text](./etc/priority-queue.urm.png "Priority Queue pattern class diagram") -## Applicability +## Aplicabilidad -Use the Priority Queue pattern when: +Utilice el patrón de Cola Prioritaria cuando: -* The system must handle multiple tasks that might have different priorities. -* Different users or tenants should be served with different priority. +* El sistema debe manejar múltiples tareas que pueden tener diferentes prioridades. +* Diferentes usuarios o inquilinos deben ser atendidos con diferente prioridad. -## Credits +## Créditos * [Priority Queue pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/priority-queue) diff --git a/priority-queue/etc/priority-queue.urm.png b/localization/es/priority-queue/etc/priority-queue.urm.png similarity index 100% rename from priority-queue/etc/priority-queue.urm.png rename to localization/es/priority-queue/etc/priority-queue.urm.png diff --git a/localization/es/property/README.md b/localization/es/property/README.md index 8ae41822312b..9c0d461d5599 100644 --- a/localization/es/property/README.md +++ b/localization/es/property/README.md @@ -1,5 +1,6 @@ --- title: Property +shortTitle: Property category: Creational language: es tag: diff --git a/localization/es/prototype/README.md b/localization/es/prototype/README.md index 10e8ade75526..c5d11d290601 100644 --- a/localization/es/prototype/README.md +++ b/localization/es/prototype/README.md @@ -1,5 +1,6 @@ --- title: Prototype +shortTitle: Prototype category: Creational language: es tag: diff --git a/localization/es/proxy/README.md b/localization/es/proxy/README.md new file mode 100644 index 000000000000..1435a3732432 --- /dev/null +++ b/localization/es/proxy/README.md @@ -0,0 +1,162 @@ +--- +title: Proxy +shortTitle: Proxy +category: Structural +language: es +tag: + - Gang Of Four + - Decoupling +--- + +## También conocido como + +Surrogate + +## Propósito + +Proporcionar un sustituto o marcador de posición de otro objeto para controlar el acceso al mismo. + +## Explicación + +Ejemplo real + +> Imagina una torre donde los magos locales van a estudiar sus hechizos. A la torre de marfil sólo se puede acceder a través de un proxy que asegura que sólo los tres primeros magos pueden entrar. Aquí el proxy representa la funcionalidad de la torre y le añade control de acceso. + +En palabras sencillas + +> Usando el patrón proxy, una clase representa la funcionalidad de otra clase. + +Wikipedia dice + +> Un proxy, en su forma más general, es una clase que funciona como interfaz de otra cosa. Un proxy es una envoltura o un objeto agente que está siendo llamado por el cliente para acceder al objeto real de servicio detrás de las escenas. El uso del proxy puede ser simplemente el reenvío al objeto real, o puede proporcionar lógica adicional. En el proxy se puede proporcionar funcionalidad adicional, por ejemplo, almacenamiento en caché cuando las operaciones en el objeto real consumen muchos recursos, o comprobación de condiciones previas antes de invocar operaciones en el objeto real. + +**Ejemplo programático** + +Tomando el ejemplo anterior de nuestra torre de asistentes. En primer lugar tenemos la interfaz `WizardTower` y la clase `IvoryTower`. + +```java +public interface WizardTower { + + void enter(Wizard wizard); +} + +@Slf4j +public class IvoryTower implements WizardTower { + + public void enter(Wizard wizard) { + LOGGER.info("{} enters the tower.", wizard); + } + +} +``` + +A continuación, una simple clase `Wizard`. + +```java +public class Wizard { + + private final String name; + + public Wizard(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} +``` + +Luego tenemos el `WizardTowerProxy` para añadir control de acceso a `WizardTower`. + +```java +@Slf4j +public class WizardTowerProxy implements WizardTower { + + private static final int NUM_WIZARDS_ALLOWED = 3; + + private int numWizards; + + private final WizardTower tower; + + public WizardTowerProxy(WizardTower tower) { + this.tower = tower; + } + + @Override + public void enter(Wizard wizard) { + if (numWizards < NUM_WIZARDS_ALLOWED) { + tower.enter(wizard); + numWizards++; + } else { + LOGGER.info("{} is not allowed to enter!", wizard); + } + } +} +``` + +Y aquí está el escenario de entrada a la torre. + +```java +var proxy = new WizardTowerProxy(new IvoryTower()); +proxy.enter(new Wizard("Red wizard")); +proxy.enter(new Wizard("White wizard")); +proxy.enter(new Wizard("Black wizard")); +proxy.enter(new Wizard("Green wizard")); +proxy.enter(new Wizard("Brown wizard")); +``` + +Salida del programa: + +``` +Red wizard enters the tower. +White wizard enters the tower. +Black wizard enters the tower. +Green wizard is not allowed to enter! +Brown wizard is not allowed to enter! +``` + +## Diagrama de clases + +![alt text](./etc/proxy.urm.png "Proxy pattern class diagram") + +## Aplicabilidad + +El proxy es aplicable siempre que se necesite una referencia a un objeto más versátil o sofisticada que un simple puntero. +que un simple puntero. He aquí varias situaciones comunes en las que el patrón Proxy es +aplicable. + +* Un proxy remoto proporciona un representante local para un objeto en un espacio de direcciones diferente. +* El proxy virtual crea objetos caros bajo demanda. +* Un proxy de protección controla el acceso al objeto original. Los proxies de protección son útiles cuando + objetos deben tener diferentes derechos de acceso. + +Típicamente, el patrón proxy se utiliza para + +* Controlar el acceso a otro objeto +* Inicialización perezosa +* Implementar el registro +* Facilitar la conexión de red +* Contar referencias a un objeto + +## Tutoriales + +* [Controlling Access With Proxy Pattern](http://java-design-patterns.com/blog/controlling-access-with-proxy-pattern/) + +## Usos conocidos + +* [java.lang.reflect.Proxy](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html) +* [Apache Commons Proxy](https://commons.apache.org/proper/commons-proxy/) +* Mocking frameworks [Mockito](https://site.mockito.org/), +[Powermock](https://powermock.github.io/), [EasyMock](https://easymock.org/) +* [UIAppearance](https://developer.apple.com/documentation/uikit/uiappearance) + +## Patrones relacionados + +* [Ambassador](https://java-design-patterns.com/patterns/ambassador/) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/es/proxy/etc/proxy.urm.png b/localization/es/proxy/etc/proxy.urm.png new file mode 100644 index 000000000000..a0c94fc7c717 Binary files /dev/null and b/localization/es/proxy/etc/proxy.urm.png differ diff --git a/localization/es/registry/README.md b/localization/es/registry/README.md index c662efbff191..2af1eb143d1b 100644 --- a/localization/es/registry/README.md +++ b/localization/es/registry/README.md @@ -1,5 +1,6 @@ --- title: Registry +shortTitle: Registry category: Creational language: es tag: diff --git a/localization/es/retry/README.md b/localization/es/retry/README.md new file mode 100644 index 000000000000..ddb4b39d1175 --- /dev/null +++ b/localization/es/retry/README.md @@ -0,0 +1,121 @@ +--- +title: Retry +shortTitle: Retry +category: Behavioral +language: es +tag: + - Performance + - Cloud distributed +--- + +## Propósito + +Reintentar de forma transparente determinadas operaciones que implican la comunicación con recursos externos, en particular a través de la red, aislando el código de llamada de los detalles de implementación del reintento. + +## Explicación + +El patrón de reintento consiste en reintentar operaciones sobre recursos remotos a través de la red un número determinado de veces. Depende estrechamente de los requisitos empresariales y técnicos: ¿Cuánto tiempo permitirá la empresa que espere el usuario final hasta que finalice la operación? ¿Cuáles son las características de rendimiento del recurso remoto durante los picos de carga, así como de nuestra aplicación a medida que más hilos esperan la disponibilidad del recurso remoto? Entre los errores devueltos por el servicio remoto, ¿cuáles pueden ignorarse con seguridad para volver a intentarlo? ¿Es la operación [idempotent](https://en.wikipedia.org/wiki/Idempotence)? + +Otra preocupación es el impacto en el código de llamada al implementar el mecanismo de reintento. Idealmente, la mecánica de reintento debería ser completamente transparente para el código de llamada (la interfaz del servicio permanece inalterada). Existen dos enfoques generales para este problema: desde el punto de vista de la arquitectura empresarial (estratégico) y desde el punto de vista de la biblioteca compartida (táctico). + +Desde un punto de vista estratégico, esto se resolvería redirigiendo las peticiones a un sistema intermediario separado, tradicionalmente un [ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), pero más recientemente un [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a). + +Desde un punto de vista táctico, esto se resolvería reutilizando bibliotecas compartidas como [Hystrix](https://github.com/Netflix/Hystrix) (nótese que Hystrix es una implementación completa del patrón [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/), del que el patrón Retry puede considerarse un subconjunto). Este es el tipo de solución que se muestra en el sencillo ejemplo que acompaña a este `README.md`. + +Ejemplo real + +> Nuestra aplicación utiliza un servicio que proporciona información sobre clientes. De vez en cuando el servicio parece fallar y puede devolver errores o a veces simplemente se desconecta. Para evitar estos problemas aplicamos el patrón retry. + +En palabras simples + +> El patrón de reintento reintenta de forma transparente las operaciones fallidas a través de la red. + +[Documentación de Microsoft](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry) dice + +> Permite a una aplicación manejar fallos transitorios cuando intenta conectarse a un servicio o recurso de red, reintentando de forma transparente una operación fallida. Esto puede mejorar la estabilidad de la aplicación. + +**Ejemplo programático** + +En nuestra aplicación hipotética, tenemos una interfaz genérica para todas las operaciones sobre interfaces remotas. + +```java +public interface BusinessOperation { + T perform() throws BusinessException; +} +``` + +Y tenemos una implementación de esta interfaz que encuentra a nuestros clientes buscando en una base de datos. + +```java +public final class FindCustomer implements BusinessOperation { + @Override + public String perform() throws BusinessException { + ... + } +} +``` + +Nuestra implementación de `FindCustomer` puede configurarse para lanzar `BusinessException`s antes de devolver el ID del cliente, simulando así un servicio defectuoso que falla intermitentemente. Algunas excepciones, como la `CustomerNotFoundException`, se consideran recuperables tras un hipotético análisis porque la causa raíz del error proviene de "algún problema de bloqueo de la base de datos". Sin embargo, la `DatabaseNotAvailableException` se considera definitivamente un showtopper - la aplicación no debe intentar recuperarse de este error. + +Podemos modelar un escenario recuperable instanciando `FindCustomer` así: + +```java +final var op = new FindCustomer( + "12345", + new CustomerNotFoundException("not found"), + new CustomerNotFoundException("still not found"), + new CustomerNotFoundException("don't give up yet!") +); +``` + +En esta configuración, `FindCustomer` lanzará `CustomerNotFoundException` tres veces, tras lo cual devolverá sistemáticamente el ID del cliente (`12345`). + +En nuestro escenario hipotético, nuestros analistas indican que esta operación suele fallar entre 2 y 4 veces para una entrada determinada durante las horas punta, y que cada hilo de trabajo del subsistema de base de datos suele necesitar 50 ms para "recuperarse de un error". Aplicando estas políticas se obtendría algo así: + +```java +final var op = new Retry<>( + new FindCustomer( + "1235", + new CustomerNotFoundException("not found"), + new CustomerNotFoundException("still not found"), + new CustomerNotFoundException("don't give up yet!") + ), + 5, + 100, + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) +); +``` + +Ejecutando `op` una vez se lanzarían automáticamente como máximo 5 intentos de reintento, con un retardo de 100 milisegundos entre intentos, ignorando cualquier `CustomerNotFoundException` lanzada durante el intento. En este escenario en particular, debido a la configuración de `FindCustomer`, habrá 1 intento inicial y 3 reintentos adicionales antes de devolver finalmente el resultado deseado `12345`. + +Si nuestra operación `FindCustomer` lanzara una fatal `DatabaseNotFoundException`, la cual se nos instruyó no ignorar, pero más importante aún, no instruimos a nuestro `Retry` ignorar, entonces la operación habría fallado inmediatamente al recibir el error, sin importar cuantos intentos quedaran. + +## Diagrama de clases + +![alt text](./etc/retry.png "Retry") + +## Aplicabilidad + +Siempre que una aplicación necesite comunicarse con un recurso externo, especialmente en un entorno de nube, y si los requisitos empresariales lo permiten. + +## Consecuencias + +**Pros:** + +* Resistencia +* Proporciona datos concretos sobre fallos externos + +**Desventajas** + +* Complejidad +* Mantenimiento de operaciones + +## Patrones relacionados + + +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) + +## Créditos + +* [Retry pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry) +* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications](https://www.amazon.com/gp/product/1621140369/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1621140369&linkId=3e3f686af5e60a7a453b48adb286797b) diff --git a/localization/es/retry/etc/retry.png b/localization/es/retry/etc/retry.png new file mode 100644 index 000000000000..3ef6d3800ee5 Binary files /dev/null and b/localization/es/retry/etc/retry.png differ diff --git a/localization/es/role-object/README.md b/localization/es/role-object/README.md new file mode 100644 index 000000000000..81f1f85a6410 --- /dev/null +++ b/localization/es/role-object/README.md @@ -0,0 +1,33 @@ +--- +title: Role Object +shortTitle: Role Object +category: Structural +language: es +tag: + - Extensibility +--- + +## También conocido como +Patrón Post, Patrón Extension Object + +## Propósito +Adaptar un objeto a las necesidades de diferentes clientes mediante objetos de rol adjuntos de forma transparente, cada uno de los cuales representa un papel +que el objeto debe desempeñar en el contexto de ese cliente. El objeto gestiona su conjunto de roles de forma dinámica. Al representar los roles como +objetos individuales, los distintos contextos se mantienen separados y se simplifica la configuración del sistema. + +## Diagrama de clases +![alt text](./etc/role-object.urm.png "Role Object pattern class diagram") + +## Aplicabilidad +Utiliza el patrón Objeto Rol, si: + +- Quieres manejar una abstracción clave en diferentes contextos y no quieres poner las interfaces específicas de contexto resultantes en la misma interfaz de clase. +- Quieres manejar los roles disponibles dinámicamente para que puedan ser adjuntados y removidos bajo demanda, es decir en tiempo de ejecución, en lugar de fijarlos estáticamente en tiempo de compilación. +- Quiere tratar las extensiones de forma transparente y necesita preservar la identidad lógica del objeto del conglomerado de objetos resultante. +- Desea mantener los pares rol/cliente independientes entre sí, de modo que los cambios en un rol no afecten a los clientes que no estén interesados en ese rol. + +## Créditos + +- [Hillside - Role object pattern](https://hillside.net/plop/plop97/Proceedings/riehle.pdf) +- [Role object](http://wiki.c2.com/?RoleObject) +- [Fowler - Dealing with roles](https://martinfowler.com/apsupp/roles.pdf) diff --git a/localization/es/role-object/etc/role-object.urm.png b/localization/es/role-object/etc/role-object.urm.png new file mode 100644 index 000000000000..65201c68aff4 Binary files /dev/null and b/localization/es/role-object/etc/role-object.urm.png differ diff --git a/localization/es/separated-interface/README.md b/localization/es/separated-interface/README.md new file mode 100644 index 000000000000..ca692c766da0 --- /dev/null +++ b/localization/es/separated-interface/README.md @@ -0,0 +1,134 @@ +--- +title: Separated Interface +shortTitle: Separated Interface +category: Structural +language: es +tag: + - Decoupling +--- + + +## Propósito + +Separe la definición de la interfaz y su implementación en paquetes diferentes. Esto permite al cliente desconozca por completo la implementación. + +## Explicación + +Ejemplo del mundo real + +> Se puede crear un generador de facturas con capacidad para utilizar diferentes calculadoras de impuestos que se pueden añadir en la factura en función del tipo de compra, región, etc. + +En pocas palabras + +> El patrón de interfaz separada anima a mantener las implementaciones de una interfaz desacopladas del cliente y su definición, para que el cliente no dependa de la implementación. + +Un código cliente puede abstraer algunas funcionalidades específicas a una interfaz, y definir la definición de +la interfaz como una SPI ([Service Programming Interface](https://en.wikipedia.org/wiki/Service_provider_interface) +es una API pensada y abierta para ser implementada o ampliada por terceros). Otro paquete puede +implementar esta definición de interfaz con una lógica concreta, que se inyectará en el código del cliente en tiempo de ejecución (con un tercero). +cliente en tiempo de ejecución (con una tercera clase, inyectando la implementación en el cliente) o en tiempo de compilación +(utilizando el patrón Plugin con algún archivo configurable). + +**Ejemplo programático** + +**Cliente** + +La clase `InvoiceGenerator` acepta el coste del producto y calcula el importe total a pagar, impuestos incluidos. +total a pagar, impuestos incluidos. + +```java +public class InvoiceGenerator { + + private final TaxCalculator taxCalculator; + + private final double amount; + + public InvoiceGenerator(double amount, TaxCalculator taxCalculator) { + this.amount = amount; + this.taxCalculator = taxCalculator; + } + + public double getAmountWithTax() { + return amount + taxCalculator.calculate(amount); + } + +} +``` + +La lógica de cálculo de impuestos se delega en la interfaz `TaxCalculator`. + +```java +public interface TaxCalculator { + + double calculate(double amount); + +} +``` + +**Paquete de aplicación** + +En otro paquete (que el cliente desconoce por completo) existen múltiples implementaciones +de la interfaz `TaxCalculator`. Una de ellas es `ForeignTaxCalculator`, que aplica un impuesto del 60 +para los productos internacionales. + +```java +public class ForeignTaxCalculator implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 60; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} +``` + +Otra es `DomesticTaxCalculator`, que grava con un 20% los productos internacionales. + +```java +public class DomesticTaxCalculator implements TaxCalculator { + + public static final double TAX_PERCENTAGE = 20; + + @Override + public double calculate(double amount) { + return amount * TAX_PERCENTAGE / 100.0; + } + +} +``` + +Estas dos implementaciones son instanciadas e inyectadas en la clase cliente por la clase `App.java`. +en la clase cliente. + +```java + var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator()); + + LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax()); + + var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator()); + + LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax()); +``` + +## Diagrama de clases + +![alt text](./etc/class_diagram.png "Separated Interface") + +## Aplicabilidad + +Utilice el patrón de interfaz separada cuando + +* Estás desarrollando un paquete de framework, y tu framework necesita llamar a algún código de aplicación a través de interfaces. +* Tienes paquetes separados que implementan las funcionalidades que pueden ser conectadas a tu código cliente en tiempo de ejecución o de compilación. +* Su código reside en una capa a la que no se le permite llamar a la capa de implementación de la interfaz por norma. Por ejemplo, una capa de dominio necesita llamar a un mapeador de datos. + +## Tutoriales + +* [Separated Interface Tutorial](https://www.youtube.com/watch?v=d3k-hOA7k2Y) + +## Créditos + +* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=e08dfb7f2cf6153542ef1b5a00b10abc) diff --git a/localization/es/separated-interface/etc/class_diagram.png b/localization/es/separated-interface/etc/class_diagram.png new file mode 100644 index 000000000000..10d509801f79 Binary files /dev/null and b/localization/es/separated-interface/etc/class_diagram.png differ diff --git a/localization/es/servant/README.md b/localization/es/servant/README.md new file mode 100644 index 000000000000..76b4026a28c0 --- /dev/null +++ b/localization/es/servant/README.md @@ -0,0 +1,236 @@ +--- +title: Servant +shortTitle: Servant +category: Behavioral +language: es +tag: +- Decoupling +--- + +## Propósito +Servant se utiliza para proporcionar algún comportamiento a un grupo de clases. +En lugar de definir ese comportamiento en cada clase - o cuando no podemos factorizar +este comportamiento en la clase padre común - se define una vez en el Servant. + +## Explicación + +Ejemplo del mundo real + +> El Rey, la Reina y otros miembros reales de palacio necesitan sirvientes que les den de comer, +> organizar bebidas, etc. + +En otras palabras + +> Garantiza que un objeto servidor preste determinados servicios a un grupo de clases atendidas. + +Wikipedia dice + +> En ingeniería de software, el patrón Servant define un objeto utilizado para ofrecer alguna funcionalidad +> a un grupo de clases sin definir esa funcionalidad en cada una de ellas. Un Servant es una clase +> cuya instancia (o incluso sólo clase) proporciona métodos que se encargan de un servicio deseado, mientras que +> los objetos para los que (o con los que) el Servant hace algo, se toman como parámetros. + +**Ejemplo programático** + +La clase Servant puede prestar servicios a otros miembros reales de palacio. + +```java +/** + * Servant. + */ +public class Servant { + + public String name; + + /** + * Constructor. + */ + public Servant(String name) { + this.name = name; + } + + public void feed(Royalty r) { + r.getFed(); + } + + public void giveWine(Royalty r) { + r.getDrink(); + } + + public void giveCompliments(Royalty r) { + r.receiveCompliments(); + } + + /** + * Check if we will be hanged. + */ + public boolean checkIfYouWillBeHanged(List tableGuests) { + return tableGuests.stream().allMatch(Royalty::getMood); + } +} +``` + +Royalty es una interfaz. Es implementada por las clases King, y Queen para obtener servicios de servant. + +```java +interface Royalty { + + void getFed(); + + void getDrink(); + + void changeMood(); + + void receiveCompliments(); + + boolean getMood(); +} +``` +La clase King implementa la interfaz Royalty. +```java +public class King implements Royalty { + + private boolean isDrunk; + private boolean isHungry = true; + private boolean isHappy; + private boolean complimentReceived; + + @Override + public void getFed() { + isHungry = false; + } + + @Override + public void getDrink() { + isDrunk = true; + } + + public void receiveCompliments() { + complimentReceived = true; + } + + @Override + public void changeMood() { + if (!isHungry && isDrunk) { + isHappy = true; + } + if (complimentReceived) { + isHappy = false; + } + } + + @Override + public boolean getMood() { + return isHappy; + } +} +``` +La clase Queen implementa la interfaz Royalty. +```java +public class Queen implements Royalty { + + private boolean isDrunk = true; + private boolean isHungry; + private boolean isHappy; + private boolean isFlirty = true; + private boolean complimentReceived; + + @Override + public void getFed() { + isHungry = false; + } + + @Override + public void getDrink() { + isDrunk = true; + } + + public void receiveCompliments() { + complimentReceived = true; + } + + @Override + public void changeMood() { + if (complimentReceived && isFlirty && isDrunk && !isHungry) { + isHappy = true; + } + } + + @Override + public boolean getMood() { + return isHappy; + } + + public void setFlirtiness(boolean f) { + this.isFlirty = f; + } + +} +``` + +Luego: + +```java +public class App { + + private static final Servant jenkins = new Servant("Jenkins"); + private static final Servant travis = new Servant("Travis"); + + /** + * Program entry point. + */ + public static void main(String[] args) { + scenario(jenkins, 1); + scenario(travis, 0); + } + + /** + * Can add a List with enum Actions for variable scenarios. + */ + public static void scenario(Servant servant, int compliment) { + var k = new King(); + var q = new Queen(); + + var guests = List.of(k, q); + + // feed + servant.feed(k); + servant.feed(q); + // serve drinks + servant.giveWine(k); + servant.giveWine(q); + // compliment + servant.giveCompliments(guests.get(compliment)); + + // outcome of the night + guests.forEach(Royalty::changeMood); + + // check your luck + if (servant.checkIfYouWillBeHanged(guests)) { + LOGGER.info("{} will live another day", servant.name); + } else { + LOGGER.info("Poor {}. His days are numbered", servant.name); + } + } +} +``` + +La salida de la consola + +``` +Jenkins will live another day +Poor Travis. His days are numbered +``` + + +## Diagrama de clases +![alt text](./etc/servant-pattern.png "Servant") + +## Aplicabilidad +Utiliza el patrón Servant cuando + +* Cuando queremos que algunos objetos realicen una acción común y no queremos definir esta acción como un método en cada clase. + +## Créditos + +* [Let's Modify the Objects-First Approach into Design-Patterns-First](http://edu.pecinovsky.cz/papers/2006_ITiCSE_Design_Patterns_First.pdf) diff --git a/localization/es/servant/etc/servant-pattern.png b/localization/es/servant/etc/servant-pattern.png new file mode 100644 index 000000000000..a8237775e332 Binary files /dev/null and b/localization/es/servant/etc/servant-pattern.png differ diff --git a/localization/es/sharding/README.md b/localization/es/sharding/README.md new file mode 100644 index 000000000000..7a7235f6418c --- /dev/null +++ b/localization/es/sharding/README.md @@ -0,0 +1,28 @@ +--- +title: Sharding +shortTitle: Sharding +category: Behavioral +language: es +tag: + - Performance + - Cloud distributed +--- + +## Propósito +El patrón sharding significa dividir el almacén de datos en particiones horizontales o shards. Cada fragmento tiene el mismo esquema, pero contiene su propio subconjunto de datos. +Un fragmento es un almacén de datos en sí mismo (puede contener los datos de muchas entidades de diferentes tipos), que se ejecuta en un servidor que actúa como nodo de almacenamiento. + +## Diagrama de clases +![alt text](./etc/sharding.urm.png "Sharding pattern class diagram") + +## Aplicabilidad +Este patrón ofrece las siguientes ventajas: + +- Se puede ampliar el sistema añadiendo más fragmentos que se ejecuten en nodos de almacenamiento adicionales. +- El sistema puede utilizar hardware comercial en lugar de ordenadores especializados (y caros) para cada nodo de almacenamiento. +- Se puede reducir la contención y mejorar el rendimiento equilibrando la carga de trabajo entre los shards. +- En la nube, los shards pueden ubicarse físicamente cerca de los usuarios que accederán a los datos. + +## Créditos + +* [Sharding pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/sharding) \ No newline at end of file diff --git a/localization/es/sharding/etc/sharding.urm.png b/localization/es/sharding/etc/sharding.urm.png new file mode 100644 index 000000000000..e7f412af3f01 Binary files /dev/null and b/localization/es/sharding/etc/sharding.urm.png differ diff --git a/localization/es/single-table-inheritance/README.md b/localization/es/single-table-inheritance/README.md new file mode 100644 index 000000000000..7e9ea9f60c1b --- /dev/null +++ b/localization/es/single-table-inheritance/README.md @@ -0,0 +1,147 @@ +--- +title: Single Table Inheritance Pattern +shortTitle: Single Table Inheritance Pattern +category: Structural +language: es +tag: + - Data access +--- + +## Single Table Inheritance(STI) + +## Propósito + +Representa una jerarquía hereditaria de clases como una única tabla que tiene columnas para todos los campos de las distintas clases. + +## Explicación + +Un ejemplo real + +> Puede haber muchos tipos diferentes de vehículos en este mundo pero todos ellos se engloban bajo el único paraguas de Vehículo + +En palabras sencillas + +> Mapea cada instancia de clase de un árbol de herencia en una única tabla. + +Wikipedia dice + +> La herencia de tabla única es una forma de emular la herencia orientada a objetos en una base de datos relacional. Al mapear desde una tabla de la base de datos a un objeto en un lenguaje orientado a objetos, un campo de la base de datos identifica a qué clase de la jerarquía pertenece el objeto. Todos los campos de todas las clases se almacenan en la misma tabla, de ahí el nombre de "herencia de tabla única". + +**Ejemplo programático** + +Baeldung - Herencia de Hibernate + +> Podemos definir la estrategia que queremos utilizar añadiendo la anotación @Inheritance a la superclase: + +```java +@Entity +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +public class MyProduct { + @Id + private long productId; + private String name; + + // constructor, getters, setters +} +``` + +El identificador de las entidades también se define en la superclase. + +A continuación, podemos añadir las entidades de la subclase: + +```java +@Entity +public class Book extends MyProduct { + private String author; +} +``` + +```java +@Entity +public class Pen extends MyProduct { + private String color; +} +``` +Valores del discriminador + +- Dado que los registros de todas las entidades estarán en la misma tabla, Hibernate necesita una forma de diferenciarlos. + +- Por defecto, esto se hace a través de una columna discriminadora llamada DTYPE que tiene como valor el nombre de la entidad. + +- Para personalizar la columna discriminadora, podemos utilizar la anotación @DiscriminatorColumn: + +```java +@Entity(name="products") +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name="product_type", + discriminatorType = DiscriminatorType.INTEGER) +public class MyProduct { + // ... +} +``` +- Aquí hemos optado por diferenciar las entidades de la subclase MyProduct mediante una columna entera llamada product_type. + +- A continuación, tenemos que indicar a Hibernate qué valor tendrá cada registro de subclase para la columna product_type: + +```java +@Entity +@DiscriminatorValue("1") +public class Book extends MyProduct { + // ... +} +``` +```java +@Entity +@DiscriminatorValue("2") +public class Pen extends MyProduct { + // ... +} +``` + +- Hibernate añade otros dos valores predefinidos que puede tomar la anotación: null y not null: + + - @DiscriminatorValue("null") significa que cualquier fila sin valor discriminador se asignará a la clase de entidad con esta anotación; esto puede aplicarse a la clase raíz de la jerarquía. + - @DiscriminatorValue("not null"): cualquier fila con un valor discriminador que no coincida con ninguno de los asociados a las definiciones de entidad se asignará a la clase con esta anotación. + +## Diagrama de clases + +![alt text](./etc/single-table-inheritance.urm.png "Singleton pattern class diagram") + +## Aplicabilidad + +Utilice el patrón Singleton cuando + +## Use STI Cuando Las Subclases Tienen Los Mismos Campos/Columnas Pero Diferente Comportamiento +- Una buena indicación de que STI es correcto es cuando las diferentes subclases tienen los mismos campos/columnas pero diferentes métodos. En el ejemplo de cuentas anterior, esperamos que todas las columnas de la base de datos sean utilizadas por cada subclase. De lo contrario, habrá muchas columnas nulas en la base de datos. +

+* Utilizar STI cuando esperamos realizar consultas en todas las subclases + - Otra buena indicación de que STI es correcto es si esperamos realizar consultas a través de todas las clases. Por ejemplo, si queremos encontrar las 10 cuentas con los saldos más altos en todas las clases, STI nos permite utilizar sólo una consulta, mientras que MTI requerirá manipulación en memoria. + +### Tutoriales + +- Cerebros de Java - Herencia de tabla única + +## Consecuencias + +* Los campos a veces son relevantes y a veces no, lo que puede confundir a la gente que usa las tablas directamente. +* Las columnas utilizadas sólo por algunas subclases hacen que se desperdicie espacio en la base de datos. + Hasta qué punto esto es realmente un problema depende de las características + específicas de los datos y de lo bien que la base de datos comprima las columnas vacías. + Oracle, por ejemplo, es muy eficiente en el recorte de espacio desperdiciado, particularmente columnas opcionales a la derecha de la tabla de la base de datos. + Cada base de datos tiene sus propios trucos para esto. +* La tabla única puede acabar siendo demasiado grande, con muchos índices y frecuentes, lo que puede perjudicar el rendimiento. Puede evitar esto teniendo + tablas de índices separadas que o bien enumeran claves de filas que tienen una determinada propiedad o que copien un subconjunto de campos relevantes para un índice. +* Sólo se dispone de un único espacio de nombres para los campos, por lo que hay que asegurarse de que + de no utilizar el mismo nombre para diferentes campos. Los nombres compuestos con el nombre de la clase como prefijo o sufijo. + +## Patrones relacionados + +* MappedSuperclass +* Single Table +* Joined Table +* Table per Class + +## Créditos + +* [Single Table Inheritance - martinFowler.com](https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html) +* [Patterns of Enterprise Application Architecture](https://books.google.co.in/books?id=vqTfNFDzzdIC&pg=PA278&redir_esc=y#v=onepage&q&f=false) diff --git a/localization/es/single-table-inheritance/etc/single-table-inheritance.urm.png b/localization/es/single-table-inheritance/etc/single-table-inheritance.urm.png new file mode 100644 index 000000000000..f3abe8df6fec Binary files /dev/null and b/localization/es/single-table-inheritance/etc/single-table-inheritance.urm.png differ diff --git a/localization/es/singleton/README.md b/localization/es/singleton/README.md index bf4f04b0b111..0ef6c558d64b 100644 --- a/localization/es/singleton/README.md +++ b/localization/es/singleton/README.md @@ -1,5 +1,6 @@ --- title: Singleton +shortTitle: Singleton category: Creational language: es tag: diff --git a/localization/es/spatial-partition/README.md b/localization/es/spatial-partition/README.md new file mode 100644 index 000000000000..85fcb1dda1ce --- /dev/null +++ b/localization/es/spatial-partition/README.md @@ -0,0 +1,74 @@ +--- +title: Spatial Partition +shortTitle: Spatial Partition +category: Behavioral +language: es +tag: + - Performance + - Game programming +--- + +## Propósito + +Como se explica en el libro [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html) +por Bob Nystrom, spatial partition pattern ayuda a localizar eficazmente los objetos almacenándolos en una +estructura de datos organizada por sus posiciones. + +## Explicación + +Digamos que usted está construyendo un juego de guerra con cientos, o incluso miles de jugadores, que se enfrentan en el campo de batalla. +La posición de cada jugador se actualiza en cada fotograma. La forma más sencilla de manejar +todas las interacciones que tienen lugar en el campo es comprobar la posición de cada jugador con la de todos los demás. +posición de cada jugador: + +```java +public void handleMeLee(Unit units[], int numUnits) { + for (var a = 0; a < numUnits - 1; a++) + { + for (var b = a + 1; b < numUnits; b++) + { + if (units[a].position() == units[b].position()) + { + handleAttack(units[a], units[b]); + } + } + } +} +``` + +Esto incluirá un montón de controles innecesarios entre jugadores que están demasiado alejados como para tener alguna +influencia entre ellos. Los bucles anidados dan a esta operación una complejidad O(n^2), que tiene que ser +cada fotograma, ya que muchos de los objetos del campo pueden moverse en cada fotograma. La idea +detrás del patrón de diseño de Partición Espacial es permitir una rápida localización de los objetos utilizando una estructura de datos que está organizada por sus posiciones. +que está organizada por sus posiciones, de modo que cuando se realiza una operación como la anterior, +no es necesario cotejar la posición de cada objeto con la de todos los demás. La estructura de datos +puede utilizarse para almacenar objetos en movimiento y estáticos, aunque para seguir la pista de los objetos en movimiento, +sus posiciones tendrán que restablecerse cada vez que se muevan. Esto significaría tener que crear una nueva +instancia de la estructura de datos cada vez que un objeto se mueva, lo que consumiría memoria adicional. La dirección +estructuras de datos comunes utilizadas para este patrón de diseño son: + +* Grid +* Árbol Quad +* Árbol K-d +* BSP +* Jerarquía de volúmenes límite + +En nuestra implementación, utilizamos la estructura de datos Quadtree que reducirá la complejidad temporal de +O(n^2) a O(nlogn), lo que reduce significativamente los cálculos necesarios en el caso de un gran número de objetos. +significativamente en el caso de un gran número de objetos. + +## Diagrama de clases + +![alt text](./etc/spatial-partition.urm.png "Spatial Partition pattern class diagram") + +## Aplicabilidad + +Utilice el patrón Spatial Partition cuando: + +* Cuando se necesita mantener un registro de un gran número de posiciones de objetos, que se actualizan cada fotograma. +* Cuando es aceptable cambiar memoria por velocidad, ya que la creación y actualización de la estructura de datos consumirá memoria extra. + +## Créditos + +* [Game Programming Patterns/Spatial Partition](http://gameprogrammingpatterns.com/spatial-partition.html) por Bob Nystrom +* [Quadtree tutorial](https://www.youtube.com/watch?v=OJxEcs0w_kE) por Daniel Schiffman diff --git a/localization/es/spatial-partition/etc/spatial-partition.urm.png b/localization/es/spatial-partition/etc/spatial-partition.urm.png new file mode 100644 index 000000000000..5172bdb36a79 Binary files /dev/null and b/localization/es/spatial-partition/etc/spatial-partition.urm.png differ diff --git a/localization/es/special-case/README.md b/localization/es/special-case/README.md new file mode 100644 index 000000000000..94ffe6ba5738 --- /dev/null +++ b/localization/es/special-case/README.md @@ -0,0 +1,367 @@ +--- +title: Special Case +shortTitle: Special Case +category: Behavioral +language: es +tag: + - Extensibility +--- + +## Propósito + +Define algunos casos especiales, y los encapsula en subclases que proporcionan diferentes comportamientos especiales. + +## Explicación + +Ejemplo del mundo real + +> En un sistema de comercio electrónico, la capa de presentación espera que la capa de aplicación produzca cierto modelo de vista. +> Tenemos un escenario de éxito, en el que el modelo de vista de recibo contiene datos reales de la compra, +> y un par de escenarios de fracaso. + +En otras palabras + +> El patrón Special Case permite devolver objetos reales no nulos que realizan comportamientos especiales. + +En [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html) se dice +la diferencia con el Patrón de Objeto Nulo + +> Si me perdonas el juego de palabras irresistible, yo veo el Objeto Nulo como un caso especial de Caso Especial. + +**Ejemplo programático** + +Para centrarnos en el patrón en sí, implementamos la BD y el bloqueo de mantenimiento del sistema de comercio electrónico mediante la instancia singleton. + +```java +public class Db { + private static Db instance; + private Map userName2User; + private Map user2Account; + private Map itemName2Product; + + public static Db getInstance() { + if (instance == null) { + synchronized (Db.class) { + if (instance == null) { + instance = new Db(); + instance.userName2User = new HashMap<>(); + instance.user2Account = new HashMap<>(); + instance.itemName2Product = new HashMap<>(); + } + } + } + return instance; + } + + public void seedUser(String userName, Double amount) { + User user = new User(userName); + instance.userName2User.put(userName, user); + Account account = new Account(amount); + instance.user2Account.put(user, account); + } + + public void seedItem(String itemName, Double price) { + Product item = new Product(price); + itemName2Product.put(itemName, item); + } + + public User findUserByUserName(String userName) { + if (!userName2User.containsKey(userName)) { + return null; + } + return userName2User.get(userName); + } + + public Account findAccountByUser(User user) { + if (!user2Account.containsKey(user)) { + return null; + } + return user2Account.get(user); + } + + public Product findProductByItemName(String itemName) { + if (!itemName2Product.containsKey(itemName)) { + return null; + } + return itemName2Product.get(itemName); + } + + public class User { + private String userName; + + public User(String userName) { + this.userName = userName; + } + + public String getUserName() { + return userName; + } + + public ReceiptDto purchase(Product item) { + return new ReceiptDto(item.getPrice()); + } + } + + public class Account { + private Double amount; + + public Account(Double amount) { + this.amount = amount; + } + + public MoneyTransaction withdraw(Double price) { + if (price > amount) { + return null; + } + return new MoneyTransaction(amount, price); + } + + public Double getAmount() { + return amount; + } + } + + public class Product { + private Double price; + + public Product(Double price) { + this.price = price; + } + + public Double getPrice() { + return price; + } + } +} + +public class MaintenanceLock { + private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class); + + private static MaintenanceLock instance; + private boolean lock = true; + + public static MaintenanceLock getInstance() { + if (instance == null) { + synchronized (MaintenanceLock.class) { + if (instance == null) { + instance = new MaintenanceLock(); + } + } + } + return instance; + } + + public boolean isLock() { + return lock; + } + + public void setLock(boolean lock) { + this.lock = lock; + LOGGER.info("Maintenance lock is set to: " + lock); + } +} +``` + +Primero presentaremos la capa de presentación, la interfaz del modelo de vista de recibo y su implementación de un escenario exitoso. + +```java +public interface ReceiptViewModel { + void show(); +} + +public class ReceiptDto implements ReceiptViewModel { + + private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class); + + private Double price; + + public ReceiptDto(Double price) { + this.price = price; + } + + public Double getPrice() { + return price; + } + + @Override + public void show() { + LOGGER.info("Receipt: " + price + " paid"); + } +} +``` + +Y aquí están las implementaciones de los escenarios de fracaso, que son los casos especiales. + +```java +public class DownForMaintenance implements ReceiptViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class); + + @Override + public void show() { + LOGGER.info("Down for maintenance"); + } +} + +public class InvalidUser implements ReceiptViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class); + + private final String userName; + + public InvalidUser(String userName) { + this.userName = userName; + } + + @Override + public void show() { + LOGGER.info("Invalid user: " + userName); + } +} + +public class OutOfStock implements ReceiptViewModel { + + private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class); + + private String userName; + private String itemName; + + public OutOfStock(String userName, String itemName) { + this.userName = userName; + this.itemName = itemName; + } + + @Override + public void show() { + LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy"); + } +} + +public class InsufficientFunds implements ReceiptViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class); + + private String userName; + private Double amount; + private String itemName; + + public InsufficientFunds(String userName, Double amount, String itemName) { + this.userName = userName; + this.amount = amount; + this.itemName = itemName; + } + + @Override + public void show() { + LOGGER.info("Insufficient funds: " + amount + " of user: " + userName + + " for buying item: " + itemName); + } +} +``` + +En segundo lugar, está la capa de aplicación, la implementación de los servicios de aplicación y la implementación de los servicios de dominio. + +```java +public class ApplicationServicesImpl implements ApplicationServices { + private DomainServicesImpl domain = new DomainServicesImpl(); + + @Override + public ReceiptViewModel loggedInUserPurchase(String userName, String itemName) { + if (isDownForMaintenance()) { + return new DownForMaintenance(); + } + return this.domain.purchase(userName, itemName); + } + + private boolean isDownForMaintenance() { + return MaintenanceLock.getInstance().isLock(); + } +} + +public class DomainServicesImpl implements DomainServices { + public ReceiptViewModel purchase(String userName, String itemName) { + Db.User user = Db.getInstance().findUserByUserName(userName); + if (user == null) { + return new InvalidUser(userName); + } + + Db.Account account = Db.getInstance().findAccountByUser(user); + return purchase(user, account, itemName); + } + + private ReceiptViewModel purchase(Db.User user, Db.Account account, String itemName) { + Db.Product item = Db.getInstance().findProductByItemName(itemName); + if (item == null) { + return new OutOfStock(user.getUserName(), itemName); + } + + ReceiptDto receipt = user.purchase(item); + MoneyTransaction transaction = account.withdraw(receipt.getPrice()); + if (transaction == null) { + return new InsufficientFunds(user.getUserName(), account.getAmount(), itemName); + } + + return receipt; + } +} +``` + +Por último, el cliente envía solicitudes a los servicios de la aplicación para obtener la vista de la presentación. + +```java + // DB seeding + LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, " + + "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}"); + Db.getInstance().seedUser("ignite1771", 1000.0); + Db.getInstance().seedItem("computer", 800.0); + Db.getInstance().seedItem("car", 20000.0); + + var applicationServices = new ApplicationServicesImpl(); + ReceiptViewModel receipt; + + LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv"); + receipt = applicationServices.loggedInUserPurchase("abc123", "tv"); + receipt.show(); + MaintenanceLock.getInstance().setLock(false); + LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv"); + receipt = applicationServices.loggedInUserPurchase("abc123", "tv"); + receipt.show(); + LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "tv"); + receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv"); + receipt.show(); + LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "car"); + receipt = applicationServices.loggedInUserPurchase("ignite1771", "car"); + receipt.show(); + LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "computer"); + receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer"); + receipt.show(); +``` + +Salida del programa de cada solicitud: + +``` + Down for maintenance + Invalid user: abc123 + Out of stock: tv for user = ignite1771 to buy + Insufficient funds: 1000.0 of user: ignite1771 for buying item: car + Receipt: 800.0 paid +``` + +## Diagrama de clases + +![alt text](./etc/special_case_urm.png "Special Case") + +## Aplicabilidad + +Utilice el patrón Special Case cuando: + +* Tienes varios lugares en el sistema que tienen el mismo comportamiento después de una comprobación condicional + para una instancia de clase en particular, o el mismo comportamiento después de una comprobación nula. +* Devuelve un objeto real que realiza el comportamiento real, en lugar de un objeto nulo que no realiza nada. + +## Tutorial + +* [Special Case Tutorial](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case) + +## Créditos + +* [How to Reduce Cyclomatic Complexity Part 2: Special Case Pattern](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case) +* [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html) +* [Special Case](https://www.martinfowler.com/eaaCatalog/specialCase.html) \ No newline at end of file diff --git a/localization/es/special-case/etc/special_case_urm.png b/localization/es/special-case/etc/special_case_urm.png new file mode 100644 index 000000000000..03ca646f3816 Binary files /dev/null and b/localization/es/special-case/etc/special_case_urm.png differ diff --git a/localization/es/specification/README.md b/localization/es/specification/README.md new file mode 100644 index 000000000000..6a5bf7fd7238 --- /dev/null +++ b/localization/es/specification/README.md @@ -0,0 +1,214 @@ +--- +title: Specification +shortTitle: Specification +category: Behavioral +language: es +tag: + - Data access +--- + +## También conocido como + +Filter, Criteria + +## Propósito + +El patrón de Specification separa la declaración de cómo emparejar un candidato, del objeto candidato +con el que se compara. Además de su utilidad en la selección, también es valioso para la +validación y para la construcción por encargo. + +## Explicación + +Ejemplo del mundo real + +> Hay un conjunto de criaturas diferentes y a menudo necesitamos seleccionar algún subconjunto de ellas. Podemos +> escribir nuestra especificación de búsqueda como "criaturas que puedan volar", "criaturas de más de 500 +> kilogramos", o como una combinación de otras especificaciones de búsqueda. +> que realizará el filtrado. + +En palabras sencillas + +> El patrón de especificación nos permite separar los criterios de búsqueda del objeto que realiza la +> búsqueda. + +Wikipedia dice + +> En programación informática, el patrón de especificación es un patrón particular de diseño de software, +> por el cual las reglas de negocio pueden ser recombinadas encadenando las reglas de negocio usando lógica booleana +> lógica booleana. + +**Ejemplo programático** + +Si nos fijamos en nuestro ejemplo anterior, tenemos un conjunto de criaturas con ciertas propiedades. +propiedades. Esas propiedades pueden formar parte de un conjunto predefinido y limitado (representado aquí por los +enums Tamaño, Movimiento y Color); pero también pueden ser valores continuos (por ejemplo, la masa de una +criatura). En este caso, es más apropiado utilizar lo que llamamos "especificación parametrizada", +en la que el valor de la propiedad puede darse como argumento al instanciar la criatura, lo que permite +mayor flexibilidad. Una tercera opción es combinar propiedades predefinidas y/o parametrizadas utilizando +lógica booleana, lo que permite posibilidades de selección casi ilimitadas (esto se denomina "especificación compuesta", véase más adelante). +especificación compuesta", véase más adelante). Los pros y los contras de cada enfoque se detallan en la tabla al final de este documento. +de este documento. + +```java +public interface Creature { + String getName(); + Size getSize(); + Movement getMovement(); + Color getColor(); + Mass getMass(); +} +``` + +Y la implementación de `Dragon` tiene este aspecto. + +```java +public class Dragon extends AbstractCreature { + + public Dragon() { + super("Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0)); + } +} +``` + +Ahora que queremos seleccionar algún subconjunto de ellos, utilizamos selectores. Para seleccionar criaturas que vuelan +debemos usar `MovementSelector`. + +```java +public class MovementSelector extends AbstractSelector { + + private final Movement movement; + + public MovementSelector(Movement m) { + this.movement = m; + } + + @Override + public boolean test(Creature t) { + return t.getMovement().equals(movement); + } +} +``` + +Por otro lado, cuando seleccionamos criaturas más pesadas que una cantidad elegida, usamos +`MassGreaterThanSelector`. + +```java +public class MassGreaterThanSelector extends AbstractSelector { + + private final Mass mass; + + public MassGreaterThanSelector(double mass) { + this.mass = new Mass(mass); + } + + @Override + public boolean test(Creature t) { + return t.getMass().greaterThan(mass); + } +} +``` + +Con estos elementos en su lugar, podemos realizar una búsqueda de criaturas rojas de la siguiente manera: + +```java + var redCreatures = creatures.stream().filter(new ColorSelector(Color.RED)) + .collect(Collectors.toList()); +``` + +Pero también podríamos usar nuestro selector parametrizado así: + +```java + var heavyCreatures = creatures.stream().filter(new MassGreaterThanSelector(500.0) + .collect(Collectors.toList()); +``` + +Nuestra tercera opción es combinar varios selectores. La búsqueda de criaturas especiales +(definidas como rojas, voladoras y no pequeñas) podría hacerse de la siguiente manera: + +```java + var specialCreaturesSelector = + new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING)).and(new SizeSelector(Size.SMALL).not()); + + var specialCreatures = creatures.stream().filter(specialCreaturesSelector) + .collect(Collectors.toList()); +``` + +**Más información sobre las especificaciones de los compuestos** + +En Composite Specification, crearemos instancias personalizadas de `AbstractSelector` combinando +otros selectores (llamados "hojas") utilizando los tres operadores lógicos básicos. Éstos se implementan en +ConjunctionSelector`, `DisjunctionSelector` y `NegationSelector`. + +```java +public abstract class AbstractSelector implements Predicate { + + public AbstractSelector and(AbstractSelector other) { + return new ConjunctionSelector<>(this, other); + } + + public AbstractSelector or(AbstractSelector other) { + return new DisjunctionSelector<>(this, other); + } + + public AbstractSelector not() { + return new NegationSelector<>(this); + } +} +``` + +```java +public class ConjunctionSelector extends AbstractSelector { + + private final List> leafComponents; + + @SafeVarargs + ConjunctionSelector(AbstractSelector... selectors) { + this.leafComponents = List.of(selectors); + } + + /** + * Tests if *all* selectors pass the test. + */ + @Override + public boolean test(T t) { + return leafComponents.stream().allMatch(comp -> (comp.test(t))); + } +} +``` + +Todo lo que queda por hacer ahora es crear selectores de hoja (ya sean de código duro o parametrizados) que +sean lo más genéricos posible, y podremos instanciar la clase ``AbstractSelector`` combinando +cualquier cantidad de selectores, como se ejemplificó anteriormente. Debemos tener cuidado, sin embargo, ya que es fácil +cometer un error al combinar muchos operadores lógicos; en particular, debemos prestar atención a la prioridad de las operaciones. +la prioridad de las operaciones. En general, la especificación compuesta es una gran manera de escribir más +código reutilizable, ya que no es necesario crear una clase Selector para cada operación de filtrado. En su lugar +simplemente creamos una instancia de ``AbstractSelector`` "sobre la marcha", utilizando selectores genéricos de "hoja" y algunos selectores booleanos básicos. +y alguna lógica booleana básica. + +**Comparación de los distintos enfoques** + +| Patrón | Uso | Pros | Contras | +|---|---|---|---| +| Especificación Codificada | Los criterios de selección son pocos y se conocen de antemano | + Fácil de implementar | - Inflexible | +| | | + Expresivo | +| Especificación Parametrizada | Los criterios de selección abarcan un amplio rango de valores (por ejemplo, masa, velocidad,...) | + Alguna flexibilidad | - Aún requiere clases de propósito especial | +| Especificación Compuesta | Hay muchos criterios de selección que se pueden combinar de múltiples maneras, por lo tanto, no es factible crear una clase para cada selector | + Muy flexible, sin requerir muchas clases especializadas | - Algo más difícil de comprender | +| | | + Admite operaciones lógicas | - Todavía necesitas crear las clases base utilizadas como hojas | +## Class diagram + +![alt text](./etc/specification.png "Specification") + +## Diagrama de clases + +Utilice el patrón Specification cuando + +* Necesitas seleccionar un subconjunto de objetos basándote en algún criterio, y refrescar la selección en varios momentos. +* Necesita comprobar que sólo se utilizan objetos adecuados para una determinada función (validación). + +## Patrones relacionados + +* Repository + +## Créditos + +* [Martin Fowler - Specifications](http://martinfowler.com/apsupp/spec.pdf) diff --git a/localization/es/specification/etc/specification.png b/localization/es/specification/etc/specification.png new file mode 100644 index 000000000000..60fb0402d8d3 Binary files /dev/null and b/localization/es/specification/etc/specification.png differ diff --git a/localization/es/state/README.md b/localization/es/state/README.md new file mode 100644 index 000000000000..b366d714ce88 --- /dev/null +++ b/localization/es/state/README.md @@ -0,0 +1,168 @@ +--- +title: State +shortTitle: State +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## También conocido como + +Objects for States + +## Propósito + +Permite que un objeto altere su comportamiento cuando cambia su estado interno. El objeto parecerá +cambiar de clase. + +## Explicación + +Ejemplo del mundo real + +> Al observar un mamut en su hábitat natural parece cambiar su comportamiento en función de la +> situación. Al principio puede parecer tranquilo, pero con el tiempo, cuando detecta una amenaza, se enfada y se vuelve peligroso para su entorno. +> peligroso para su entorno. + +En palabras sencillas + +> El patrón de estado permite a un objeto cambiar su comportamiento. + +Wikipedia dice + +> El patrón de estado es un patrón de diseño de software de comportamiento que permite a un objeto alterar su +> comportamiento cuando cambia su estado interno. Este patrón es cercano al concepto de máquinas de estado finito. +> máquinas de estado finito. El patrón de estado puede ser interpretado como un patrón de estrategia, que es capaz de cambiar una estrategia a través de invocaciones de métodos definidos por el usuario. +> estrategia a través de invocaciones de métodos definidos en la interfaz del patrón. + +**Ejemplo programático** + +Esta es la interfaz de estado y sus implementaciones concretas. + +```java +public interface State { + + void onEnterState(); + + void observe(); +} + +@Slf4j +public class PeacefulState implements State { + + private final Mammoth mammoth; + + public PeacefulState(Mammoth mammoth) { + this.mammoth = mammoth; + } + + @Override + public void observe() { + LOGGER.info("{} is calm and peaceful.", mammoth); + } + + @Override + public void onEnterState() { + LOGGER.info("{} calms down.", mammoth); + } +} + +@Slf4j +public class AngryState implements State { + + private final Mammoth mammoth; + + public AngryState(Mammoth mammoth) { + this.mammoth = mammoth; + } + + @Override + public void observe() { + LOGGER.info("{} is furious!", mammoth); + } + + @Override + public void onEnterState() { + LOGGER.info("{} gets angry!", mammoth); + } +} +``` + +Y aquí está el mammoth que contiene el Estado. + +```java +public class Mammoth { + + private State state; + + public Mammoth() { + state = new PeacefulState(this); + } + + public void timePasses() { + if (state.getClass().equals(PeacefulState.class)) { + changeStateTo(new AngryState(this)); + } else { + changeStateTo(new PeacefulState(this)); + } + } + + private void changeStateTo(State newState) { + this.state = newState; + this.state.onEnterState(); + } + + @Override + public String toString() { + return "The mammoth"; + } + + public void observe() { + this.state.observe(); + } +} +``` + +He aquí el ejemplo completo de cómo se comporta el mammoth a lo largo del tiempo. + +```java + var mammoth = new Mammoth(); + mammoth.observe(); + mammoth.timePasses(); + mammoth.observe(); + mammoth.timePasses(); + mammoth.observe(); +``` + +Salida del programa: + +```java + The mammoth gets angry! + The mammoth is furious! + The mammoth calms down. + The mammoth is calm and peaceful. +``` + +## Diagrama de clases + +![alt text](./etc/state_urm.png "State") + +## Aplicabilidad + +Utiliza el patrón State en cualquiera de los siguientes casos: + +* El comportamiento de un objeto depende de su estado, y debe cambiar su comportamiento en tiempo de ejecución dependiendo de ese estado. +* Las operaciones tienen grandes sentencias condicionales multiparte que dependen del estado del objeto. Este estado suele estar representado por una o más constantes enumeradas. A menudo, varias operaciones contendrán esta misma estructura condicional. El patrón State coloca cada rama de la condicional en una clase separada. Esto permite tratar el estado del objeto como un objeto en sí mismo que puede variar independientemente de otros objetos. + +Traducción realizada con la versión gratuita del traductor www.DeepL.com/Translator + +## Usos conocidos + +* [javax.faces.lifecycle.Lifecycle#execute()](http://docs.oracle.com/javaee/7/api/javax/faces/lifecycle/Lifecycle.html#execute-javax.faces.context.FacesContext-) controlled by [FacesServlet](http://docs.oracle.com/javaee/7/api/javax/faces/webapp/FacesServlet.html), the behavior is dependent on current phase of lifecycle. +* [JDiameter - Diameter State Machine](https://github.com/npathai/jdiameter/blob/master/core/jdiameter/api/src/main/java/org/jdiameter/api/app/State.java) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/es/state/etc/state_urm.png b/localization/es/state/etc/state_urm.png new file mode 100644 index 000000000000..c2cf9f562943 Binary files /dev/null and b/localization/es/state/etc/state_urm.png differ diff --git a/localization/es/step-builder/README.md b/localization/es/step-builder/README.md index 73dcf46747d0..0b844713d0a3 100644 --- a/localization/es/step-builder/README.md +++ b/localization/es/step-builder/README.md @@ -1,5 +1,6 @@ --- title: Step Builder +shortTitle: Step Builder category: Creational language: es tag: diff --git a/localization/es/strangler/README.md b/localization/es/strangler/README.md new file mode 100644 index 000000000000..89a5520b6fc6 --- /dev/null +++ b/localization/es/strangler/README.md @@ -0,0 +1,29 @@ +--- +title: Strangler +shortTitle: Strangler +category: Structural +language: es +tag: + - Extensibility + - Cloud distributed +--- + +## Propósito +Migrar de forma incremental un sistema heredado sustituyendo gradualmente piezas específicas de funcionalidad +con nuevas aplicaciones y servicios. A medida que se sustituyen las funciones del sistema heredado, el nuevo +sistema acaba cubriendo todas las funciones del sistema antiguo y puede tener sus propias funciones nuevas, con lo que +estrangulando el sistema antiguo y permitiéndole retirarlo del servicio. + +## Diagrama de clases +![alt text](./etc/strangler.png "Strangler") + +## Aplicabilidad +Este patrón estrangulador es una manera segura de eliminar gradualmente una cosa por algo mejor, más barato, o +más expandible. Especialmente cuando se quiere actualizar el sistema heredado con nuevas técnicas y se necesita +desarrollar continuamente nuevas características al mismo tiempo. Tenga en cuenta que este patrón requiere un esfuerzo adicional, +por lo que normalmente se utiliza cuando el sistema no es tan simple. + +## Créditos + +* [Strangler pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler) +* [Legacy Application Strangulation : Case Studies](https://paulhammant.com/2013/07/14/legacy-application-strangulation-case-studies/) diff --git a/localization/es/strangler/etc/strangler.png b/localization/es/strangler/etc/strangler.png new file mode 100644 index 000000000000..c69305e5d510 Binary files /dev/null and b/localization/es/strangler/etc/strangler.png differ diff --git a/localization/es/strategy/README.md b/localization/es/strategy/README.md new file mode 100644 index 000000000000..d211914458e2 --- /dev/null +++ b/localization/es/strategy/README.md @@ -0,0 +1,188 @@ +--- +title: Strategy +shortTitle: Strategy +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## También conocido como + +Policy + +## Propósito + +Definir una familia de algoritmos, encapsular cada uno de ellos y hacerlos intercambiables. La estrategia permite +que el algoritmo varíe independientemente de los clientes que lo utilicen. + +## Explicación + +Ejemplo del mundo real + +> Matar dragones es un trabajo peligroso. Con la experiencia, se hace más fácil. Los veteranos de +> Los matadragones han desarrollado diferentes estrategias de lucha contra distintos tipos de dragones. + +En palabras sencillas + +> El patrón de estrategia permite elegir el algoritmo más adecuado en tiempo de ejecución. + +Wikipedia dice + +> En programación informática, el patrón de estrategia (también conocido como patrón de política) es un patrón de > diseño de software de comportamiento que permite seleccionar un algoritmo en tiempo de ejecución. +> patrón de diseño de software que permite seleccionar un algoritmo en tiempo de ejecución. + +**Ejemplo programático** + +Presentemos primero la interfaz de la estrategia para matar dragones y sus implementaciones. + +```java +@FunctionalInterface +public interface DragonSlayingStrategy { + + void execute(); +} + +@Slf4j +public class MeleeStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("With your Excalibur you sever the dragon's head!"); + } +} + +@Slf4j +public class ProjectileStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); + } +} + +@Slf4j +public class SpellStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"); + } +} +``` + +Y aquí está el poderoso cazador de dragones, que puede elegir su estrategia de lucha en función del +oponente. + +```java +public class DragonSlayer { + + private DragonSlayingStrategy strategy; + + public DragonSlayer(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void changeStrategy(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void goToBattle() { + strategy.execute(); + } +} +``` + +Por último, aquí está el cazador de dragones en acción. + +```java + LOGGER.info("Green dragon spotted ahead!"); + var dragonSlayer = new DragonSlayer(new MeleeStrategy()); + dragonSlayer.goToBattle(); + LOGGER.info("Red dragon emerges."); + dragonSlayer.changeStrategy(new ProjectileStrategy()); + dragonSlayer.goToBattle(); + LOGGER.info("Black dragon lands before you."); + dragonSlayer.changeStrategy(new SpellStrategy()); + dragonSlayer.goToBattle(); +``` + +Salida del programa: + +``` + Green dragon spotted ahead! + With your Excalibur you sever the dragon's head! + Red dragon emerges. + You shoot the dragon with the magical crossbow and it falls dead on the ground! + Black dragon lands before you. + You cast the spell of disintegration and the dragon vaporizes in a pile of dust! +``` + +Además, las expresiones lambda de Java 8 proporcionan otro enfoque para la implementación: + +```java +public class LambdaStrategy { + + private static final Logger LOGGER = LoggerFactory.getLogger(LambdaStrategy.class); + + public enum Strategy implements DragonSlayingStrategy { + MeleeStrategy(() -> LOGGER.info( + "With your Excalibur you sever the dragon's head!")), + ProjectileStrategy(() -> LOGGER.info( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), + SpellStrategy(() -> LOGGER.info( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + + private final DragonSlayingStrategy dragonSlayingStrategy; + + Strategy(DragonSlayingStrategy dragonSlayingStrategy) { + this.dragonSlayingStrategy = dragonSlayingStrategy; + } + + @Override + public void execute() { + dragonSlayingStrategy.execute(); + } + } +} +``` + +Y aquí está el cazador de dragones en acción. + +```java + LOGGER.info("Green dragon spotted ahead!"); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.MeleeStrategy); + dragonSlayer.goToBattle(); + LOGGER.info("Red dragon emerges."); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.ProjectileStrategy); + dragonSlayer.goToBattle(); + LOGGER.info("Black dragon lands before you."); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.SpellStrategy); + dragonSlayer.goToBattle(); +``` + +La salida del programa es la misma que la anterior. + +## Diagrama de clases + +![alt text](./etc/strategy_urm.png "Strategy") + +## Aplicabilidad + +Utilice el patrón Strategy cuando + +* Muchas clases relacionadas sólo difieren en su comportamiento. Las estrategias proporcionan una forma de configurar una clase con uno de varios comportamientos +* Necesita diferentes variantes de un algoritmo. Por ejemplo, puede definir algoritmos que reflejen diferentes compensaciones espacio/tiempo. Las estrategias pueden utilizarse cuando estas variantes se implementan como una jerarquía de clases de algoritmos +* Un algoritmo utiliza datos que los clientes no deberían conocer. Utilice el patrón Strategy para evitar exponer estructuras de datos complejas específicas del algoritmo. +* Una clase define muchos comportamientos, y éstos aparecen como múltiples sentencias condicionales en sus operaciones. En lugar de muchas condicionales, mueva las ramas condicionales relacionadas a su propia clase Strategy. + +## Tutorial + +* [Strategy Pattern Tutorial](https://www.journaldev.com/1754/strategy-design-pattern-in-java-example-tutorial) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/es/strategy/etc/strategy_urm.png b/localization/es/strategy/etc/strategy_urm.png new file mode 100644 index 000000000000..67d19acae62d Binary files /dev/null and b/localization/es/strategy/etc/strategy_urm.png differ diff --git a/localization/es/subclass-sandbox/README.md b/localization/es/subclass-sandbox/README.md new file mode 100644 index 000000000000..4ad6d802db97 --- /dev/null +++ b/localization/es/subclass-sandbox/README.md @@ -0,0 +1,113 @@ +--- +title: Subclass Sandbox +shortTitle: Subclass Sandbox +category: Behavioral +language: es +tag: + - Game programming +--- + +## Propósito +El patrón Subclass Sandbox describe una idea básica, aunque no tiene una mecánica muy detallada. Necesitarás el patrón cuando tengas varias subclases similares. Si tienes que hacer un pequeño cambio, entonces cambia la clase base, mientras que todas las subclases no deberían tener que ser tocadas. Así que la clase base tiene que ser capaz de proporcionar todas las operaciones que una clase derivada necesita realizar. + +## Explicación +Ejemplo del mundo real +> Consideremos que queremos crear algunos superpoderes en el juego, y necesitan moverse acompañados de un efecto de sonido y desovar partículas. ¿Crear muchas clases que contengan métodos similares o necesitar una clase base para derivarlos? El patrón subclase-base te permite tratar este problema de la segunda manera. + +En palabras sencillas +> El subclass-sandbox consiste en trasladar los métodos solapados en las subclases a una clase base que reduzca la tasa de redundancia en las clases. + +Wikipedia dice +> A base class defines an abstract sandbox method and several provided operations. Marking them protected makes it clear that they are for use by derived classes. Each derived sandboxed subclass implements the sandbox method using the provided operations. + +**Ejemplo programático** +Comenzamos con la clase base `Superpower`. Contiene un método abstracto sandbox `active()` y algunas operaciones proporcionadas. + +``` +public abstract class Superpower { + + protected Logger logger; + + protected abstract void activate(); + + protected void move(double x, double y, double z) { + logger.info("Move to ( " + x + ", " + y + ", " + z + " )"); + } + + protected void playSound(String soundName, int volume) { + logger.info("Play " + soundName + " with volume " + volume); + } + + protected void spawnParticles(String particleType, int count) { + logger.info("Spawn " + count + " particle with type " + particleType); + } +} +``` +A continuación podemos crear una subclase derivada sandboxed que implemente el método sandbox usando las operaciones proporcionadas. Aquí está la primera Superpower: +``` +public class SkyLaunch extends Superpower { + + public SkyLaunch() { + super(); + logger = LoggerFactory.getLogger(SkyLaunch.class); + } + + @Override + protected void activate() { + move(0, 0, 20); + playSound("SKYLAUNCH_SOUND", 1); + spawnParticles("SKYLAUNCH_PARTICLE", 100); + } +} +``` +Aquí está la segunda Superpower. +``` +public class GroundDive extends Superpower { + + public GroundDive() { + super(); + logger = LoggerFactory.getLogger(GroundDive.class); + } + + @Override + protected void activate() { + move(0, 0, -20); + playSound("GROUNDDIVE_SOUND", 5); + spawnParticles("GROUNDDIVE_PARTICLE", 20); + } +} +``` +Por último, aquí están los superpower en activo. +``` + LOGGER.info("Use superpower: sky launch"); + var skyLaunch = new SkyLaunch(); + skyLaunch.activate(); + LOGGER.info("Use superpower: ground dive"); + var groundDive = new GroundDive(); + groundDive.activate(); +``` +Salida del programa: +``` +// Use superpower: sky launch +// Move to ( 0.0, 0.0, 20.0 ) +// Play SKYLAUNCH_SOUND with volume 1 +// Spawn 100 particle with type SKYLAUNCH_PARTICLE +// Use superpower: ground dive +// Move to ( 0.0, 0.0, -20.0 ) +// Play GROUNDDIVE_SOUND with volume 5 +// Spawn 20 particle with type GROUNDDIVE_PARTICLE +``` +## Diagrama de clases +![alt text](./etc/subclass-sandbox.urm.png "Subclass Sandbox pattern class diagram") + +## Aplicabilidad +El patrón Subclass Sandbox es un patrón muy simple y común que se encuentra en muchas bases de código, incluso fuera de los juegos. Si tienes un método protegido no virtual por ahí, probablemente ya estés usando algo como esto. Subclass Sandbox es un buen ajuste cuando: + +- Usted tiene una clase base con un número de clases derivadas. +- La clase base es capaz de proporcionar todas las operaciones que una clase derivada puede necesitar realizar. +- Hay solapamiento de comportamiento en las subclases y desea que sea más fácil compartir código entre ellas. +- Usted quiere minimizar el acoplamiento entre las clases derivadas y el resto del programa + +## Créditos + +* [Game Programming Patterns - Subclass Sandbox](https://gameprogrammingpatterns.com/subclass-sandbox.html) diff --git a/localization/es/subclass-sandbox/etc/subclass-sandbox.urm.png b/localization/es/subclass-sandbox/etc/subclass-sandbox.urm.png new file mode 100644 index 000000000000..db81e12cc815 Binary files /dev/null and b/localization/es/subclass-sandbox/etc/subclass-sandbox.urm.png differ diff --git a/localization/es/table-module/README.md b/localization/es/table-module/README.md new file mode 100644 index 000000000000..a7320e983710 --- /dev/null +++ b/localization/es/table-module/README.md @@ -0,0 +1,134 @@ +--- +title: Table Module +shortTitle: Table Module +category: Structural +language: es +tag: + - Data access +--- + +## Propósito +El módulo de tablas organiza la lógica del dominio con una clase por tabla de la base de datos, y una única instancia de una clase contiene los distintos procedimientos que actuarán sobre los datos. + +## Explicación + +Ejemplo del mundo real + +> Cuando tratamos con un sistema de usuarios, necesitamos algunas operaciones sobre la tabla de usuarios. Podemos utilizar el patrón de módulo de tabla en este escenario. Podemos crear una clase llamada UserTableModule e inicializar una instancia de esa clase para manejar la lógica de negocio para todas las filas de la tabla de usuarios. + +En palabras simples + +> Una única instancia que maneja la lógica de negocio para todas las filas de una tabla o vista de la base de datos. + +**Ejemplo programático** + +En el ejemplo del sistema de usuarios, necesitamos manejar la lógica de dominio del login y registro de usuarios. Podemos utilizar el patrón de módulo de tabla y crear una instancia de la clase `UserTableModule` para manejar la lógica de negocio de todas las filas de la tabla de usuarios. + +Aquí está la entidad básica `User`. + +```java +@Setter +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor +public class User { + private int id; + private String username; + private String password; +} +``` + +Aquí está la clase `UserTableModule`. + +```java +public class UserTableModule { + private final DataSource dataSource; + private Connection connection = null; + private ResultSet resultSet = null; + private PreparedStatement preparedStatement = null; + + public UserTableModule(final DataSource userDataSource) { + this.dataSource = userDataSource; + } + + /** + * Login using username and password. + * + * @param username the username of a user + * @param password the password of a user + * @return the execution result of the method + * @throws SQLException if any error + */ + public int login(final String username, final String password) throws SQLException { + // Method implementation. + + } + + /** + * Register a new user. + * + * @param user a user instance + * @return the execution result of the method + * @throws SQLException if any error + */ + public int registerUser(final User user) throws SQLException { + // Method implementation. + } +} +``` + +En la clase `App`, usamos una instancia del `UserTableModule` para manejar el login y registro de usuarios. + +```java +// Create data source and create the user table. +final var dataSource = createDataSource(); +createSchema(dataSource); +userTableModule = new UserTableModule(dataSource); + +//Initialize two users. +var user1 = new User(1, "123456", "123456"); +var user2 = new User(2, "test", "password"); + +//Login and register using the instance of userTableModule. +userTableModule.registerUser(user1); +userTableModule.login(user1.getUsername(), user1.getPassword()); +userTableModule.login(user2.getUsername(), user2.getPassword()); +userTableModule.registerUser(user2); +userTableModule.login(user2.getUsername(), user2.getPassword()); + +deleteSchema(dataSource); +``` + +La salida del programa: + +```java +12:22:13.095 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully! +12:22:13.117 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully! +12:22:13.128 [main] INFO com.iluwatar.tablemodule.UserTableModule - Fail to login! +12:22:13.136 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully! +12:22:13.144 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully! +``` + +## Diagrama de clases + +![](./etc/table-module.urm.png "table module") + +## Aplicabilidad + +Utilice el patrón de módulo de tabla cuando + +- La lógica del dominio es simple y los datos están en forma tabular. +- La aplicación sólo utiliza unas pocas estructuras de datos comunes compartidas orientadas a tablas. + +## Patrones relacionados + +- [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/) + +- [Domain Model](https://java-design-patterns.com/patterns/domain-model/) + +## Créditos + +* [Table Module Pattern](http://wiki3.cosc.canterbury.ac.nz/index.php/Table_module_pattern) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04) +* [Architecture patterns: domain model and friends](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends) \ No newline at end of file diff --git a/localization/es/table-module/etc/table-module.urm.png b/localization/es/table-module/etc/table-module.urm.png new file mode 100644 index 000000000000..9c4bc0b189db Binary files /dev/null and b/localization/es/table-module/etc/table-module.urm.png differ diff --git a/localization/es/template-method/README.md b/localization/es/template-method/README.md new file mode 100644 index 000000000000..2797f54a0c98 --- /dev/null +++ b/localization/es/template-method/README.md @@ -0,0 +1,156 @@ +--- +title: Template method +shortTitle: Template method +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## Propósito + +Define el esqueleto de un algoritmo en una operación, difiriendo algunos pasos a subclases. Plantilla +Método que permite a las subclases redefinir ciertos pasos de un algoritmo sin cambiar la estructura del algoritmo. +del algoritmo. + +## Explicación + +Ejemplo del mundo real + +> Los pasos generales para robar un objeto son los mismos. Primero, eliges al objetivo, luego lo confundes... +> y finalmente, robas el objeto. Sin embargo, hay muchas maneras de implementar estos +> pasos. + +En palabras sencillas + +> El patrón Template Method esboza los pasos generales en la clase padre y deja que las implementaciones hijas concretas definan los detalles. + +Wikipedia dice + +> En programación orientada a objetos, el método de plantillas es uno de los patrones de diseño de comportamiento +> identificados por Gamma et al. en el libro Design Patterns. El método de plantilla es un método en una +> superclase, normalmente una superclase abstracta, y define el esqueleto de una operación en términos de +> una serie de pasos de alto nivel. Estos pasos son a su vez implementados por métodos de ayuda adicionales +> en la misma clase que el método de plantilla. + +**Ejemplo programático** + +Presentemos primero la clase de método de plantilla junto con sus implementaciones concretas. +Para asegurarse de que las subclases no sobrescriben el método de plantilla, el método de plantilla (en nuestro caso +método `steal`) debe ser declarado `final`, de lo contrario el esqueleto definido en la clase base podría +ser sobreescrito en subclases. + +```java +@Slf4j +public abstract class StealingMethod { + + protected abstract String pickTarget(); + + protected abstract void confuseTarget(String target); + + protected abstract void stealTheItem(String target); + + public final void steal() { + var target = pickTarget(); + LOGGER.info("The target has been chosen as {}.", target); + confuseTarget(target); + stealTheItem(target); + } +} + +@Slf4j +public class SubtleMethod extends StealingMethod { + + @Override + protected String pickTarget() { + return "shop keeper"; + } + + @Override + protected void confuseTarget(String target) { + LOGGER.info("Approach the {} with tears running and hug him!", target); + } + + @Override + protected void stealTheItem(String target) { + LOGGER.info("While in close contact grab the {}'s wallet.", target); + } +} + +@Slf4j +public class HitAndRunMethod extends StealingMethod { + + @Override + protected String pickTarget() { + return "old goblin woman"; + } + + @Override + protected void confuseTarget(String target) { + LOGGER.info("Approach the {} from behind.", target); + } + + @Override + protected void stealTheItem(String target) { + LOGGER.info("Grab the handbag and run away fast!"); + } +} +``` + +Aquí está la clase ladrón halfling que contiene el método de plantilla. + +```java +public class HalflingThief { + + private StealingMethod method; + + public HalflingThief(StealingMethod method) { + this.method = method; + } + + public void steal() { + method.steal(); + } + + public void changeMethod(StealingMethod method) { + this.method = method; + } +} +``` + +Y por último, mostramos cómo el ladrón halfling utiliza los diferentes métodos de robo. + +```java + var thief = new HalflingThief(new HitAndRunMethod()); + thief.steal(); + thief.changeMethod(new SubtleMethod()); + thief.steal(); +``` + +## Diagrama de clases + +![alt text](./etc/template_method_urm.png "Template Method") + +## Aplicabilidad + +El patrón Template Method debería utilizarse + +* Para implementar las partes invariantes de un algoritmo una vez y dejar que las subclases implementen el comportamiento que puede variar. +* Cuando el comportamiento común entre subclases debe ser factorizado y localizado en una clase común para evitar la duplicación de código. Este es un buen ejemplo de "refactorizar para generalizar", tal y como lo describen Opdyke y Johnson. Primero se identifican las diferencias en el código existente y luego se separan las diferencias en nuevas operaciones. Por último, se sustituye el código diferente por un método de plantilla que llama a una de estas nuevas operaciones +* Para controlar las extensiones de las subclases. Puede definir un método de plantilla que llame a operaciones "gancho" en puntos específicos, permitiendo así extensiones sólo en esos puntos + +## Tutoriales + +* [Template-method Pattern Tutorial](https://www.journaldev.com/1763/template-method-design-pattern-in-java) + +## Usos conocidos + +* [javax.servlet.GenericServlet.init](https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/GenericServlet.html#init--): +El método `GenericServlet.init(ServletConfig config)` llama al método sin parámetros `GenericServlet.init()` que está pensado para ser sobreescrito en subclases. +El método `GenericServlet.init(ServletConfig config)` es el método plantilla en este ejemplo. + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/es/template-method/etc/template_method_urm.png b/localization/es/template-method/etc/template_method_urm.png new file mode 100644 index 000000000000..b7babccff96d Binary files /dev/null and b/localization/es/template-method/etc/template_method_urm.png differ diff --git a/localization/es/throttling/README.md b/localization/es/throttling/README.md new file mode 100644 index 000000000000..a69039b42a3d --- /dev/null +++ b/localization/es/throttling/README.md @@ -0,0 +1,219 @@ +--- +title: Throttling +shortTitle: Throttling +category: Behavioral +language: es +tag: + - Performance + - Cloud distributed +--- + +## Propósito + +Garantizar que un cliente determinado no pueda acceder a los recursos del servicio más allá del límite asignado. + +## Explicación + +Ejemplo del mundo real + +> Un joven humano y un viejo enano entran en un bar. Empiezan a pedir cervezas al camarero. +> El camarero se da cuenta inmediatamente de que el joven humano no debe consumir demasiadas bebidas demasiado rápido +> y se niega a servir si no ha pasado suficiente tiempo. Para el viejo enano, el ritmo de servicio puede ser mayor. +> ser mayor. + +En palabras sencillas + +> El patrón de Throttling se utiliza para limitar la velocidad de acceso a un recurso. + +[Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) dice + +> Controlar el consumo de recursos utilizados por una instancia de una aplicación, un tenant individual, +> o un servicio completo. Esto puede permitir que el sistema continúe funcionando y cumpla con los acuerdos de nivel de servicio, incluso cuando un aumento de la demanda impone una carga extrema sobre los recursos. + +**Ejemplo programático** + +La clase `BarCustomer` presenta los clientes de la API `Bartender`. La clase `CallsCount` registra el número de +llamadas por `BarCustomer`. + +```java +public class BarCustomer { + + @Getter + private final String name; + @Getter + private final int allowedCallsPerSecond; + + public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) { + if (allowedCallsPerSecond < 0) { + throw new InvalidParameterException("Number of calls less than 0 not allowed"); + } + this.name = name; + this.allowedCallsPerSecond = allowedCallsPerSecond; + callsCount.addTenant(name); + } +} + +@Slf4j +public final class CallsCount { + private final Map tenantCallsCount = new ConcurrentHashMap<>(); + + public void addTenant(String tenantName) { + tenantCallsCount.putIfAbsent(tenantName, new AtomicLong(0)); + } + + public void incrementCount(String tenantName) { + tenantCallsCount.get(tenantName).incrementAndGet(); + } + + public long getCount(String tenantName) { + return tenantCallsCount.get(tenantName).get(); + } + + public void reset() { + tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); + LOGGER.info("reset counters"); + } +} +``` + +A continuación, se introduce el servicio al que llaman los inquilinos. Para realizar un seguimiento del número de llamadas, se utiliza un temporizador de estrangulamiento. + +```java +public interface Throttler { + + void start(); +} + +public class ThrottleTimerImpl implements Throttler { + + private final int throttlePeriod; + private final CallsCount callsCount; + + public ThrottleTimerImpl(int throttlePeriod, CallsCount callsCount) { + this.throttlePeriod = throttlePeriod; + this.callsCount = callsCount; + } + + @Override + public void start() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + callsCount.reset(); + } + }, 0, throttlePeriod); + } +} +``` + +El `Bartender` ofrece el servicio `orderDrink` a los `BarCustomer`s. Los clientes probablemente no +saben que la tasa de servicio de cerveza está limitada por su apariencia. + +```java +class Bartender { + + private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class); + private final CallsCount callsCount; + + public Bartender(Throttler timer, CallsCount callsCount) { + this.callsCount = callsCount; + timer.start(); + } + + public int orderDrink(BarCustomer barCustomer) { + var tenantName = barCustomer.getName(); + var count = callsCount.getCount(tenantName); + if (count >= barCustomer.getAllowedCallsPerSecond()) { + LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName); + return -1; + } + callsCount.incrementCount(tenantName); + LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1); + return getRandomCustomerId(); + } + + private int getRandomCustomerId() { + return ThreadLocalRandom.current().nextInt(1, 10000); + } +} +``` + +Ahora es posible ver el ejemplo completo en acción. BarCustomer` el joven humano está limitado a 2 +llamadas por segundo y el viejo enano a 4. + +```java +public static void main(String[] args) { + var callsCount = new CallsCount(); + var human = new BarCustomer("young human", 2, callsCount); + var dwarf = new BarCustomer("dwarf soldier", 4, callsCount); + + var executorService = Executors.newFixedThreadPool(2); + + executorService.execute(() -> makeServiceCalls(human, callsCount)); + executorService.execute(() -> makeServiceCalls(dwarf, callsCount)); + + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.error("Executor service terminated: {}", e.getMessage()); + } +} + +private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { + var timer = new ThrottleTimerImpl(1000, callsCount); + var service = new Bartender(timer, callsCount); + // Sleep is introduced to keep the output in check and easy to view and analyze the results. + IntStream.range(0, 50).forEach(i -> { + service.orderDrink(barCustomer); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: {}", e.getMessage()); + } + }); +} +``` + +Un extracto de la salida de consola del ejemplo: + +``` +18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters +18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters +18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed] +18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed] +18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed] +18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed] +18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed] +18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed] +18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today! +18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! +``` + +## Diagrama de clases + +![alt text](./etc/throttling_urm.png "Throttling pattern class diagram") + +## Aplicabilidad + +El patrón Throttling debe utilizarse: + +* Cuando se necesita restringir el acceso al servicio para no tener un alto impacto en el rendimiento del mismo. +* Cuando varios clientes consumen los mismos recursos del servicio y la restricción debe hacerse en función del uso por cliente. + +## Créditos + +* [Throttling pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) +* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications (Microsoft patterns & practices)](https://www.amazon.com/gp/product/B00ITGHBBS/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B00ITGHBBS&linkId=12aacdd0cec04f372e7152689525631a) diff --git a/localization/es/throttling/etc/throttling_urm.png b/localization/es/throttling/etc/throttling_urm.png new file mode 100644 index 000000000000..a9824e24b5a4 Binary files /dev/null and b/localization/es/throttling/etc/throttling_urm.png differ diff --git a/localization/es/trampoline/README.md b/localization/es/trampoline/README.md new file mode 100644 index 000000000000..8e4e43ec71e4 --- /dev/null +++ b/localization/es/trampoline/README.md @@ -0,0 +1,150 @@ +--- +title: Trampoline +shortTitle: Trampoline +category: Behavioral +language: es +tag: + - Performance +--- + +## Propósito + +El patrón Trampoline se utiliza para implementar algoritmos recursivamente en Java sin volar la pila +y para intercalar la ejecución de funciones sin codificarlas juntas. + +## Explicación + +La recursión es una técnica frecuentemente adoptada para resolver problemas algorítmicos en un estilo de divide y vencerás. +Por ejemplo, el cálculo de la suma acumulativa de Fibonacci y los factoriales. En este tipo de +problemas, la recursividad es más directa que su homóloga de bucle. Además, la recursividad puede +necesitar menos código y parecer más concisa. Hay un dicho que dice que todo problema de recursión puede resolverse +utilizando un bucle a costa de escribir código más difícil de entender. + +Sin embargo, las soluciones de tipo recursivo tienen una gran advertencia. Para cada llamada recursiva, normalmente se necesita +un valor intermedio almacenado y hay una cantidad limitada de memoria de pila disponible. Quedarse sin +memoria de pila crea un error de desbordamiento de pila y detiene la ejecución del programa. + +Trampoline pattern es un truco que permite definir algoritmos recursivos en Java sin desbordar la +pila. + +Ejemplo del mundo real + +> Un cálculo Fibonacci recursivo sin el problema de desbordamiento de pila utilizando el patrón Trampoline. + +En palabras sencillas + +> El patrón Trampoline permite la recursión sin agotar la memoria de la pila. + +Wikipedia dice + +> En Java, trampoline se refiere al uso de reflection para evitar el uso de clases internas, por ejemplo en +> eventos. La sobrecarga de tiempo de una llamada a reflection se intercambia por la sobrecarga de espacio de una clase interna. +> Trampolines en Java generalmente implican la creación de un GenericListener para pasar eventos a una clase externa. + +**Ejemplo programático** + +Esta es la implementación de `Trampoline` en Java. + +Cuando se llama a `get` sobre el Trampoline devuelto, internamente se itera llamando a `jump` sobre el +siempre que la instancia concreta devuelta sea `Trampoline`, deteniéndose una vez que la instancia +instancia devuelta sea `done`. + +```java +public interface Trampoline { + + T get(); + + default Trampoline jump() { + return this; + } + + default T result() { + return get(); + } + + default boolean complete() { + return true; + } + + static Trampoline done(final T result) { + return () -> result; + } + + static Trampoline more(final Trampoline> trampoline) { + return new Trampoline() { + @Override + public boolean complete() { + return false; + } + + @Override + public Trampoline jump() { + return trampoline.result(); + } + + @Override + public T get() { + return trampoline(this); + } + + T trampoline(final Trampoline trampoline) { + return Stream.iterate(trampoline, Trampoline::jump) + .filter(Trampoline::complete) + .findFirst() + .map(Trampoline::result) + .orElseThrow(); + } + }; + } +} +``` + +Uso del `Trampoline` para obtener valores Fibonacci. + +```java +public static void main(String[] args) { + LOGGER.info("Start calculating war casualties"); + var result = loop(10, 1).result(); + LOGGER.info("The number of orcs perished in the war: {}", result); +} + +public static Trampoline loop(int times, int prod) { + if (times == 0) { + return Trampoline.done(prod); + } else { + return Trampoline.more(() -> loop(times - 1, prod * times)); + } +} +``` + +Salida del programa: + +``` +19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties +19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800 +``` + +## Diagrama de clases + +![alt text](./etc/trampoline.urm.png "Trampoline pattern class diagram") + +## Aplicabilidad + +Utilice el patrón Trampoline cuando: + +* Para implementar funciones recursivas de cola. Este patrón permite encender una operación sin pila. +* Para intercalar la ejecución de dos o más funciones en el mismo hilo. + +## Usos conocidos + +* [cyclops-react](https://github.com/aol/cyclops-react) + +## Créditos + +* [Trampolining: a practical guide for awesome Java Developers](https://medium.com/@johnmcclean/trampolining-a-practical-guide-for-awesome-java-developers-4b657d9c3076) +* [Trampoline in java ](http://mindprod.com/jgloss/trampoline.html) +* [Laziness, trampolines, monoids and other functional amenities: this is not your father's Java](https://www.slideshare.net/mariofusco/lazine) +* [Trampoline implementation](https://github.com/bodar/totallylazy/blob/master/src/com/googlecode/totallylazy/Trampoline.java) +* [What is a trampoline function?](https://stackoverflow.com/questions/189725/what-is-a-trampoline-function) +* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://www.amazon.com/gp/product/1617293563/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617293563&linkId=ad53ae6f9f7c0982e759c3527bd2595c) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://www.amazon.com/gp/product/1617291994/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617291994&linkId=e3e5665b0732c59c9d884896ffe54f4f) diff --git a/localization/es/trampoline/etc/trampoline.urm.png b/localization/es/trampoline/etc/trampoline.urm.png new file mode 100644 index 000000000000..f2e9c7439a32 Binary files /dev/null and b/localization/es/trampoline/etc/trampoline.urm.png differ diff --git a/localization/es/transaction-script/README.md b/localization/es/transaction-script/README.md new file mode 100644 index 000000000000..1ecb005ff482 --- /dev/null +++ b/localization/es/transaction-script/README.md @@ -0,0 +1,118 @@ +--- +title: Transaction Script +shortTitle: Transaction Script +category: Behavioral +language: es +tag: + - Data access +--- + +## Propósito + +Transaction Script organiza la lógica de negocio por procedimientos donde cada procedimiento maneja una única +solicitud de la presentación. + +## Explicación + +Ejemplo del mundo real + +> Necesitas crear un sistema de reservas de habitaciones de hotel. Dado que los requisitos son bastante simples +> utilizar el patrón Transaction Script. + +En palabras sencillas + +> Transaction Script organiza la lógica de negocio en transacciones que el sistema necesita llevar a cabo. + +Ejemplo programático + +La clase `Hotel` se encarga de reservar y cancelar las reservas de habitaciones. + +```java +@Slf4j +public class Hotel { + + private final HotelDaoImpl hotelDao; + + public Hotel(HotelDaoImpl hotelDao) { + this.hotelDao = hotelDao; + } + + public void bookRoom(int roomNumber) throws Exception { + + Optional room = hotelDao.getById(roomNumber); + + if (room.isEmpty()) { + throw new Exception("Room number: " + roomNumber + " does not exist"); + } else { + if (room.get().isBooked()) { + throw new Exception("Room already booked!"); + } else { + Room updateRoomBooking = room.get(); + updateRoomBooking.setBooked(true); + hotelDao.update(updateRoomBooking); + } + } + } + + public void cancelRoomBooking(int roomNumber) throws Exception { + + Optional room = hotelDao.getById(roomNumber); + + if (room.isEmpty()) { + throw new Exception("Room number: " + roomNumber + " does not exist"); + } else { + if (room.get().isBooked()) { + Room updateRoomBooking = room.get(); + updateRoomBooking.setBooked(false); + int refundAmount = updateRoomBooking.getPrice(); + hotelDao.update(updateRoomBooking); + + LOGGER.info("Booking cancelled for room number: " + roomNumber); + LOGGER.info(refundAmount + " is refunded"); + } else { + throw new Exception("No booking for the room exists"); + } + } + } +} +``` + +La clase `Hotel` tiene dos métodos, uno para reservar y otro para cancelar una habitación respectivamente. Cada uno de ellos +transacción en el sistema, haciendo que `Hotel` implemente el patrón Transaction Script +Transaction Script. + +El método `bookRoom` consolida todos los pasos necesarios como comprobar si la habitación ya está reservada +o no, si no está reservada entonces reserva la habitación y actualiza la base de datos utilizando el DAO. + +El método `cancelRoom` consolida pasos como comprobar si la habitación está reservada o no, +si está reservada, calcula el importe del reembolso y actualiza la base de datos utilizando el DAO. + +## Diagrama de clases + +![alt text](./etc/transaction-script.png "Transaction script model") + +## Aplicabilidad + +Utilice el patrón Transaction Script cuando la aplicación tenga sólo una pequeña cantidad de lógica y esa +lógica no será extendida en el futuro. + +## Consecuencias + +* A medida que la lógica de negocio se complica, + se hace progresivamente más difícil mantener el script de transacción + en un estado bien diseñado. +* Puede ocurrir duplicación de código entre scripts de transacciones. +* Normalmente no es fácil refactorizar el script de transacciones a otros patrones de lógica de dominio. + del dominio. + +## Patrones relacionados + +* Domain Model +* Table Module +* Service Layer + +## Créditos + +* [Transaction Script Pattern](https://dzone.com/articles/transaction-script-pattern#:~:text=Transaction%20Script%20(TS)%20is%20the,need%20big%20architecture%20behind%20them.) +* [Transaction Script](https://www.informit.com/articles/article.aspx?p=1398617) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04) diff --git a/localization/es/transaction-script/etc/transaction-script.png b/localization/es/transaction-script/etc/transaction-script.png new file mode 100644 index 000000000000..6d0cffb6a551 Binary files /dev/null and b/localization/es/transaction-script/etc/transaction-script.png differ diff --git a/localization/es/twin/README.md b/localization/es/twin/README.md new file mode 100644 index 000000000000..7fa93f8a612f --- /dev/null +++ b/localization/es/twin/README.md @@ -0,0 +1,157 @@ +--- +title: Twin +shortTitle: Twin +category: Structural +language: es +tag: + - Extensibility +--- + +## Propósito +Twin pattern es un patrón de diseño que proporciona una solución estándar para simular herencia múltiple en java + +## Explicación + +Ejemplo real + +> Consideremos un juego con una pelota que necesita características de dos tipos, Game Item, e hilos para funcionar sin problemas en el juego. Podemos utilizar dos objetos, con un objeto compatible con el primer tipo y el otro compatible con el segundo tipo. El par de objetos juntos pueden funcionar como una pelota en el juego. + +En palabras sencillas + +> Proporciona una forma de formar dos subclases estrechamente acopladas que pueden actuar como una clase gemela que tiene dos extremos. + +Wikipedia dice + +> En ingeniería de software, el patrón Gemelo es un patrón de diseño de software que permite a los desarrolladores modelar herencia múltiple en lenguajes de programación que no soportan herencia múltiple. Este patrón evita muchos de los problemas de la herencia múltiple. + +**Ejemplo programático** + +Tomemos nuestro ejemplo anterior de la bola de juego. Consideremos que tenemos un juego en el que la pelota necesita ser tanto un `GameItem` como un `Thread`. +En primer lugar, tenemos la clase `GameItem` dada a continuación y la clase `Thread`. + + +```java + +@Slf4j +public abstract class GameItem { + + public void draw() { + LOGGER.info("draw"); + doDraw(); + } + + public abstract void doDraw(); + + + public abstract void click(); +} + +``` + +A continuación, tenemos las subclases `BallItem` y `BallThread` que las heredan, respectivamente. + +```java + +@Slf4j +public class BallItem extends GameItem { + + private boolean isSuspended; + + @Setter + private BallThread twin; + + @Override + public void doDraw() { + + LOGGER.info("doDraw"); + } + + public void move() { + LOGGER.info("move"); + } + + @Override + public void click() { + + isSuspended = !isSuspended; + + if (isSuspended) { + twin.suspendMe(); + } else { + twin.resumeMe(); + } + } +} + + +@Slf4j +public class BallThread extends Thread { + + @Setter + private BallItem twin; + + private volatile boolean isSuspended; + + private volatile boolean isRunning = true; + + /** + * Run the thread. + */ + public void run() { + + while (isRunning) { + if (!isSuspended) { + twin.draw(); + twin.move(); + } + try { + Thread.sleep(250); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + public void suspendMe() { + isSuspended = true; + LOGGER.info("Begin to suspend BallThread"); + } + + public void resumeMe() { + isSuspended = false; + LOGGER.info("Begin to resume BallThread"); + } + + public void stopMe() { + this.isRunning = false; + this.isSuspended = true; + } +} + +``` + +Ahora, cuando necesitemos la pelota, podemos instanciar objetos tanto del `BallThread` como del `BallItem` como un par y pasarlos a su objeto par para que puedan actuar juntos según convenga. + +```java + +var ballItem = new BallItem(); +var ballThread = new BallThread(); + +ballItem.setTwin(ballThread); +ballThread.setTwin(ballItem); + +``` + + +## Diagrama de clases +![alt text](./etc/twin.png "Twin") + +## Aplicabilidad +Utilice el lenguaje Twin + +* Para simular herencia múltiple en un lenguaje que no soporta esta característica. +* Para evitar ciertos problemas de la herencia múltiple como los choques de nombres. + +## Créditos + +* [Twin – A Design Pattern for Modeling Multiple Inheritance](http://www.ssw.uni-linz.ac.at/Research/Papers/Moe99/Paper.pdf) diff --git a/localization/es/twin/etc/twin.png b/localization/es/twin/etc/twin.png new file mode 100644 index 000000000000..724092525bd6 Binary files /dev/null and b/localization/es/twin/etc/twin.png differ diff --git a/typeobjectpattern/README.md b/localization/es/typeobjectpattern/README.md similarity index 51% rename from typeobjectpattern/README.md rename to localization/es/typeobjectpattern/README.md index 339aa7100439..9d59f8c1a69c 100644 --- a/typeobjectpattern/README.md +++ b/localization/es/typeobjectpattern/README.md @@ -1,29 +1,30 @@ --- title: Type-Object +shortTitle: Type-Object category: Behavioral -language: en +language: es tag: - Game programming - Extensibility --- -## Intent -As explained in the book Game Programming Patterns by Robert Nystrom, type object pattern helps in +## Propósito +Como se explica en el libro Game Programming Patterns de Robert Nystrom, el patrón objeto tipo ayuda a -> Allowing flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object +> Permitir la creación flexible de nuevas "clases" mediante la creación de una única clase, cada instancia de la cual representa un tipo diferente de objeto -## Explanation -Real-world example -> You are working on a game with many different breeds of monsters. Each monster breed has different values for the attributes, such as attack, health, intelligence, etc. You want to create new monster breeds, or modify the attributes of an existing breed, without needing to modify the code and recompiling the game. +## Explicación +Ejemplo del mundo real +> Estás trabajando en un juego con muchas razas diferentes de monstruos. Cada raza de monstruo tiene diferentes valores para los atributos, como ataque, salud, inteligencia, etc. Quieres crear nuevas razas de monstruos, o modificar los atributos de una raza existente, sin necesidad de modificar el código y recompilar el juego. -In plain words -> Define a type object class, and a typed object class. We give each type object instance a reference to a typed object, containing the information for that type. +En palabras sencillas +> Definimos una clase de objeto de tipo y una clase de objeto tipado. Damos a cada instancia de objeto de tipo una referencia a un objeto tipado, que contiene la información para ese tipo. -**Programmatic example** +**Ejemplo programático** -Suppose we are developing a game of Candy Crush. There are many different candy types, and we may want to edit or create new ones over time as we develop the game. +Supongamos que estamos desarrollando un juego de Candy Crush. Hay muchos tipos de caramelos diferentes, y es posible que queramos editar o crear nuevos con el tiempo a medida que desarrollamos el juego. -First, we have a type for the candies, with a field name, parent, points and Type. +En primer lugar, tenemos un tipo para los caramelos, con un nombre de campo, padre, puntos y Tipo. ```java @Getter(AccessLevel.PACKAGE) @@ -53,7 +54,7 @@ public class Candy { } ``` -The field data for candy types are stored in the JSON file ```candy.json```. New candies can be added just by appending it to this file. +Los datos de campo de los tipos de caramelos se almacenan en el archivo JSON ``candy.json``. Se pueden añadir nuevos caramelos simplemente añadiéndolos a este archivo. ```json {"candies" : [ @@ -103,7 +104,8 @@ The field data for candy types are stored in the JSON file ```candy.json```. New } ``` -The JSON file is parsed, instanciating each Candy type, and storing it in a hashtable. The ```type``` field is matched with the ```Type``` enum defined in the Candy class. +El archivo JSON se analiza, instanciando cada tipo de caramelo y almacenándolo en una tabla hash. El campo ``type`` se compara con el enum ``Type`` definido en la clase Candy. + ```java public class JsonParser { Hashtable candies; @@ -149,17 +151,17 @@ public class JsonParser { } ``` -## In Plain Words +## En palabras sencillas -The Type-Object pattern in Java is a method to encapsulate type-specific properties and behaviors within an object. This design pattern facilitates the addition of new types without necessitating changes to existing code, thereby enhancing codebase expansion and maintenance. +El patrón Tipo-Objeto en Java es un método para encapsular propiedades y comportamientos específicos de un tipo dentro de un objeto. Este patrón de diseño facilita la adición de nuevos tipos sin necesidad de realizar cambios en el código existente, mejorando así la expansión y el mantenimiento de la base de código. -## Wikipedia Says +## Wikipedia dice -While there isn't a specific Wikipedia entry for the Type-Object pattern, it is a commonly used technique in object-oriented programming. This pattern assists in managing objects that share similar characteristics but have different values for those characteristics. It finds widespread use in game development, where numerous types of objects (like enemies) share common behavior but have different properties. +Aunque no existe una entrada específica en Wikipedia para el patrón Tipo-Objeto, se trata de una técnica de uso común en la programación orientada a objetos. Este patrón ayuda a gestionar objetos que comparten características similares pero tienen valores diferentes para esas características. Su uso está muy extendido en el desarrollo de juegos, donde numerosos tipos de objetos (como los enemigos) comparten un comportamiento común pero tienen propiedades diferentes. -## Programmatic Example +## Ejemplo programático -Consider an example involving different types of enemies in a game. Each enemy type has distinct properties like speed, health, and damage. +Consideremos un ejemplo en el que intervienen distintos tipos de enemigos en un juego. Cada tipo de enemigo tiene propiedades distintas, como velocidad, salud y daño. ```java public class EnemyType { @@ -190,19 +192,19 @@ public class Enemy { } ``` -In the above example, `EnemyType` encapsulates type-specific properties (name, speed, health, damage), and `Enemy` uses an instance of `EnemyType` to define its type. This way, you can add as many enemy types as you want without modifying the `Enemy` class. +En el ejemplo anterior, `EnemyType` encapsula propiedades específicas del tipo (nombre, velocidad, salud, daño), y `Enemy` utiliza una instancia de `EnemyType` para definir su tipo. De esta forma, puedes añadir tantos tipos de enemigos como quieras sin modificar la clase "Enemigo". -## Applicability -This pattern can be used when: +## Aplicabilidad +Este patrón puede utilizarse cuando: -* We don’t know what types we will need up front. -* We want to be able to modify or add new types without having to recompile or change code. -* Only difference between the different 'types' of objects is the data, not the behaviour. +* No sabemos de antemano qué tipos vamos a necesitar. +* Queremos ser capaces de modificar o añadir nuevos tipos sin tener que recompilar o cambiar el código. +* La única diferencia entre los diferentes "tipos" de objetos son los datos, no el comportamiento. -## Another example with class diagram +## Otro ejemplo con diagrama de clases ![alt text](./etc/typeobjectpattern.urm.png "Type-Object pattern class diagram") -## Credits +## Créditos * [Game Programming Patterns - Type Object](http://gameprogrammingpatterns.com/type-object.html) * [Types as Objects Pattern](http://www.cs.sjsu.edu/~pearce/modules/patterns/analysis/top.htm) diff --git a/typeobjectpattern/etc/typeobjectpattern.urm.png b/localization/es/typeobjectpattern/etc/typeobjectpattern.urm.png similarity index 100% rename from typeobjectpattern/etc/typeobjectpattern.urm.png rename to localization/es/typeobjectpattern/etc/typeobjectpattern.urm.png diff --git a/localization/es/update-method/README.md b/localization/es/update-method/README.md new file mode 100644 index 000000000000..60d48081f238 --- /dev/null +++ b/localization/es/update-method/README.md @@ -0,0 +1,34 @@ +--- +title: Update Method +shortTitle: Update Method +category: Behavioral +language: es +tag: + - Game programming +--- + +## Propósito +El patrón de método de actualización simula una colección de objetos independientes indicando a cada uno que procese un fotograma de comportamiento a la vez. + +## Explicación +El mundo del juego mantiene una colección de objetos. Cada objeto implementa un método de actualización que simula un fotograma del comportamiento del objeto. En cada fotograma, el juego actualiza cada objeto de la colección. + +Para obtener más información sobre cómo se ejecuta el bucle de juego y cuándo se invocan los métodos de actualización, consulte Patrón de bucle de juego. + +## Diagrama de clases +![alt text](./etc/update-method.urm.png "Update Method pattern class diagram") + +## Aplicabilidad +Si el patrón Game Loop es lo mejor desde el pan rebanado, entonces el patrón Update Method es su mantequilla. Una amplia gama de juegos con entidades vivas con las que el jugador interactúa utilizan este patrón de una forma u otra. Si el juego tiene marines espaciales, dragones, marcianos, fantasmas o atletas, es muy probable que utilice este patrón. + +Sin embargo, si el juego es más abstracto y las piezas móviles se parecen menos a actores vivos y más a piezas de un tablero de ajedrez, este patrón no suele encajar. En un juego como el ajedrez, no necesitas simular todas las piezas concurrentemente, y probablemente no necesites decirle a los peones que se actualicen a sí mismos en cada frame. + +Los métodos de actualización funcionan bien cuando: + +- Su juego tiene un número de objetos o sistemas que necesitan ejecutarse simultáneamente. +- El comportamiento de cada objeto es independiente de los demás. +- Los objetos necesitan ser simulados en el tiempo. + +## Créditos + +* [Game Programming Patterns - Update Method](http://gameprogrammingpatterns.com/update-method.html) diff --git a/localization/es/update-method/etc/update-method.urm.png b/localization/es/update-method/etc/update-method.urm.png new file mode 100644 index 000000000000..ddc47b5fe145 Binary files /dev/null and b/localization/es/update-method/etc/update-method.urm.png differ diff --git a/localization/es/value-object/README.md b/localization/es/value-object/README.md index f53050f57440..6fcc616e6b0e 100644 --- a/localization/es/value-object/README.md +++ b/localization/es/value-object/README.md @@ -1,5 +1,6 @@ --- title: Value Object +shortTitle: Value Object category: Creational language: es tag: diff --git a/localization/es/visitor/README.md b/localization/es/visitor/README.md new file mode 100644 index 000000000000..e79f21122a41 --- /dev/null +++ b/localization/es/visitor/README.md @@ -0,0 +1,233 @@ +--- +title: Visitor +shortTitle: Visitor +category: Behavioral +language: es +tag: + - Gang of Four +--- + +## Propósito + +Representar una operación a realizar sobre los elementos de una estructura de objetos. Visitante permite +definir una nueva operación sin cambiar las clases de los elementos sobre los que opera. + +## Explicación + +Ejemplo del mundo real + +> Considere una estructura de árbol con unidades del ejército. El comandante tiene dos sargentos bajo él y cada sargento +> tiene tres soldados bajo él. Dado que la jerarquía implementa el patrón visitante, podemos +> crear fácilmente nuevos objetos que interactúen con el comandante, los sargentos, los soldados, o todos ellos. + + +Ejemplo del mundo real + +> El patrón Visitante define las operaciones que se pueden realizar en los nodos de la estructura de datos. + +Wikipedia dice + +> En programación orientada a objetos e ingeniería de software, el patrón de diseño visitante es una forma de +> separar un algoritmo de una estructura de objetos sobre la que opera. Un resultado práctico de esta +> separación es la capacidad de añadir nuevas operaciones a estructuras de objetos existentes sin modificarlas. +> las estructuras. + +**Ejemplo programático** + +Dado el ejemplo anterior de la unidad del ejército, primero tenemos los tipos básicos Unit y UnitVisitor. + +```java +public abstract class Unit { + + private final Unit[] children; + + public Unit(Unit... children) { + this.children = children; + } + + public void accept(UnitVisitor visitor) { + Arrays.stream(children).forEach(child -> child.accept(visitor)); + } +} + +public interface UnitVisitor { + + void visit(Soldier soldier); + + void visit(Sergeant sergeant); + + void visit(Commander commander); +} +``` + +Luego tenemos las unidades de hormigón. + +```java +public class Commander extends Unit { + + public Commander(Unit... children) { + super(children); + } + + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "commander"; + } +} + +public class Sergeant extends Unit { + + public Sergeant(Unit... children) { + super(children); + } + + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "sergeant"; + } +} + +public class Soldier extends Unit { + + public Soldier(Unit... children) { + super(children); + } + + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "soldier"; + } +} +``` + +He aquí, pues, algunos visitantes concretos. + +```java +@Slf4j +public class CommanderVisitor implements UnitVisitor { + + @Override + public void visit(Soldier soldier) { + // Do nothing + } + + @Override + public void visit(Sergeant sergeant) { + // Do nothing + } + + @Override + public void visit(Commander commander) { + LOGGER.info("Good to see you {}", commander); + } +} + +@Slf4j +public class SergeantVisitor implements UnitVisitor { + + @Override + public void visit(Soldier soldier) { + // Do nothing + } + + @Override + public void visit(Sergeant sergeant) { + LOGGER.info("Hello {}", sergeant); + } + + @Override + public void visit(Commander commander) { + // Do nothing + } +} + +@Slf4j +public class SoldierVisitor implements UnitVisitor { + + @Override + public void visit(Soldier soldier) { + LOGGER.info("Greetings {}", soldier); + } + + @Override + public void visit(Sergeant sergeant) { + // Do nothing + } + + @Override + public void visit(Commander commander) { + // Do nothing + } +} +``` + +Por último, podemos mostrar el poder de los visitantes en acción. + +```java +commander.accept(new SoldierVisitor()); +commander.accept(new SergeantVisitor()); +commander.accept(new CommanderVisitor()); +``` + +Salida del programa: + +``` +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Hello sergeant +Hello sergeant +Good to see you commander +``` + +## Diagrama de clases + +![alt text](./etc/visitor_1.png "Visitor") + +## Aplicabilidad + +Utilice el patrón Visitor cuando + +* Una estructura de objetos contiene muchas clases de objetos con diferentes interfaces, y desea realizar operaciones en estos objetos que dependen de sus clases concretas. +* Es necesario realizar muchas operaciones distintas y no relacionadas en los objetos de una estructura de objetos, y se desea evitar "contaminar" sus clases con estas operaciones. Visitor permite mantener juntas las operaciones relacionadas definiéndolas en una clase. Cuando la estructura de objetos es compartida por muchas aplicaciones, utilice Visitor para poner las operaciones sólo en aquellas aplicaciones que las necesiten. +* Las clases que definen la estructura del objeto raramente cambian, pero a menudo se quieren definir nuevas operaciones sobre la estructura. Cambiar las clases de la estructura del objeto requiere redefinir la interfaz para todos los visitantes, lo que es potencialmente costoso. Si las clases de la estructura del objeto cambian a menudo, probablemente sea mejor definir las operaciones en esas clases. + +## Tutoriales + +* [Refactoring Guru](https://refactoring.guru/design-patterns/visitor) +* [Dzone](https://dzone.com/articles/design-patterns-visitor) +* [Sourcemaking](https://sourcemaking.com/design_patterns/visitor) + +## Usos conocidos + +* [Apache Wicket](https://github.com/apache/wicket) component tree, Mire [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) +* [javax.lang.model.element.AnnotationValue](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValue.html) y [AnnotationValueVisitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValueVisitor.html) +* [javax.lang.model.element.Element](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Element.html) y [Element Visitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/ElementVisitor.html) +* [java.nio.file.FileVisitor](http://docs.oracle.com/javase/8/docs/api/java/nio/file/FileVisitor.html) + +## Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/es/visitor/etc/visitor.png b/localization/es/visitor/etc/visitor.png new file mode 100644 index 000000000000..5bbee60eeabf Binary files /dev/null and b/localization/es/visitor/etc/visitor.png differ diff --git a/localization/es/visitor/etc/visitor_1.png b/localization/es/visitor/etc/visitor_1.png new file mode 100644 index 000000000000..de5285d7fdc3 Binary files /dev/null and b/localization/es/visitor/etc/visitor_1.png differ diff --git a/localization/fr/anti-corruption-layer/README.md b/localization/fr/anti-corruption-layer/README.md new file mode 100644 index 000000000000..46a62ae1ffe3 --- /dev/null +++ b/localization/fr/anti-corruption-layer/README.md @@ -0,0 +1,180 @@ +--- +title: "Patron de conception Couche Anti-Corruption en Java : Assurer l'intégrité du système face aux systèmes hérités" +shortTitle: Couche Anti-Corruption +description: "Découvrez comment le patron de conception de la Couche Anti-Corruption (Anti-Corruption Layer Pattern) aide à découpler les sous-systèmes, à prévenir la corruption des données et à faciliter une intégration fluide dans les applications Java." +category: Integration +language: fr +tag: + - Architecture + - Decoupling + - Integration + - Isolation + - Layered architecture + - Migration + - Modernization + - Refactoring + - Wrapping +--- + +## Aussi connu sous le nom de + +* ACL +* Couche d'interface +* Couche de traduction + +## Intention du modèle de conception de la couche anti-corruption + +La couche anti-corruption (ACL) est un patron de conception essentiel dans le développement Java, en particulier pour l'intégration des systèmes et le maintien de l'intégrité des données. Implémentez une couche de façade ou d'adaptateur entre différents sous-systèmes qui ne partagent pas la même sémantique. Elle traduit entre différents formats de données et systèmes, garantissant que l'intégration entre les systèmes ne conduit pas à une corruption de la logique métier ou à une perte d'intégrité des données. + +## Explication détaillée du patron de conception Couche Anti-Corruption avec des exemples concrets + +Exemple concret + +> Cet exemple montre comment la couche anti-corruption garantit une intégration fluide entre les systèmes hérités et les plateformes modernes, essentiel pour maintenir l'intégrité de la logique métier lors de la migration du système. +> +> Imaginez une grande entreprise de vente au détail qui passe d'un ancien logiciel de gestion des stocks à une nouvelle plateforme moderne. Le système hérité est utilisé depuis des décennies et contient des règles métier complexes et des formats de données incompatibles avec le nouveau système. Au lieu de connecter directement le nouveau système à l'ancien, l'entreprise implémente une couche anti-corruption (ACL). +> +> L'ACL agit comme un médiateur, traduisant et adaptant les données entre les deux systèmes. Lorsque le nouveau système demande des données d'inventaire, l'ACL traduit la requête dans un format compréhensible par le système hérité, récupère les données, puis les traduit à nouveau dans un format adapté au nouveau système. Cette approche garantit que le nouveau système reste indépendant des complexités du système hérité, évitant ainsi la corruption des données et de la logique métier tout en facilitant une transition en douceur. + +En termes simples + +> Le patron de conception Couche Anti-Corruption protège un système des complexités et des changements des systèmes externes en fournissant une couche de traduction intermédiaire. + +[La documentation de Microsoft](https://learn.microsoft.com/fr-fr/azure/architecture/patterns/anti-corruption-layer) dit + +> Implémentez une couche de façade ou d’adaptateur entre différents sous-systèmes qui ne partagent pas la même sémantique. Cette couche traduit les requêtes qu’un sous-système envoie à l’autre sous-système. Utilisez ce modèle pour vous assurer que la conception d’une application n’est pas limitée par les dépendances aux sous-systèmes externes. Ce modèle a d’abord été décrit par Eric Evans dans Domain-Driven Design (Conception orientée domaine). + +## Exemple programmatique du patron de conception de Couche Anti-Corruption en Java + +Le modèle ACL en Java fournit une couche intermédiaire qui traduit les formats de données, garantissant que l'intégration entre différents systèmes ne conduit pas à une corruption des données. + +Voici 2 systèmes de commande de magasin : `Legacy` et `Modern`. + +Ces systèmes ont des modèles de domaine différents et doivent fonctionner simultanément. Comme ils travaillent de manière indépendante, les commandes peuvent provenir soit du système `Legacy`, soit du système `Modern`. Par conséquent, le système qui reçoit la commande `orderLegacy` doit vérifier si cette commande est valide et non présente dans l'autre système. Ensuite, il peut placer la commande `orderLegacy` dans son propre système. + +Mais pour cela, le système doit connaître le modèle de domaine de l'autre système et pour éviter cela, la couche anti-corruption (ACL) est introduite. L'ACL est une couche qui traduit le modèle de domaine du système `Legacy` en celui du système `Modern` et inversement. De plus, elle masque toutes les autres opérations avec l'autre système, découplant les systèmes. + +Modèle de domaine du système `Legacy` : + +```java +public class LegacyOrder { + private String id; + private String customer; + private String item; + private String qty; + private String price; +} +``` + +Modèle de domaine du système `Modern` : + +```java +public class ModernOrder { + private String id; + private Customer customer; + + private Shipment shipment; + + private String extra; +} + +public class Customer { + private String address; +} + +public class Shipment { + private String item; + private String qty; + private String price; +} +``` + +Couche anti-corruption : + +```java +public class AntiCorruptionLayer { + + @Autowired + private ModernShop modernShop; + + @Autowired + private LegacyShop legacyShop; + + public Optional findOrderInModernSystem(String id) { + return modernShop.findOrder(id).map(o -> /* map to legacyOrder*/); + } + + public Optional findOrderInLegacySystem(String id) { + return legacyShop.findOrder(id).map(o -> /* map to modernOrder*/); + } + +} +``` + +La connexion entre les systèmes. Chaque fois que le système `Legacy` ou `Modern` doit communiquer avec l'autre, l'ACL doit être utilisée pour éviter de corrompre le modèle de domaine actuel. L'exemple ci-dessous montre comment le système `Legacy` passe une commande avec une validation du système `Modern`. + +```java +public class LegacyShop { + @Autowired + private AntiCorruptionLayer acl; + + public void placeOrder(LegacyOrder legacyOrder) throws ShopException { + + String id = legacyOrder.getId(); + + Optional orderInModernSystem = acl.findOrderInModernSystem(id); + + if (orderInModernSystem.isPresent()) { + // la commande est déjà dans le système moderne + } else { + // passer la commande dans le système actuel + } + } +} +``` +## Quand utiliser le patron de conception Couche Anti-Corruption en Java + +Utilisez ce modèle lorsque : + +* Une migration est prévue en plusieurs étapes, mais l'intégration entre les nouveaux et les anciens systèmes doit être maintenue +* Deux ou plusieurs sous-systèmes ont une sémantique différentes, mais doivent tout de même communiquer +* Lors de l'intégration avec des systèmes hérités ou des systèmes externes où une intégration directe pourrait polluer le modèle de domaine du nouveau système +* Dans des scénarios où différents sous-systèmes au sein d'un système plus large utilisent différents formats ou structures de données +* Lorsqu'il est nécessaire de garantir un découplage lâche entre différents sous-systèmes ou services externes pour faciliter la maintenance et la scalabilité + +## Tutoriels sur le patron de conception Couche Anti-Corruption en Java + +* [Couche Anti-Corruption](https://learn.microsoft.com/fr-fr/azure/architecture/patterns/anti-corruption-layer) +* [Patron de conception Couche Anti-Corruption](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html) + +## Applications concrètes du modèle de couche anti-corruption en Java + +* Architectures microservices où les services individuels doivent communiquer sans être étroitement couplés aux schémas de données des autres +* Intégration des systèmes d'entreprise, notamment lors de l'intégration des systèmes modernes avec des systèmes hérités +* Dans des contextes délimités au sein de la Domain-Driven Design (DDD) pour maintenir l'intégrité d'un modèle de domaine lors de l'interaction avec des systèmes ou sous-systèmes externes + +## Avantages et compromis du modèle de couche anti-corruption + +Avantages : + +* Protège l'intégrité du modèle de domaine en fournissant une frontière claire +* Favorise le découplage lâche entre les systèmes, rendant le système plus résilient aux changements des systèmes externes +* Facilite un code plus propre et plus facile à maintenir en isolant le code d'intégration de la logique métier + +Compromis : + +* Introduit une complexité supplémentaire et un potentiel de surcharge de performance en raison du processus de traduction +* Nécessite un effort supplémentaire dans la conception et la mise en œuvre pour s'assurer que la couche est efficace sans devenir un goulet d'étranglement +* Peut entraîner une duplication des modèles s'il n'est pas géré avec soin + +## Patron de conception Java associés + +* [Adaptateur (Adapter)](https://java-design-patterns.com/patterns/adapter/): La couche anti-corruption peut être implémentée en utilisant le modèle Adaptateur pour traduire entre différents formats ou structures de données +* [Façade](https://java-design-patterns.com/patterns/facade/): La couche anti-corruption peut être vue comme une forme spécialisée du modèle Façade utilisée pour isoler différents sous-systèmes +* [Passerelle (Gateway)](https://java-design-patterns.com/patterns/gateway/): La couche anti-corruption peut être utilisée comme une passerelle vers des systèmes externes pour fournir une interface unifiée + +## Références et Crédits + +* [Domain-Driven Design : Résoudre la complexité au cœur du logiciel](https://amzn.to/3vptcJz) +* [Implémentation du Domain-Driven Design](https://amzn.to/3ISOSRA) +* [Patterns of Enterprise Application Architecture (Modèles d'architecture des applications d'entreprise)](https://amzn.to/3WfKBPR) diff --git a/localization/fr/builder/README.md b/localization/fr/builder/README.md new file mode 100644 index 000000000000..1c731520993a --- /dev/null +++ b/localization/fr/builder/README.md @@ -0,0 +1,205 @@ +--- +title: "Patron de conception 'Builder' en Java: Créer des objets personnalisés avec clarté" +shortTitle: Builder +description: "Découvrez le patron de conception Builder en Java, un puissant modèle de création qui simplifie la construction d'objets. Apprenez à séparer la construction d'un objet complexe de sa représentation avec des exemples pratiques et des cas d'utilisation." +category: Creational +language: fr +tag: + - Gang of Four + - Instantiation + - Object composition +--- + +## Intention du Patron de conception Builder + +Le patron de conception Builder en Java, un modèle de création fondamental, permet de construire des objets complexes étape par étape. Il sépare la construction d'un objet complexe de sa représentation afin que le même processus de construction puisse créer différentes représentations. + +## Explication détaillée du patron de conception Builder avec des exemples réels + +Exemple réel + +> Le patron de conception Builder en Java est particulièrement utile dans des scénarios où la création d'un objet nécessite de nombreux paramètres. +> +> Imaginez que vous créez un sandwich personnalisable dans un restaurant. Le patron de conception Builder dans ce contexte impliquerait un `SandwichBuilder` qui vous permet de spécifier chaque composant du sandwich, comme le type de pain, de viande, de fromage, de légumes, et de condiments. Au lieu de savoir comment assembler le sandwich de zéro, vous utilisez le `SandwichBuilder` pour ajouter chaque composant désiré étape par étape, assurant que vous obtenez exactement le sandwich que vous souhaitez. Cette séparation entre la construction et la représentation finale du produit garantit que le même processus de construction peut produire différents types de sandwiches selon les composants spécifiés. + +En termes simples + +> Il vous permet de créer différentes versions d'un objet tout en évitant la "pollution" des constructeurs. Utile lorsque plusieurs variantes d'un objet sont possibles, ou lorsqu'il y a beaucoup d'étapes impliquées dans la création d'un objet. + +Wikipedia dit + +> Le modèle Builder est un modèle de conception de création d'objets qui vise à trouver une solution au problème des constructeurs à paramètres multiples, aussi connu sous le nom d'antipattern du constructeur télescopique. + +Avec cela à l'esprit, expliquons ce qu'est l'antipattern du constructeur télescopique. À un moment donné, nous avons tous rencontré un constructeur comme celui-ci: + +```java +public Hero(Profession profession,String name,HairType hairType,HairColor hairColor,Armor armor,Weapon weapon){ + // Assignation des valeurs +} +``` + +Comme vous pouvez le voir, le nombre de paramètres du constructeur peut rapidement devenir écrasant, ce qui rend difficile de comprendre leur agencement. De plus, cette liste de paramètres pourrait continuer à s'allonger si vous décidez d'ajouter plus d'options à l'avenir. Cela s'appelle l'antipattern du constructeur télescopique. + +## Exemple programmatique du Modèle Builder en Java + +Dans cet exemple du patron de conception Builder en Java, nous construisons différents types d'objets `Hero` avec des attributs variés. + +Imaginez un générateur de personnages pour un jeu de rôle. La solution la plus simple est de laisser l'ordinateur générer le personnage pour vous. Cependant, si vous préférez sélectionner manuellement les détails du personnage comme la profession, le genre, la couleur des cheveux, etc., la création de personnage devient un processus étape par étape qui se termine une fois toutes les sélections effectuées. + +Une approche plus logique est d'utiliser le modèle Builder. D'abord, considérons le `Hero` que nous voulons créer: + +```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 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; + } +} +```` + +Ensuite, nous avons le `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; + + 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; + } + + public Builder withHairType(HairType hairType) { + this.hairType = hairType; + return this; + } + + public Builder withHairColor(HairColor hairColor) { + this.hairColor = hairColor; + return this; + } + + public Builder withArmor(Armor armor) { + this.armor = armor; + return this; + } + + public Builder withWeapon(Weapon weapon) { + this.weapon = weapon; + return this; + } + + public Hero build() { + return new Hero(this); + } +} +``` + +Ensuite, il peut être utilisé comme suit: + +```java + public static void main(String[] args) { + + var mage = new Hero.Builder(Profession.MAGE, "Riobard") + .withHairColor(HairColor.BLACK) + .withWeapon(Weapon.DAGGER) + .build(); + LOGGER.info(mage.toString()); + + var warrior = new Hero.Builder(Profession.WARRIOR, "Amberjill") + .withHairColor(HairColor.BLOND) + .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD) + .build(); + LOGGER.info(warrior.toString()); + + var thief = new Hero.Builder(Profession.THIEF, "Desmond") + .withHairType(HairType.BALD) + .withWeapon(Weapon.BOW) + .build(); + LOGGER.info(thief.toString()); +} +``` + +Sortie du programme: + +``` +16:28:06.058 [main] INFO com.iluwatar.builder.App -- This is a mage named Riobard with black hair and wielding a dagger. +16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a warrior named Amberjill with blond long curly hair wearing chain mail and wielding a sword. +16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a thief named Desmond with bald head and wielding a bow. +``` + +## Diagramme de classe du Modèle Builder + +![Builder](./etc/builder.urm.png "Diagramme de classe du patron Builder") + +## Quand utiliser le patron de conception Builder en Java + +Utilisez le modèle Builder lorsque + +* Le patron de conception Builder est idéal pour les applications Java nécessitant la création d'objets complexes. +* L'algorithme pour créer un objet complexe doit être indépendant des parties qui composent l'objet et de la façon dont elles sont assemblées. +* Le processus de construction doit permettre différentes représentations de l'objet construit. +* Il est particulièrement utile lorsqu'un produit nécessite beaucoup d'étapes pour être créé et lorsque ces étapes doivent être exécutées dans un ordre spécifique. + +## Tutoriels Java sur le Modèle Builder + + +* [Patron de conception Builder (DigitalOcean)](https://www.journaldev.com/1425/builder-design-pattern-in-java) +* [Builder (Refactoring Guru)](https://refactoring.guru/design-patterns/builder) +* [Exploring Joshua Bloch’s Builder design pattern in Java (Java Magazine)](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) + +## Applications réelles du Modèle Builder en Java + +* StringBuilder en Java pour construire des chaînes de caractères. +* java.lang.StringBuffer utilisé pour créer des objets chaîne modifiables. +* Java.nio.ByteBuffer ainsi que des tampons similaires comme FloatBuffer, IntBuffer, et d'autres. +* javax.swing.GroupLayout.Group#addComponent() +* Divers constructeurs d'IHM dans les IDE qui créent des composants d'interface utilisateur. +* Toutes les implémentations de [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) +* [Constructeurs Apache Camel](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) + +## Avantages et inconvénients du Modèle Builder + +Avantages: + +* Plus de contrôle sur le processus de construction par rapport à d'autres modèles de création. +* Permet de construire des objets étape par étape, de différer les étapes de construction ou d'exécuter des étapes de manière récursive. +* Peut construire des objets qui nécessitent un assemblage complexe de sous-objets. Le produit final est détaché des parties qui le composent ainsi que de leur processus d'assemblage. +* Principe de responsabilité unique. Vous pouvez isoler le code de construction complexe de la logique métier du produit. + +Inconvénients: + +* La complexité générale du code peut augmenter car le modèle nécessite la création de plusieurs nouvelles classes. +* Peut augmenter l'utilisation de la mémoire en raison de la nécessité de créer plusieurs objets builder. + +## Patrons de conception Java liés + +* [Fabrique abstraite (Abstract Factory)](https://java-design-patterns.com/patterns/abstract-factory/): Peut être utilisé en conjonction avec le patron de conception Builder pour construire des parties d'un objet complexe. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Les builders créent souvent des objets à partir d'un prototype. +* [Step Builder](https://java-design-patterns.com/patterns/step-builder/): Il s'agit d'une variation du modèle Builder qui génère un objet complexe en utilisant une approche étape par étape. Le modèle Step Builder est un bon choix lorsque vous avez besoin de construire un objet avec un grand nombre de paramètres optionnels, et que vous souhaitez éviter l'antipattern du constructeur télescopique. + +## Références et crédits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/localization/fr/business-delegate/README.md b/localization/fr/business-delegate/README.md new file mode 100644 index 000000000000..ba44559ea1fb --- /dev/null +++ b/localization/fr/business-delegate/README.md @@ -0,0 +1,187 @@ +--- +title: "Business Delegate Pattern en Java: Simplifier l'intéraction avec les services métiers" +shortTitle: Business Delegate +description: "Apprenez à connaître le patron Business Delegate en Java. Ce patron ajoute une niveau d'abstraction en la couche de présentation et la couche métier, assurant un couplage faible et une intéraction simplifiée entre les sevices." +category: Structural +language: fr +tag: + - Business + - Decoupling + - Delegation + - Enterprise patterns + - Layered architecture +--- + +## Aussi connu sous le nom de + +* Service Representative + +## But du modèle de conception Business-Delegate + +Le modèle Business Delegate est un modèle de conception structurel en Java qui ajoute un niveau d'abstraction en la couche de pésentation et la couche métier. Utiliser ce modèle garanti un couplage faible entre les couches et encapsule les informations sur la localisation, la connexion et les interactions des objets métiers qui composent l'application. + +## Explication détaillée et exemples concrets + +Exemple concret + +> Dans une application d'entreprise utilisant Java EE, le modèle Business-Delegate permet de gérer les interactions entre les différents services commerciaux. +> +> Imaginez un restaurant où le personnel de service sert d'intermédiaire entre les clients et la cuisine. Lorsqu'un client passe une commande, le serveur l'apporte à la cuisine, transmet toute demande spécifique et ramène ensuite le plat préparé au client. Le personnel de salle fait abstraction de la complexité des opérations de cuisine, ce qui permet aux chefs de se concentrer uniquement sur la préparation des plats, sans avoir à interagir directement avec les clients. Cette configuration permet au service à la clientèle (niveau de présentation) et à la cuisine (service commercial) de fonctionner de manière indépendante et efficace. Le personnel de service joue le rôle de délégué commercial, en gérant la communication et en garantissant des interactions harmonieuses entre les deux secteurs distincts. + +En clair + +> Le Business-Delegate ajoute une couche d'abstraction entre le niveau de présentation et le niveau commercial. + +Définition Wikipedia : + +> Business Delegate est un modèle de conception Java EE. Ce modèle vise à réduire le couplage entre les services métier et le niveau de présentation connecté, et à masquer les détails de mise en œuvre des services (y compris la consultation et l'accessibilité de l'architecture EJB). Les délégués commerciaux agissent comme un adaptateur permettant d'invoquer des objets commerciaux à partir du niveau de présentation. + +## Example de programme + +Le code Java suivant montre comment mettre en œuvre le modèle de délégué commercial. Ce modèle est particulièrement utile dans les applications nécessitant un couplage lâche et une interaction efficace entre les services. + +Une application pour téléphone portable promet de diffuser en continu n'importe quel film existant sur votre appareil. Elle capture la chaîne de recherche de l'utilisateur et la transmet au Business Delegate. Ce dernier sélectionne le service de streaming vidéo le plus approprié et lit la vidéo à partir de là. + +Tout d'abord, nous disposons d'une abstraction pour les services de streaming vidéo et de quelques implémentations. + +```java +public interface VideoStreamingService { + void doProcessing(); +} + +@Slf4j +public class NetflixService implements VideoStreamingService { + @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"); + } +} +``` + +Ensuite, nous avons un service de recherche qui décide quel service de streaming vidéo utiliser. + +```java + +@Setter +public class BusinessLookup { + + private NetflixService netflixService; + private YouTubeService youTubeService; + + public VideoStreamingService getBusinessService(String movie) { + if (movie.toLowerCase(Locale.ROOT).contains("die hard")) { + return netflixService; + } else { + return youTubeService; + } + } +} +``` + +Le Business Delegate utilise un service de recherche pour acheminer les requêtes vers le bon service de streaming. + +```java + +@Setter +public class BusinessDelegate { + + private BusinessLookup lookupService; + + public void playbackMovie(String movie) { + VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie); + videoStreamingService.doProcessing(); + } +} +``` + +Le client mobile se sert du Business Delegate pour appeler la couche métier. + +```java +public class MobileClient { + + private final BusinessDelegate businessDelegate; + + public MobileClient(BusinessDelegate businessDelegate) { + this.businessDelegate = businessDelegate; + } + + public void playbackMovie(String movie) { + businessDelegate.playbackMovie(movie); + } +} +``` + +Finalement, l'exemple concret en 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); + + // create the client and use the business delegate + var client = new MobileClient(businessDelegate); + client.playbackMovie("Die Hard 2"); + client.playbackMovie("Maradona: The Greatest Ever"); +} +``` + +Voici la sortie affichée dans la console. + +``` +21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing +21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing +``` + +## Diagramme de classe + +![Business Delegate](./etc/business-delegate.urm.png "Business Delegate") + +## Quand utiliser ce modèle de conception + +Utilisez le modèle Business Delegate lorsque + +* Vous avez besoin d'un couplage lâche entre les niveaux de présentation et d'activité ou vous avez besoin d'abstraire les recherches de services. +* Vous souhaitez organiser des appels à de multiples services commerciaux. +* Vous voulez encapsuler les recherches et les appels de services. +* Il est nécessaire d'abstraire et d'encapsuler la communication entre le niveau client et les services métier. + +## Tutoriels + +* [Design Patterns - Business Delegate Pattern (TutorialsPoint)](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) + +## Applications concrêtes + +* Applications d'entreprise utilisant Java EE (Java Platform, Enterprise Edition) +* Applications nécessitant un accès à distance aux services de l'entreprised + +## Avantages et inconvénients + +Avantages : + +* Découplage des niveaux de présentation et d'activité : Permet au niveau client et aux services commerciaux d'évoluer indépendamment. +* Transparence de l'emplacement : Les clients ne sont pas affectés par les changements de localisation ou d'instanciation des services commerciaux. +* Réutilisation et évolutivité : Les objets Business Delegate peuvent être réutilisés par plusieurs clients, et le modèle prend en charge l'équilibrage de la charge et l'évolutivité. + +Inconvénients : + +* Complexity: Introduces additional layers and abstractions, which may increase complexity. +* Performance Overhead: The additional indirection may incur a slight performance penalty. + +## Réferences et crédits + +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/localization/ko/adapter/README.md b/localization/ko/adapter/README.md index 9e9bd30c3ca7..0b8201b56c4a 100644 --- a/localization/ko/adapter/README.md +++ b/localization/ko/adapter/README.md @@ -1,5 +1,6 @@ --- title: Adapter +shortTitle: Adapter category: Structural language: ko tag: diff --git a/localization/ko/builder/README.md b/localization/ko/builder/README.md index bce0841a1713..6189c74fb160 100644 --- a/localization/ko/builder/README.md +++ b/localization/ko/builder/README.md @@ -1,5 +1,6 @@ --- title: Builder +shortTitle: Builder category: Creational language: ko tag: diff --git a/localization/ko/callback/README.md b/localization/ko/callback/README.md index b11c6dea68e7..caaa4a39874f 100644 --- a/localization/ko/callback/README.md +++ b/localization/ko/callback/README.md @@ -1,5 +1,6 @@ --- title: Callback +shortTitle: Callback category: Idiom language: ko tag: diff --git a/localization/ko/decorater/README.md b/localization/ko/decorater/README.md index c68a6d1f99e6..5a21841fb4ca 100644 --- a/localization/ko/decorater/README.md +++ b/localization/ko/decorater/README.md @@ -1,5 +1,6 @@ --- title: Decorator +shortTitle: Decorator category: Structural language: en tag: diff --git a/localization/ko/event-driven-architecture/README.md b/localization/ko/event-driven-architecture/README.md index 0296076db582..88f5a395c731 100644 --- a/localization/ko/event-driven-architecture/README.md +++ b/localization/ko/event-driven-architecture/README.md @@ -1,5 +1,6 @@ --- title: Event Driven Architecture +shortTitle: Event Driven Architecture category: Architectural language: ko tag: diff --git a/localization/ko/event-sourcing/README.md b/localization/ko/event-sourcing/README.md index 1d3bd584061c..e71882970e59 100644 --- a/localization/ko/event-sourcing/README.md +++ b/localization/ko/event-sourcing/README.md @@ -1,5 +1,6 @@ --- title: Event Sourcing +shortTitle: Event Sourcing category: Architectural language: ko tag: diff --git a/localization/ko/facade/README.md b/localization/ko/facade/README.md index 57f7ffed43fa..5ab53835a7f0 100644 --- a/localization/ko/facade/README.md +++ b/localization/ko/facade/README.md @@ -1,5 +1,6 @@ --- title: Facade +shortTitle: Facade category: Structural language: ko tag: diff --git a/localization/ko/factory-method/README.md b/localization/ko/factory-method/README.md new file mode 100644 index 000000000000..9a1cf3e709d5 --- /dev/null +++ b/localization/ko/factory-method/README.md @@ -0,0 +1,131 @@ +--- +title: "Factory Method" +shortTitle: Factory Method +category: Creational +language: ko +tag: + - Gang of Four +--- + +## 또한 ~으로 알려진 + +* Virtual Constructor + +## 의도 + +팩토리 메소드 패턴을 사용하여 객체를 생성하기 위한 인터페이스를 정의하고, 어떤 클래스를 인스턴스화할지는 하위 클래스가 결정하도록 합니다. 이 생성 설계 패턴은 클래스가 인스턴스 생성을 하위 클래스로 위임하여 코드 유연성과 유지보수성을 향상시킬 수 있습니다. + +## 설명 + +예시 + +> 한 물류 회사가 다양한 유형의 패키지를 배송해야 한다고 가정해 봅시다. 일반, 급송, 그리고 대형 배송 패키지가 있습니다. 이 회사는 중앙 시스템을 통해 배송 요청을 처리하지만, 각 패키지 유형이 어떻게 처리되는지에 대한 구체적인 사항은 알지 못합니다. 이를 관리하기 위해 이 회사는 팩토리 메소드 패턴을 사용합니다. +> +> 이 구조에서 중앙 클래스인 `DeliveryRequest`에는 `createPackage()`라는 메서드가 있습니다. 이 메서드는 `StandardDelivery`, `ExpressDelivery`, `OversizedDelivery`와 같은 하위 클래스에서 오버라이딩되며, 각 하위 클래스는 해당 패키지 유형을 생성하고 관리하는 방법을 알고 있습니다. 이렇게 함으로써 중앙 시스템은 각 패키지 유형이 어떻게 생성되고 처리되는지에 대한 세부 사항을 알 필요 없이 배송 요청을 처리할 수 있어, 유연하고 쉽게 유지보수가 가능합니다. + +평범하게 말하자면, + +> 이 패턴은 인스턴스 생성 로직을 자식 클래스에 위임하는 방법을 제공합니다. + +Wikipedia 말에 의하면 + +> 클래스 기반 프로그래밍에서, 팩토리 메소드 패턴은 생성 패턴 중 하나로, 생성할 객체의 구체적인 클래스를 명시하지 않고 객체를 생성하는 문제를 해결합니다. 이를 위해 생성자는 직접 호출하지 않고, 팩토리 메소드를 호출하여 객체를 생성하는 방식을 사용합니다. 팩토리 메소드는 인터페이스에 명시되어 자식 클래스가 구현하거나, 기본 클래스에 구현되어 파생 클래스에서 필요에 따라 오버라이딩할 수 있습니다. + +## 프로그램 코드 예제 + +팩토리 메소드 접근 방식은 다음 예제에서 볼 수 있듯이 유연하고 유지 관리 가능한 코드를 달성하기 위한 자바 디자인 패턴에서 중추적인 역할을 합니다. + +대장장이가 무기를 제작합니다. 엘프는 엘프족 무기를, 오크는 오크족 무기를 필요로 합니다. 고객에 따라 적합한 유형의 대장장이가 소환됩니다. + +우선, `Blacksmith` 인터페이스와 이를 구현한 몇 가지 클래스들이 있습니다. + +```java +public interface Blacksmith { + Weapon manufactureWeapon(WeaponType weaponType); +} + +public class ElfBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ELFARSENAL.get(weaponType); + } +} + +public class OrcBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ORCARSENAL.get(weaponType); + } +} +``` + +고객이 오면, 올바른 유형의 대장장이가 소환되어 요청된 무기를 제작합니다. + +```java +public static void main(String[] args) { + + Blacksmith blacksmith = new OrcBlacksmith(); + Weapon weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + weapon = blacksmith.manufactureWeapon(WeaponType.AXE); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + + blacksmith = new ElfBlacksmith(); + weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + weapon = blacksmith.manufactureWeapon(WeaponType.AXE); + LOGGER.info(MANUFACTURED, blacksmith, weapon); +} +``` + +프로그램 실행 결과 +``` +06:40:07.269 [main] INFO com.iluwatar.factory.method.App -- The orc blacksmith manufactured an orcish spear +06:40:07.271 [main] INFO com.iluwatar.factory.method.App -- The orc blacksmith manufactured an orcish axe +06:40:07.272 [main] INFO com.iluwatar.factory.method.App -- The elf blacksmith manufactured an elven spear +06:40:07.272 [main] INFO com.iluwatar.factory.method.App -- The elf blacksmith manufactured an elven axe +``` + +## 클래스 다이어그램 + +![alt text](./etc/factory-method.urm.png "Factory Method Class Diagram") + +## 적용 가능성 + +자바에서 팩토리 메소드 패턴을 사용하는 경우: + +* 클래스가 생성해야 할 객체의 클래스를 예측할 수 없을 때 +* 클래스가 생성할 객체를 자식 클래스가 지정하도록 하고 싶을 때 +* 여러 보조 자식 클래스 중 하나에 책임을 위임하고, 어느 자식 클래스가 해당 작업을 담당하는지에 대한 정보를 국지화하고 싶을 때 + +## 실제 사례 + +* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-) +* [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-) +* [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) +* Frameworks that run application components, configured dynamically at runtime. + +## 장단점 + +장점: + +* 팩토리 메소드 패턴은 하위 클래스에 대한 후크(hooks)를 제공하여 코드의 유연성과 유지보수성을 높입니다. +* 병렬적인 클래스 계층 구조를 연결합니다. +* 애플리케이션 특화 클래스를 코드에 직접 포함할 필요가 없어집니다. 코드가 제품 인터페이스와만 상호작용하므로, 사용자 정의 구체 클래스와 쉽게 연동할 수 있습니다. + +단점: + +* 확장된 팩토리 메소드를 구현하기 위해 새로운 하위 클래스를 추가해야 하므로 코드가 복잡해질 수 있습니다. + +## 관련 패턴 + +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): 팩토리 메소드는 종종 추상 팩토리 패턴 내에서 호출됩니다. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): 프로토타입 클래스를 복제한 새 인스턴스를 반환하는 팩토리 메소드. + +## 크레딧 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0Rk5y) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG) +* [Patterns of Enterprise Application Architecture](https://amzn.to/4b2ZxoM) diff --git a/localization/ko/factory-method/etc/factory-method.urm.png b/localization/ko/factory-method/etc/factory-method.urm.png new file mode 100644 index 000000000000..7c97aff91ee6 Binary files /dev/null and b/localization/ko/factory-method/etc/factory-method.urm.png differ diff --git a/localization/ko/factory/README.md b/localization/ko/factory/README.md index 25bac7ae8597..48d126195c86 100644 --- a/localization/ko/factory/README.md +++ b/localization/ko/factory/README.md @@ -1,5 +1,6 @@ --- title: Factory +shortTitle: Factory category: Creational language: ko tag: diff --git a/localization/ko/iterator/README.md b/localization/ko/iterator/README.md index 480c50fe5a09..fdf662efb1e6 100644 --- a/localization/ko/iterator/README.md +++ b/localization/ko/iterator/README.md @@ -1,5 +1,6 @@ --- title: Iterator +shortTitle: Iterator categories: Behavioral language: ko tag: diff --git a/localization/ko/observer/README.md b/localization/ko/observer/README.md index b2fb7977e051..983d6f6fbc27 100644 --- a/localization/ko/observer/README.md +++ b/localization/ko/observer/README.md @@ -1,5 +1,6 @@ --- title: Observer +shortTitle: Observer category: Behavioral language: ko tag: diff --git a/localization/ko/prototype/README.md b/localization/ko/prototype/README.md index a993d23a75f1..498ce4c21251 100644 --- a/localization/ko/prototype/README.md +++ b/localization/ko/prototype/README.md @@ -1,5 +1,6 @@ --- title: Prototype +shortTitle: Prototype category: Creational language: ko tag: diff --git a/localization/ko/proxy/README.md b/localization/ko/proxy/README.md index 77b2b2607541..0b371ac1de00 100644 --- a/localization/ko/proxy/README.md +++ b/localization/ko/proxy/README.md @@ -1,5 +1,6 @@ --- title: Proxy +shortTitle: Proxy category: Structural language: ko tag: diff --git a/localization/ko/singleton/README.md b/localization/ko/singleton/README.md index 35e56e648c00..2b5c8502a909 100644 --- a/localization/ko/singleton/README.md +++ b/localization/ko/singleton/README.md @@ -1,5 +1,6 @@ --- title: Singleton +shortTitle: Singleton category: Creational language: ko tag: diff --git a/localization/ko/strategy/README.md b/localization/ko/strategy/README.md index 93d737868538..f604962d1f6b 100644 --- a/localization/ko/strategy/README.md +++ b/localization/ko/strategy/README.md @@ -1,5 +1,6 @@ --- title: Strategy +shortTitle: Strategy category: Behavioral language: ko tag: @@ -122,7 +123,7 @@ public class LambdaStrategy { public enum Strategy implements DragonSlayingStrategy { MeleeStrategy(() -> LOGGER.info( - "With your Excalibur you severe the dragon's head!")), + "With your Excalibur you sever the dragon's head!")), ProjectileStrategy(() -> LOGGER.info( "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), SpellStrategy(() -> LOGGER.info( diff --git a/localization/ko/template-method/README.md b/localization/ko/template-method/README.md index 6a4650861da7..d2dbde0a0656 100644 --- a/localization/ko/template-method/README.md +++ b/localization/ko/template-method/README.md @@ -1,5 +1,6 @@ --- title: Template Method +shortTitle: Template Method category: Behavioral language: ko tag: diff --git a/localization/ko/visitor/README.md b/localization/ko/visitor/README.md index c533b0a2fec9..6b8408c3ff41 100644 --- a/localization/ko/visitor/README.md +++ b/localization/ko/visitor/README.md @@ -1,5 +1,6 @@ --- title: Visitor +shortTitle: Visitor category: Behavioral language: ko tag: diff --git a/localization/pt/singleton/README.md b/localization/pt/singleton/README.md new file mode 100644 index 000000000000..83604d959368 --- /dev/null +++ b/localization/pt/singleton/README.md @@ -0,0 +1,109 @@ +--- +title: "Singleton Pattern in Java: Implementing Global Access Points in Java Applications" +shortTitle: Singleton +description: "Explore the Singleton Pattern in Java with our comprehensive guide. Learn how to implement efficient object management for your Java applications, ensuring optimal use of resources and easy access with examples and detailed explanations." +category: Creational +language: pt +tag: + - Gang of Four + - Instantiation + - Lazy initialization + - Resource management +--- + +## Também conhecido como + +* Single Instance + +## Propósito + +Assegurar que a classe Java possua somente uma instância e forneça um ponto global de acesso a essa instância Singleton. + +## Explicação + +Exemplo do Mundo Real + +> Uma analogia do mundo real para o padrão Singleton é um governo emitindo um passaporte. Em um país, cada cidadão pode receber apenas um passaporte válido por vez. O escritório de passaportes garante que nenhum passaporte duplicado seja emitido para a mesma pessoa. Sempre que um cidadão precisa viajar, ele deve usar este passaporte único, que serve como o identificador único e globalmente reconhecido para suas credenciais de viagem. Este acesso controlado e gerenciamento de instância exclusivo espelham como o padrão Singleton garante o gerenciamento eficiente de objetos em aplicativos Java. + +Em outras palavras + +> Assegura que somente um objeto de classe em particular seja criado. + +De acordo com a Wikipédia + +> Na engenharia de software, o padrão Singleton é um padrão de design de software que restringe a instanciação de uma classe a um objeto. Isso é útil quando exatamente um objeto é necessário para coordenar ações no sistema. + +## Exemplo Programático + +Joshua Bloch, Effective Java 2nd Edition pg.18 + +> Um tipo Enum de elemento único é a melhor maneira de implementar um Singleton + +```java +public enum EnumIvoryTower { + INSTANCE +} +``` + +Então para usar: + +```java + var enumIvoryTower1 = EnumIvoryTower.INSTANCE; + var enumIvoryTower2 = EnumIvoryTower.INSTANCE; + LOGGER.info("enumIvoryTower1={}", enumIvoryTower1); + LOGGER.info("enumIvoryTower2={}", enumIvoryTower2); +``` + +A saída do console + +``` +enumIvoryTower1=com.iluwatar.singleton.EnumIvoryTower@1221555852 +enumIvoryTower2=com.iluwatar.singleton.EnumIvoryTower@1221555852 +``` + +## Quando usar + +Use o padrão Singleton quando + +* Deve haver exatamente uma instância de uma classe, e ela deve ser acessível aos clientes a partir de um ponto de acesso bem conhecido +* Quando a única instância deve ser extensível por herança, e os clientes devem ser capazes de usar uma instância estendida sem modificar seu código + +## Aplicações do mundo real + +* A classe de Log +* Classes de configuração em muitos aplicativos +* Pools de conexão +* Gerenciador de arquivos +* [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) +* [java.awt.Desktop#getDesktop()](http://docs.oracle.com/javase/8/docs/api/java/awt/Desktop.html#getDesktop--) +* [java.lang.System#getSecurityManager()](http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getSecurityManager--) + +## Benefícios e desafios do padrão Singleton + +Benefícios: + +* Acesso controlado à instância única. +* Poluição reduzida do namespace. +* Permite refinamento de operações e representação. +* Permite um número variável de instâncias (se desejado mais de uma). +* Mais flexível do que operações de classe. + +Desafios: + +* Difícil de testar devido ao estado global. +* Gerenciamento de ciclo de vida potencialmente mais complexo. +* Pode introduzir gargalos se usado em um contexto concorrente sem sincronização cuidadosa. + +## Outros Padrōes de Projeto Relacionados + +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Geralmente usado para garantir que uma classe tenha apenas uma instância. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): O padrão Singleton pode ser implementado usando o padrāo Factory para encapsular a lógica de criação. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Evita a necessidade de criar instâncias, pode ser usado em conjunto com o Singleton para gerenciar instâncias únicas. + +## Referências e Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/localization/vi/builder/README.md b/localization/vi/builder/README.md new file mode 100644 index 000000000000..b463ed5c64bc --- /dev/null +++ b/localization/vi/builder/README.md @@ -0,0 +1,166 @@ +--- +title: Builder +category: Creational +language: vi +tag: + - Gang of Four +--- + +## Mục tiêu + +Tách rời việc xây dựng một đối tượng phức tạp khỏi cách thể hiện của nó, để cùng một quá trình xây dựng có thể tạo ra các cách thể hiện khác nhau. + +## Giải thích + +Ví dụ thực tế + +> Hãy tưởng tượng một trình tạo nhân vật cho trò chơi nhập vai. Cách đơn giản nhất là để máy tính tạo nhân vật cho bạn. Nếu bạn muốn chọn các chi tiết về nhân vật như nghề nghiệp, giới tính, màu tóc, v.v. thì việc tạo nhân vật trở thành một quá trình từng bước và hoàn tất khi tất cả các lựa chọn đã sẵn sàng. + +Nói một cách đơn giản + +> Mẫu Builder cho phép bạn tạo ra các phiên bản khác nhau của một đối tượng trong khi tránh việc dùng hàm khởi tạo. Mẫu này hữu ích khi có thể có nhiều phiên bản của một đối tượng. Hoặc khi có nhiều bước liên quan tới việc tạo một đối tượng. + +Theo Wikipedia + +> Mẫu Builder là một mẫu thiết kế phần mềm dùng để tạo ra đối tượng, với mục đích tìm ra giải pháp cho phản thiết kế hàm khởi tạo quá nhiều tham số (telescoping constructor antipattern). + +Để giải thích rõ hơn về lỗi phản thiết kế hàm khởi tạo có quá nhiều tham số là gì. Ở một thời điểm nào đó, tất cả chúng ta đều đã thấy một hàm khởi tạo như bên dưới: + +```java +public Hero(Profession profession,String name,HairType hairType,HairColor hairColor,Armor armor,Weapon weapon){ + } +``` + +Như bạn có thể thấy, số lượng tham số của hàm khởi tạo có thể nhanh chóng trở nên rất nhiều, và có thể trở nên khó khăn cho việc sắp xếp các tham số. Hơn nữa, danh sách tham số này có thể tiếp tục tăng lên nếu bạn muốn bổ sung thêm tùy chọn trong tương lai. Đây được gọi là lỗi phản thiết kế hàm khởi tạo có quá nhiều tham số. + +**Ví dụ lập trình** + +Một giải pháp hợp lý hơn là sử dụng mẫu Builder. Trước hết, chúng ta có nhân vật anh hùng mà chúng ta muốn tạo ra: + +```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 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; + } +} +``` + +Sau đó chúng ta có lớp 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; + + 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; + } + + public Builder withHairType(HairType hairType) { + this.hairType = hairType; + return this; + } + + public Builder withHairColor(HairColor hairColor) { + this.hairColor = hairColor; + return this; + } + + public Builder withArmor(Armor armor) { + this.armor = armor; + return this; + } + + public Builder withWeapon(Weapon weapon) { + this.weapon = weapon; + return this; + } + + public Hero build() { + return new Hero(this); + } +} +``` + +Sau đó nó có thể được sử dụng như sau: + +```java +var mage=new Hero.Builder(Profession.MAGE,"Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); +``` + +## Class diagram + +![alt text](../../../builder/etc/builder.urm.png "Builder class diagram") + +## Ứng dụng + +Sử dụng mẫu Builder khi: + +* Thuật toán để tạo một đối tượng phức tạp phải độc lập với các bộ phận tạo nên đối tượng đó và cách chúng liên kết với nhau. +* Quá trình xây dựng phải cho phép các cách thể hiện khác nhau cho đối tượng được tạo ra. +* Đặc biệt hữu ích khi một sản phẩm cần nhiều bước để được tạo ra và khi các bước này cần được thực hiện theo một trình tự nhất định. + +## Các trường hợp sử dụng đã biết + +* Java.lang.StringBuilder +* Java.nio.ByteBuffer cũng như các bộ đệm tương tự như FloatBuffer, IntBuffer, và các bộ đệm khác. +* javax.swing.GroupLayout.Group#addComponent() + +## Hậu quả + +Ưu điểm: + +* Kiểm soát tốt hơn quá trình xây dựng đối tượng so với các mẫu tạo đối tượng khác. +* Hỗ trợ xây dựng đối tượng theo từng bước, hoãn các bước xây dựng hoặc chạy các bước đệ quy. +* Có thể xây dựng các đối tượng đòi hỏi quá trình kết hợp phức tạp các đối tượng con. Sản phẩm cuối cùng được tách rời khỏi các bộ phận tạo nên nó, cũng như quá trình liên kết chúng. +* Nguyên tắc Đơn Trách nhiệm. Bạn có thể tách rời mã xây dựng phức tạp khỏi logic nghiệp vụ của sản phẩm. + +Nhược điểm: + +* Tổng thể độ phức tạp của code có thể tăng lên vì mẫu đòi hỏi phải tạo nhiều lớp mới. + +## Hướng dẫn + +* [Refactoring Guru](https://refactoring.guru/design-patterns/builder) +* [Oracle Blog](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) +* [Journal Dev](https://www.journaldev.com/1425/builder-design-pattern-in-java) + +## Các trường hợp sử dụng đã biết + +* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) +* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) cũng như các bộ đệm tương tự như FloatBuffer, IntBuffer, v.v. +* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) +* Tất cả các triển khai của [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) +* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) + +## Mẫu liên quan + +* [Step Builder](https://java-design-patterns.com/patterns/step-builder/) là một biến thể của mẫu Builder tạo ra một đối tượng phức tạp theo cách tiếp cận từng bước. Mẫu Step Builder là một lựa chọn tốt khi bạn cần xây dựng một đối tượng với nhiều tham số tùy chọn, và bạn muốn tránh lỗi phản thiết kế hàm khởi tạo có quá nhiều tham số. + +## Ghi nhận + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) \ No newline at end of file diff --git a/localization/vi/game-loop/README.md b/localization/vi/game-loop/README.md new file mode 100644 index 000000000000..31c8fad0b5bb --- /dev/null +++ b/localization/vi/game-loop/README.md @@ -0,0 +1,275 @@ +--- +title: Game Loop (Vòng lặp trò chơi) +category: Behavioral +language: vi +tag: + - Concurrency + - Event-driven + - Game programming + - Performance +--- + +## Còn được gọi là + +* Game Cycle (Chu kỳ trò chơi) +* Main Game Loop (Vòng lặp trò chơi chính) + +## Mục tiêu + +Mẫu thiết kế Game Loop nhằm thực thi liên tục của một trò chơi, trong đó mỗi chu kỳ vòng lặp xử lý đầu vào, cập nhật trạng thái trò chơi, và hiển thị trạng thái trò chơi lên màn hình, duy trì trải nghiệm chơi mượt mà và tương tác. + +## Giải thích + +Ví dụ thực tế + +> Game loop là quá trình chính của tất cả các luồng hiển thị trò chơi. Nó hiện diện trong tất cả các trò chơi hiện đại. Nó điều khiển việc xử lý đầu vào, cập nhật trạng thái nội bộ, hiển thị, xử lý trí tuệ nhân tạo và tất cả các quá trình khác. + +Nói một cách đơn giản + +> Mẫu Game Loop đảm bảo rằng thời gian trò chơi thực thi với tốc độ bằng nhau trong tất cả các cấu hình phần cứng khác nhau. + +Theo Wikipedia + +> Thành phần trung tâm của bất kỳ trò chơi nào, từ góc độ lập trình, đều là game loop. Game loop cho phép trò chơi chạy trơn tru bất kể đầu vào của người dùng hoặc thiếu đầu vào. + +**Ví dụ lập trình** + +Hãy bắt đầu với một thứ gì đó đơn giản. Đây là lớp `Bullet` (Đạn). Đạn sẽ di chuyển trong trò chơi của chúng ta. Để dễ hình dung, ta cho nó có vị trí 1 chiều. + +```java +public class Bullet { + + private float position; + + public Bullet() { + position = 0.0f; + } + + public float getPosition() { + return position; + } + + public void setPosition(float position) { + this.position = position; + } +} +``` + +`GameController` có trách nhiệm di chuyển các đối tượng trong trò chơi, bao gồm cả đạn đã đề cập bên trên. + +```java +public class GameController { + + protected final Bullet bullet; + + public GameController() { + bullet = new Bullet(); + } + + public void moveBullet(float offset) { + var currentPosition = bullet.getPosition(); + bullet.setPosition(currentPosition + offset); + } + + public float getBulletPosition() { + return bullet.getPosition(); + } +} +``` + +Bây giờ chúng ta tạo ra một vòng lặp trò chơi (game loop). Hoặc thực tế trong ví dụ minh họa này, chúng ta có loại 3 vòng lặp trò chơi khác nhau. Hãy xem lớp cha `GameLoop` trước. + +```java +public enum GameStatus { + + RUNNING, + STOPPED +} + +public abstract class GameLoop { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected volatile GameStatus status; + + protected GameController controller; + + private Thread gameThread; + + public GameLoop() { + controller = new GameController(); + status = GameStatus.STOPPED; + } + + public void run() { + status = GameStatus.RUNNING; + gameThread = new Thread(this::processGameLoop); + gameThread.start(); + } + + public void stop() { + status = GameStatus.STOPPED; + } + + public boolean isGameRunning() { + return status == GameStatus.RUNNING; + } + + protected void processInput() { + try { + var lag = new Random().nextInt(200) + 50; + Thread.sleep(lag); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + + protected void render() { + var position = controller.getBulletPosition(); + logger.info("Current bullet position: " + position); + } + + protected abstract void processGameLoop(); +} +``` + +Đây là cách hiện thực đầu tiên, `FrameBasedGameLoop` (Vòng lặp trò chơi dựa trên khung hình): + +```java +public class FrameBasedGameLoop extends GameLoop { + + @Override + protected void processGameLoop() { + while (isGameRunning()) { + processInput(); + update(); + render(); + } + } + + protected void update() { + controller.moveBullet(0.5f); + } +} +``` + +Cuối cùng, chúng ta sẽ thực thi tất cả các vòng lặp trò chơi. + +```java + try { + LOGGER.info("Start frame-based game loop:"); + var frameBasedGameLoop = new FrameBasedGameLoop(); + frameBasedGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + frameBasedGameLoop.stop(); + LOGGER.info("Stop frame-based game loop."); + + LOGGER.info("Start variable-step game loop:"); + var variableStepGameLoop = new VariableStepGameLoop(); + variableStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + variableStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + + LOGGER.info("Start fixed-step game loop:"); + var fixedStepGameLoop = new FixedStepGameLoop(); + fixedStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + fixedStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } +``` + +Kết quả chương trình: + +```Start frame-based game loop: +Current bullet position: 0.5 +Current bullet position: 1.0 +Current bullet position: 1.5 +Current bullet position: 2.0 +Current bullet position: 2.5 +Current bullet position: 3.0 +Current bullet position: 3.5 +Current bullet position: 4.0 +Current bullet position: 4.5 +Current bullet position: 5.0 +Current bullet position: 5.5 +Current bullet position: 6.0 +Stop frame-based game loop. +Start variable-step game loop: +Current bullet position: 6.5 +Current bullet position: 0.038 +Current bullet position: 0.084 +Current bullet position: 0.145 +Current bullet position: 0.1805 +Current bullet position: 0.28 +Current bullet position: 0.32 +Current bullet position: 0.42549998 +Current bullet position: 0.52849996 +Current bullet position: 0.57799995 +Current bullet position: 0.63199997 +Current bullet position: 0.672 +Current bullet position: 0.778 +Current bullet position: 0.848 +Current bullet position: 0.8955 +Current bullet position: 0.9635 +Stop variable-step game loop. +Start fixed-step game loop: +Current bullet position: 0.0 +Current bullet position: 1.086 +Current bullet position: 0.059999995 +Current bullet position: 0.12999998 +Current bullet position: 0.24000004 +Current bullet position: 0.33999994 +Current bullet position: 0.36999992 +Current bullet position: 0.43999985 +Current bullet position: 0.5399998 +Current bullet position: 0.65999967 +Current bullet position: 0.68999964 +Current bullet position: 0.7299996 +Current bullet position: 0.79999954 +Current bullet position: 0.89999944 +Current bullet position: 0.98999935 +Stop variable-step game loop. +``` + +## Class diagram + +![alt text](../../../game-loop/etc/game-loop.urm.png "Game Loop pattern class diagram") + +## Ứng dụng + +Mẫu Game Loop được áp dụng trong mô phỏng thời gian thực và trò chơi, nơi trạng thái cần được cập nhật liên tục và nhất quán để đáp ứng đầu vào của người dùng và các sự kiện khác. + +## Các trường hợp ứng dụng đã biết + +* Trò chơi video, cả 2D và 3D, trên các nền tảng khác nhau. +* Mô phỏng thời gian thực yêu cầu tỷ lệ khung hình ổn định để cập nhật logic và hiển thị. + +## Hậu quả + +Ưu điểm: + +* Đảm bảo trò chơi thực thi mượt mà và nhất quán. +* Tạo điều kiện thuận lợi cho việc đồng bộ giữa trạng thái trò chơi, đầu vào người dùng và hiển thị lên màn hình. +* Cung cấp một cấu trúc rõ ràng để các nhà phát triển trò chơi quản lý chuyển động và thời gian trong trò chơi. + +Nhược điểm: + +* Có thể dẫn đến vấn đề hiệu suất nếu vòng lặp không được quản lý tốt, đặc biệt là trong các hàm cập nhật hoặc hiển thị tốn nhiều tài nguyên. +* Khó khăn trong việc quản lý tỷ lệ khung hình (frame rates) khác nhau trên các phần cứng khác nhau. + +## Các mẫu liên quan + +* [State](https://java-design-patterns.com/patterns/state/): Thường được sử dụng trong vòng lặp trò chơi để quản lý các trạng thái khác nhau của trò chơi (ví dụ: menu, đang chơi, tạm dừng). Mối quan hệ nằm ở việc quản lý hành vi cụ thể của trạng thái và chuyển đổi trạng thái một cách mượt mà trong vòng lặp trò chơi. +* [Observer](https://java-design-patterns.com/patterns/observer/): Hữu ích trong vòng lặp trò chơi để xử lý sự kiện, nơi các thực thể trò chơi có thể đăng ký và phản ứng với các sự kiện (ví dụ: va chạm, ghi điểm). + +## Tham khảo + +* [Game Programming Patterns - Game Loop](http://gameprogrammingpatterns.com/game-loop.html) +* [Game Programming Patterns](https://www.amazon.com/gp/product/0990582906/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0990582906&linkId=1289749a703b3fe0e24cd8d604d7c40b) +* [Game Engine Architecture, Third Edition](https://www.amazon.com/gp/product/1138035459/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1138035459&linkId=94502746617211bc40e0ef49d29333ac) +* [Real-Time Collision Detection](https://amzn.to/3W9Jj8T) \ No newline at end of file diff --git a/localization/zh/abstract-document/README.md b/localization/zh/abstract-document/README.md index 95781a1c70ea..cf19dfcf84cb 100644 --- a/localization/zh/abstract-document/README.md +++ b/localization/zh/abstract-document/README.md @@ -1,5 +1,6 @@ --- title: Abstract Document +shortTitle: Abstract Document category: Structural language: zh tag: diff --git a/localization/zh/abstract-factory/README.md b/localization/zh/abstract-factory/README.md index 676b7f75ea82..eca9f0d1fb30 100644 --- a/localization/zh/abstract-factory/README.md +++ b/localization/zh/abstract-factory/README.md @@ -1,5 +1,6 @@ --- title: Abstract Factory +shortTitle: Abstract Factory category: Creational language: zh tag: diff --git a/localization/zh/active-object/README.md b/localization/zh/active-object/README.md index f149af2b87d7..ba35892e81c2 100644 --- a/localization/zh/active-object/README.md +++ b/localization/zh/active-object/README.md @@ -1,5 +1,6 @@ --- title: Active Object +shortTitle: Active Object category: Concurrency language: zh tag: diff --git a/localization/zh/acyclic-visitor/README.md b/localization/zh/acyclic-visitor/README.md index e5edd89b7b97..ea76ae45cf46 100644 --- a/localization/zh/acyclic-visitor/README.md +++ b/localization/zh/acyclic-visitor/README.md @@ -1,5 +1,6 @@ --- title: Acyclic Visitor +shortTitle: Acyclic Visitor category: Behavioral language: zh tag: diff --git a/localization/zh/adapter/README.md b/localization/zh/adapter/README.md index d00a7f9795d6..09bccc9c6b17 100644 --- a/localization/zh/adapter/README.md +++ b/localization/zh/adapter/README.md @@ -1,5 +1,6 @@ --- title: Adapter +shortTitle: Adapter category: Structural language: zh tag: diff --git a/localization/zh/aggregator-microservices/README.md b/localization/zh/aggregator-microservices/README.md index e61e76e2c84b..bbdf83ec4981 100644 --- a/localization/zh/aggregator-microservices/README.md +++ b/localization/zh/aggregator-microservices/README.md @@ -1,5 +1,6 @@ --- title: Aggregator Microservices +shortTitle: Aggregator Microservices category: Architectural language: zh tag: diff --git a/localization/zh/ambassador/README.md b/localization/zh/ambassador/README.md index deb43ecb0405..1ddc4188c9d6 100644 --- a/localization/zh/ambassador/README.md +++ b/localization/zh/ambassador/README.md @@ -1,5 +1,6 @@ --- title: Ambassador +shortTitle: Ambassador category: Structural language: zh tag: diff --git a/localization/zh/api-gateway/README.md b/localization/zh/api-gateway/README.md index 7cbac9913078..7b8996a5769c 100644 --- a/localization/zh/api-gateway/README.md +++ b/localization/zh/api-gateway/README.md @@ -1,5 +1,6 @@ --- title: API Gateway +shortTitle: API Gateway category: Architectural language: zh tag: diff --git a/localization/zh/arrange-act-assert/README.md b/localization/zh/arrange-act-assert/README.md index 7a95d3ba681a..c115589509bf 100644 --- a/localization/zh/arrange-act-assert/README.md +++ b/localization/zh/arrange-act-assert/README.md @@ -1,5 +1,6 @@ --- title: Arrange/Act/Assert +shortTitle: Arrange/Act/Assert category: Idiom language: zh tag: diff --git a/localization/zh/async-method-invocation/README.md b/localization/zh/async-method-invocation/README.md index 746a2d65cf47..b8bc62ae3577 100644 --- a/localization/zh/async-method-invocation/README.md +++ b/localization/zh/async-method-invocation/README.md @@ -1,5 +1,6 @@ --- title: Async Method Invocation +shortTitle: Async Method Invocation category: Concurrency language: zh tag: diff --git a/localization/zh/balking/README.md b/localization/zh/balking/README.md index c59e8635a3af..de7c25717f89 100644 --- a/localization/zh/balking/README.md +++ b/localization/zh/balking/README.md @@ -1,5 +1,6 @@ --- title: Balking +shortTitle: Balking category: Concurrency language: zh tag: diff --git a/localization/zh/bridge/README.md b/localization/zh/bridge/README.md index 8cc508f61a09..53771df805da 100644 --- a/localization/zh/bridge/README.md +++ b/localization/zh/bridge/README.md @@ -1,5 +1,6 @@ --- title: Bridge +shortTitle: Bridge category: Structural language: zh tag: diff --git a/localization/zh/builder/README.md b/localization/zh/builder/README.md index 51f672346b68..87f581a2c420 100644 --- a/localization/zh/builder/README.md +++ b/localization/zh/builder/README.md @@ -1,5 +1,6 @@ --- title: Builder +shortTitle: Builder category: Creational language: zh tag: diff --git a/localization/zh/business-delegate/README.md b/localization/zh/business-delegate/README.md index 9280be21502e..337e90ea6ea4 100644 --- a/localization/zh/business-delegate/README.md +++ b/localization/zh/business-delegate/README.md @@ -1,5 +1,6 @@ --- title: Business Delegate +shortTitle: Business Delegate category: Structural language: zh tag: diff --git a/localization/zh/bytecode/README.md b/localization/zh/bytecode/README.md index 48e1c213344a..e7b4a5717b5a 100644 --- a/localization/zh/bytecode/README.md +++ b/localization/zh/bytecode/README.md @@ -1,5 +1,6 @@ --- title: Bytecode +shortTitle: Bytecode category: Behavioral language: zh tag: diff --git a/localization/zh/caching/README.md b/localization/zh/caching/README.md index 3df919cebe61..4d0c1b930829 100644 --- a/localization/zh/caching/README.md +++ b/localization/zh/caching/README.md @@ -1,5 +1,6 @@ --- title: Caching +shortTitle: Caching category: Behavioral language: zh tag: diff --git a/localization/zh/callback/README.md b/localization/zh/callback/README.md index f6216c53296d..937de469a077 100644 --- a/localization/zh/callback/README.md +++ b/localization/zh/callback/README.md @@ -1,5 +1,6 @@ --- title: Callback +shortTitle: Callback category: Idiom language: zh tag: diff --git a/localization/zh/chain/README.md b/localization/zh/chain/README.md index 54791cb858da..63c7f2b39f78 100644 --- a/localization/zh/chain/README.md +++ b/localization/zh/chain/README.md @@ -1,5 +1,6 @@ --- title: Chain of responsibility +shortTitle: Chain of responsibility category: Behavioral language: zh tag: diff --git a/localization/zh/circuit-breaker/README.md b/localization/zh/circuit-breaker/README.md index fba8c383d405..6658a2f1a15f 100644 --- a/localization/zh/circuit-breaker/README.md +++ b/localization/zh/circuit-breaker/README.md @@ -1,5 +1,6 @@ --- title: Circuit Breaker +shortTitle: Circuit Breaker category: Behavioral language: zh tag: diff --git a/localization/zh/cloud-static-content-hosting/README.md b/localization/zh/cloud-static-content-hosting/README.md index 3be30cff9c1a..78f696d533dd 100644 --- a/localization/zh/cloud-static-content-hosting/README.md +++ b/localization/zh/cloud-static-content-hosting/README.md @@ -1,5 +1,6 @@ --- title: Static Content Hosting +shortTitle: Static Content Hosting category: Cloud language: zh tag: diff --git a/localization/zh/collection-pipeline/README.md b/localization/zh/collection-pipeline/README.md index 8d604a77c9e9..725067421486 100644 --- a/localization/zh/collection-pipeline/README.md +++ b/localization/zh/collection-pipeline/README.md @@ -1,5 +1,6 @@ --- title: Collection Pipeline +shortTitle: Collection Pipeline category: Functional language: zh tag: diff --git a/localization/zh/combinator/README.md b/localization/zh/combinator/README.md index 2e92be4eaec2..79ac664f4ee9 100644 --- a/localization/zh/combinator/README.md +++ b/localization/zh/combinator/README.md @@ -1,5 +1,6 @@ --- title: Combinator +shortTitle: Combinator category: Idiom language: zh tag: diff --git a/localization/zh/command/README.md b/localization/zh/command/README.md index 9c34bca0e08e..d8b93b363e91 100644 --- a/localization/zh/command/README.md +++ b/localization/zh/command/README.md @@ -1,5 +1,6 @@ --- title: Command +shortTitle: Command category: Behavioral language: zh tag: diff --git a/localization/zh/commander/README.md b/localization/zh/commander/README.md index 9ee832f1b7e0..8d75337d9e4d 100644 --- a/localization/zh/commander/README.md +++ b/localization/zh/commander/README.md @@ -1,5 +1,6 @@ --- title: Commander +shortTitle: Commander category: Concurrency language: zh tag: diff --git a/localization/zh/composite-entity/README.md b/localization/zh/composite-entity/README.md index a7e11c053e7a..aa904246ab75 100644 --- a/localization/zh/composite-entity/README.md +++ b/localization/zh/composite-entity/README.md @@ -1,5 +1,6 @@ --- title: Composite Entity +shortTitle: Composite Entity category: Structural language: zh tag: diff --git a/localization/zh/composite/README.md b/localization/zh/composite/README.md index 7ee840bac962..88d05c9cc6fb 100644 --- a/localization/zh/composite/README.md +++ b/localization/zh/composite/README.md @@ -1,5 +1,6 @@ --- title: Composite +shortTitle: Composite category: Structural language: zh tag: diff --git a/localization/zh/converter/README.md b/localization/zh/converter/README.md index face00f9fa2e..2164f712c74b 100644 --- a/localization/zh/converter/README.md +++ b/localization/zh/converter/README.md @@ -1,5 +1,6 @@ --- title: Converter +shortTitle: Converter category: Creational language: zh tag: diff --git a/localization/zh/crtp/README.md b/localization/zh/crtp/README.md index 80632021a70e..297c89fdccbb 100644 --- a/localization/zh/crtp/README.md +++ b/localization/zh/crtp/README.md @@ -1,5 +1,6 @@ --- title: Curiously Recurring Template Pattern +shortTitle: Curiously Recurring Template Pattern language: zh category: Structural tag: diff --git a/localization/zh/dao/README.md b/localization/zh/dao/README.md index 9b0ad4de9f42..0d9370e83618 100644 --- a/localization/zh/dao/README.md +++ b/localization/zh/dao/README.md @@ -1,5 +1,6 @@ --- title: Data Access Object +shortTitle: Data Access Object category: Architectural language: zh tag: diff --git a/localization/zh/data-bus/README.md b/localization/zh/data-bus/README.md index a5e2b72943f0..5ce08a8ca22f 100644 --- a/localization/zh/data-bus/README.md +++ b/localization/zh/data-bus/README.md @@ -1,5 +1,6 @@ --- title: Data Bus +shortTitle: Data Bus category: Architectural language: zh tag: diff --git a/localization/zh/data-mapper/README.md b/localization/zh/data-mapper/README.md index 191b82a8f930..59388b364e75 100644 --- a/localization/zh/data-mapper/README.md +++ b/localization/zh/data-mapper/README.md @@ -1,5 +1,6 @@ --- title: Data Mapper +shortTitle: Data Mapper category: Architectural language: zh tag: diff --git a/localization/zh/data-transfer-object/README.md b/localization/zh/data-transfer-object/README.md index 8a7410f852fd..38a637aec091 100644 --- a/localization/zh/data-transfer-object/README.md +++ b/localization/zh/data-transfer-object/README.md @@ -1,5 +1,6 @@ --- title: Data Transfer Object +shortTitle: Data Transfer Object category: Architectural language: zh tag: diff --git a/localization/zh/decorator/README.md b/localization/zh/decorator/README.md index 256662f23b41..a1a46ef83e69 100644 --- a/localization/zh/decorator/README.md +++ b/localization/zh/decorator/README.md @@ -1,5 +1,6 @@ --- title: Decorator +shortTitle: Decorator category: Structural language: zh tag: diff --git a/localization/zh/delegation/README.md b/localization/zh/delegation/README.md index 43407164d1b8..3932d895d501 100644 --- a/localization/zh/delegation/README.md +++ b/localization/zh/delegation/README.md @@ -1,5 +1,6 @@ --- title: Delegation +shortTitle: Delegation category: Structural language: zh tag: diff --git a/localization/zh/dependency-injection/README.md b/localization/zh/dependency-injection/README.md index 55e8b0392c99..2b0ae7b036b3 100644 --- a/localization/zh/dependency-injection/README.md +++ b/localization/zh/dependency-injection/README.md @@ -1,5 +1,6 @@ --- title: Dependency Injection +shortTitle: Dependency Injection category: Creational language: zh tag: diff --git a/localization/zh/dirty-flag/README.md b/localization/zh/dirty-flag/README.md index e17725ddbc7c..9a7bb8f77c41 100644 --- a/localization/zh/dirty-flag/README.md +++ b/localization/zh/dirty-flag/README.md @@ -1,5 +1,6 @@ --- title: Dirty Flag +shortTitle: Dirty Flag category: Behavioral language: zh tag: diff --git a/localization/zh/double-checked-locking/README.md b/localization/zh/double-checked-locking/README.md index b4cbaecc9597..539127027ec7 100644 --- a/localization/zh/double-checked-locking/README.md +++ b/localization/zh/double-checked-locking/README.md @@ -1,5 +1,6 @@ --- title: Double Checked Locking +shortTitle: Double Checked Locking category: Idiom language: zh tag: diff --git a/localization/zh/facade/README.md b/localization/zh/facade/README.md index c6fd471e8f38..9d8689b8d879 100644 --- a/localization/zh/facade/README.md +++ b/localization/zh/facade/README.md @@ -1,5 +1,6 @@ --- title: Facade +shortTitle: Facade category: Structural language: zh tag: diff --git a/localization/zh/factory-kit/README.md b/localization/zh/factory-kit/README.md index 51183ad3a4a0..d23b081f1950 100644 --- a/localization/zh/factory-kit/README.md +++ b/localization/zh/factory-kit/README.md @@ -1,5 +1,6 @@ --- title: Factory Kit +shortTitle: Factory Kit category: Creational language: zh tag: diff --git a/localization/zh/factory-method/README.md b/localization/zh/factory-method/README.md index e9a6d537dc0b..f8c8b8db1109 100644 --- a/localization/zh/factory-method/README.md +++ b/localization/zh/factory-method/README.md @@ -1,5 +1,6 @@ --- title: Factory Method +shortTitle: Factory Method category: Creational language: zh tag: diff --git a/localization/zh/factory/README.md b/localization/zh/factory/README.md index 8a1de5d876b0..d399436f8443 100644 --- a/localization/zh/factory/README.md +++ b/localization/zh/factory/README.md @@ -1,5 +1,6 @@ --- title: Factory +shortTitle: Factory category: Creational language: zh tag: diff --git a/localization/zh/interpreter/README.md b/localization/zh/interpreter/README.md index f4157b2d7f19..a0dc580ae16c 100644 --- a/localization/zh/interpreter/README.md +++ b/localization/zh/interpreter/README.md @@ -1,5 +1,6 @@ --- title: Interpreter +shortTitle: Interpreter category: Behavioral language: zh tag: diff --git a/localization/zh/iterator/README.md b/localization/zh/iterator/README.md index 5e1f0ebf03bc..14f8fb2d68a3 100644 --- a/localization/zh/iterator/README.md +++ b/localization/zh/iterator/README.md @@ -1,5 +1,6 @@ --- title: Iterator +shortTitle: Iterator category: Behavioral language: zh tag: diff --git a/localization/zh/monitor/README.md b/localization/zh/monitor/README.md index c01389afbc98..f6c6cd25443c 100644 --- a/localization/zh/monitor/README.md +++ b/localization/zh/monitor/README.md @@ -1,5 +1,6 @@ --- title: Monitor +shortTitle: Monitor category: Concurrency language: zh tag: diff --git a/localization/zh/observer/README.md b/localization/zh/observer/README.md index 5b8e0d77b183..90b2583b7d60 100644 --- a/localization/zh/observer/README.md +++ b/localization/zh/observer/README.md @@ -1,5 +1,6 @@ --- title: Observer +shortTitle: Observer category: Behavioral language: zh tag: diff --git a/localization/zh/private-class-data/README.md b/localization/zh/private-class-data/README.md index c7e0d2ec4026..25fe2b880a1c 100644 --- a/localization/zh/private-class-data/README.md +++ b/localization/zh/private-class-data/README.md @@ -1,5 +1,6 @@ --- title: Private Class Data +shortTitle: Private Class Data category: Idiom language: zh tag: diff --git a/localization/zh/producer-consumer/README.md b/localization/zh/producer-consumer/README.md index 5c459ac164a8..cf7f894c4a95 100644 --- a/localization/zh/producer-consumer/README.md +++ b/localization/zh/producer-consumer/README.md @@ -1,5 +1,6 @@ --- title: Producer Consumer +shortTitle: Producer Consumer category: Concurrency language: zh tag: diff --git a/localization/zh/proxy/README.md b/localization/zh/proxy/README.md index 99c85cde8961..f0ed1d5b61dd 100644 --- a/localization/zh/proxy/README.md +++ b/localization/zh/proxy/README.md @@ -1,5 +1,6 @@ --- title: Proxy +shortTitle: Proxy category: Structural language: zh tag: diff --git a/localization/zh/servant/README.md b/localization/zh/servant/README.md index 4889a924a504..467785b619fd 100644 --- a/localization/zh/servant/README.md +++ b/localization/zh/servant/README.md @@ -1,5 +1,6 @@ --- title: Servant +shortTitle: Servant category: Behavioral language: zh tag: diff --git a/localization/zh/sharding/README.md b/localization/zh/sharding/README.md index 00a731fe94dd..5db42563d5c0 100644 --- a/localization/zh/sharding/README.md +++ b/localization/zh/sharding/README.md @@ -1,5 +1,6 @@ --- -title: Sharding +title: Sharding +shortTitle: Sharding category: Behavioral language: zh tag: diff --git a/localization/zh/singleton/README.md b/localization/zh/singleton/README.md index a015d6b49af1..4f9ddc2f54ae 100644 --- a/localization/zh/singleton/README.md +++ b/localization/zh/singleton/README.md @@ -1,5 +1,6 @@ --- title: Singleton +shortTitle: Singleton category: Creational language: zh tag: diff --git a/localization/zh/state/README.md b/localization/zh/state/README.md index ab1eefe7435f..b717ba0e40fd 100644 --- a/localization/zh/state/README.md +++ b/localization/zh/state/README.md @@ -1,5 +1,6 @@ --- title: State +shortTitle: State category: Behavioral language: zh tag: diff --git a/localization/zh/step-builder/README.md b/localization/zh/step-builder/README.md index 5e27f6f1d071..36dd0308a163 100644 --- a/localization/zh/step-builder/README.md +++ b/localization/zh/step-builder/README.md @@ -1,5 +1,6 @@ --- title: Step Builder +shortTitle: Step Builder category: Creational language: zn tag: diff --git a/localization/zh/strategy/README.md b/localization/zh/strategy/README.md index da5e913c80fd..8e6a1f3930df 100644 --- a/localization/zh/strategy/README.md +++ b/localization/zh/strategy/README.md @@ -1,5 +1,6 @@ --- title: Strategy +shortTitle: Strategy category: Behavioral language: zh tag: diff --git a/localization/zh/table-module/README.md b/localization/zh/table-module/README.md index f8b404faa58d..9a9dfed1eb7c 100644 --- a/localization/zh/table-module/README.md +++ b/localization/zh/table-module/README.md @@ -1,5 +1,6 @@ --- title: Table Module +shortTitle: Table Module category: Structural language: zh tag: diff --git a/localization/zh/template-method/README.md b/localization/zh/template-method/README.md index cb51d85e3207..57b4f0a9de2a 100644 --- a/localization/zh/template-method/README.md +++ b/localization/zh/template-method/README.md @@ -1,5 +1,6 @@ --- title: Template method +shortTitle: Template method category: Behavioral language: zh tag: diff --git a/localization/zh/trampoline/README.md b/localization/zh/trampoline/README.md index fd65c5d4c0d8..438d75905170 100644 --- a/localization/zh/trampoline/README.md +++ b/localization/zh/trampoline/README.md @@ -1,5 +1,6 @@ --- title: Trampoline +shortTitle: Trampoline category: Behavioral language: zh tag: diff --git a/localization/zh/unit-of-work/README.md b/localization/zh/unit-of-work/README.md index a957390e824f..5623b42ed1f4 100644 --- a/localization/zh/unit-of-work/README.md +++ b/localization/zh/unit-of-work/README.md @@ -1,5 +1,6 @@ --- title: Unit Of Work +shortTitle: Unit Of Work category: Architectural language: zn tag: diff --git a/localization/zh/update-method/README.md b/localization/zh/update-method/README.md index 64038e34eb8d..01bb51587876 100644 --- a/localization/zh/update-method/README.md +++ b/localization/zh/update-method/README.md @@ -1,5 +1,6 @@ --- title: Update Method +shortTitle: Update Method category: Behavioral language: zn tag: diff --git a/localization/zh/value-object/README.md b/localization/zh/value-object/README.md index 59a76d978fdb..7ce457e690dd 100644 --- a/localization/zh/value-object/README.md +++ b/localization/zh/value-object/README.md @@ -1,5 +1,6 @@ --- title: Value Object +shortTitle: Value Object category: Creational language: zn tag: diff --git a/localization/zh/version-number/README.md b/localization/zh/version-number/README.md index 9495fc0bd3a0..aa0966556fe8 100644 --- a/localization/zh/version-number/README.md +++ b/localization/zh/version-number/README.md @@ -1,5 +1,6 @@ --- title: Version Number +shortTitle: Version Number category: Concurrency language: zh tag: diff --git a/localization/zh/visitor/README.md b/localization/zh/visitor/README.md index d0a4329e52d9..58c4475696e5 100644 --- a/localization/zh/visitor/README.md +++ b/localization/zh/visitor/README.md @@ -1,5 +1,6 @@ --- title: Visitor +shortTitle: Visitor category: Behavioral language: zh tag: diff --git a/lockable-object/README.md b/lockable-object/README.md index f0dbfd60e35c..9ae3caf52834 100644 --- a/lockable-object/README.md +++ b/lockable-object/README.md @@ -1,283 +1,163 @@ --- -title: Lockable Object +title: "Lockable Object Pattern in Java: Implementing Robust Synchronization Mechanisms" +shortTitle: Lockable Object +description: "Learn about the Lockable Object design pattern in Java. Explore its usage, real-world examples, benefits, and how it ensures thread safety and resource management in multithreaded environments." category: Concurrency language: en tag: -- Performance + - Decoupling + - Encapsulation + - Security + - Synchronization + - Thread management --- +## Also known as -## Intent +* Resource Lock +* Mutual Exclusion Object -The lockable object design pattern ensures that there is only one user using the target object. Compared to the built-in synchronization mechanisms such as using the `synchronized` keyword, this pattern can lock objects for an undetermined time and is not tied to the duration of the request. +## Intent of Lockable Object Design Pattern -## Explanation +The Lockable Object pattern in Java aims to control access to a shared resource in a multithreaded environment, ensuring thread safety by providing a mechanism for resource locking, ensuring that only one thread can access the resource at a time. +## Detailed Explanation of Lockable Object Pattern with Real-World Examples Real-world example ->The Sword Of Aragorn is a legendary object that only one creature can possess at the time. ->Every creature in the middle earth wants to possess is, so as long as it's not locked, every creature will fight for it. +> Imagine a shared printer in a busy office as an analogous real-world example of the Lockable Object design pattern in Java. This pattern ensures that only one thread can access the resource at a time, thus maintaining concurrency control and synchronization. Multiple employees need to print documents throughout the day, but the printer can only handle one print job at a time. To manage this, there's a locking system in place—much like a lockable object in programming—that ensures when one person is printing, others must wait their turn. This prevents print jobs from overlapping or interfering with each other, ensuring that each document is printed correctly and in the order it was sent, mirroring the concept of thread synchronization and resource locking in software development. -Under the hood +In plain words ->In this particular module, the SwordOfAragorn.java is a class that implements the Lockable interface. -It reaches the goal of the Lockable-Object pattern by implementing unlock() and unlock() methods using -thread-safety logic. The thread-safety logic is implemented with the built-in monitor mechanism of Java. -The SwordOfAaragorn.java has an Object property called "synchronizer". In every crucial concurrency code block, -it's synchronizing the block by using the synchronizer. +> The Lockable Object design pattern ensures safe access to a shared resource in a multithreaded environment by allowing only one thread to access the resource at a time through locking mechanisms. +Wikipedia says +> In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive that prevents state from being modified or accessed by multiple threads of execution at once. Locks enforce mutual exclusion concurrency control policies, and with a variety of possible methods there exist multiple unique implementations for different applications. -**Programmatic Example** +## Programmatic Example of Lockable Object Pattern in Java + +The Lockable Object pattern is a concurrency control design pattern in Java that allows only one thread to access a shared resource at a time, ensuring mutual exclusion and preventing data corruption. Instead of using the `synchronized` keyword on the methods to be synchronized, the object which implements the Lockable interface handles the request. + +In this example, we have a `SwordOfAragorn` object that implements the `Lockable` interface. Multiple `Creature` objects, represented by `Elf`, `Orc`, and `Human` classes, are trying to acquire the sword. Each `Creature` is wrapped in a `Feind` object that implements `Runnable`, allowing each creature to attempt to acquire the sword in a separate thread. + +Here's the `Lockable` interface: ```java -/** This interface describes the methods to be supported by a lockable object. */ public interface Lockable { - - /** - * Checks if the object is locked. - * - * @return true if it is locked. - */ boolean isLocked(); - - /** - * locks the object with the creature as the locker. - * - * @param creature as the locker. - * @return true if the object was locked successfully. - */ - boolean lock(Creature creature); - - /** - * Unlocks the object. - * - * @param creature as the locker. - */ - void unlock(Creature creature); - - /** - * Gets the locker. - * - * @return the Creature that holds the object. Returns null if no one is locking. - */ Creature getLocker(); - - /** - * Returns the name of the object. - * - * @return the name of the object. - */ - String getName(); + boolean acquire(Creature creature); + void release(Creature creature); } - ``` -We have defined that according to our context, the object must implement the Lockable interface. - -For example, the SwordOfAragorn class: +The `SwordOfAragorn` class implements this interface: ```java public class SwordOfAragorn implements Lockable { - - private Creature locker; - private final Object synchronizer; - private static final String NAME = "The Sword of Aragorn"; - - public SwordOfAragorn() { - this.locker = null; - this.synchronizer = new Object(); - } - - @Override - public boolean isLocked() { - return this.locker != null; - } - - @Override - public boolean lock(@NonNull Creature creature) { - synchronized (synchronizer) { - LOGGER.info("{} is now trying to acquire {}!", creature.getName(), this.getName()); - if (!isLocked()) { - locker = creature; - return true; - } else { - if (!locker.getName().equals(creature.getName())) { - return false; - } - } - } - return false; - } - - @Override - public void unlock(@NonNull Creature creature) { - synchronized (synchronizer) { - if (locker != null && locker.getName().equals(creature.getName())) { - locker = null; - LOGGER.info("{} is now free!", this.getName()); - } - if (locker != null) { - throw new LockingException("You cannot unlock an object you are not the owner of."); - } - } - } - - @Override - public Creature getLocker() { - return this.locker; - } - - @Override - public String getName() { - return NAME; - } + // Implementation details... } ``` -According to our context, there are creatures that are looking for the sword, so must define the parent class: +The `Creature` class and its subclasses (`Elf`, `Orc`, `Human`) represent different creatures that can try to acquire the sword: ```java public abstract class Creature { + // Implementation details... +} + +public class Elf extends Creature { + // Implementation details... +} - private String name; - private CreatureType type; - private int health; - private int damage; - Set instruments; +public class Orc extends Creature { + // Implementation details... +} - protected Creature(@NonNull String name) { - this.name = name; - this.instruments = new HashSet<>(); - } +public class Human extends Creature { + // Implementation details... +} +``` - /** - * Reaches for the Lockable and tried to hold it. - * - * @param lockable as the Lockable to lock. - * @return true of Lockable was locked by this creature. - */ - public boolean acquire(@NonNull Lockable lockable) { - if (lockable.lock(this)) { - instruments.add(lockable); - return true; - } - return false; - } +The `Feind` class wraps a `Creature` and a `Lockable` object, and implements `Runnable`: - /** Terminates the Creature and unlocks all of the Lockable that it posses. */ - public synchronized void kill() { - LOGGER.info("{} {} has been slayed!", type, name); - for (Lockable lockable : instruments) { - lockable.unlock(this); - } - this.instruments.clear(); - } +```java +public class Feind implements Runnable { + private final Creature creature; + private final Lockable target; - /** - * Attacks a foe. - * - * @param creature as the foe to be attacked. - */ - public synchronized void attack(@NonNull Creature creature) { - creature.hit(getDamage()); + public Feind(@NonNull Creature feind, @NonNull Lockable target) { + this.creature = feind; + this.target = target; } - /** - * When a creature gets hit it removed the amount of damage from the creature's life. - * - * @param damage as the damage that was taken. - */ - public synchronized void hit(int damage) { - if (damage < 0) { - throw new IllegalArgumentException("Damage cannot be a negative number"); - } - if (isAlive()) { - setHealth(getHealth() - damage); - if (!isAlive()) { - kill(); - } + @Override + public void run() { + if (!creature.acquire(target)) { + fightForTheSword(creature, target.getLocker(), target); + } else { + LOGGER.info("{} has acquired the sword!", target.getLocker().getName()); } } - /** - * Checks if the creature is still alive. - * - * @return true of creature is alive. - */ - public synchronized boolean isAlive() { - return getHealth() > 0; - } - + // Additional methods... } ``` -As mentioned before, we have classes that extend the Creature class, such as Elf, Orc, and Human. - -Finally, the following program will simulate a battle for the sword: +In the `App` class, multiple `Feind` objects are created and submitted to an `ExecutorService`, each in a separate thread: ```java public class App implements Runnable { - - private static final int WAIT_TIME = 3; - private static final int WORKERS = 2; - private static final int MULTIPLICATION_FACTOR = 3; - - /** - * main method. - * - * @param args as arguments for the main method. - */ - public static void main(String[] args) { - var app = new App(); - app.run(); - } - @Override public void run() { - // The target object for this example. var sword = new SwordOfAragorn(); - // Creation of creatures. List creatures = new ArrayList<>(); - for (var i = 0; i < WORKERS; i++) { - creatures.add(new Elf(String.format("Elf %s", i))); - creatures.add(new Orc(String.format("Orc %s", i))); - creatures.add(new Human(String.format("Human %s", i))); - } - int totalFiends = WORKERS * MULTIPLICATION_FACTOR; + // Creation of creatures... ExecutorService service = Executors.newFixedThreadPool(totalFiends); - // Attach every creature and the sword is a Fiend to fight for the sword. for (var i = 0; i < totalFiends; i = i + MULTIPLICATION_FACTOR) { service.submit(new Feind(creatures.get(i), sword)); service.submit(new Feind(creatures.get(i + 1), sword)); service.submit(new Feind(creatures.get(i + 2), sword)); } - // Wait for program to terminate. - try { - if (!service.awaitTermination(WAIT_TIME, TimeUnit.SECONDS)) { - LOGGER.info("The master of the sword is now {}.", sword.getLocker().getName()); - } - } catch (InterruptedException e) { - LOGGER.error(e.getMessage()); - Thread.currentThread().interrupt(); - } finally { - service.shutdown(); - } + // Additional code... } } ``` -## Applicability +This example demonstrates the Lockable Object pattern by showing how multiple threads can attempt to acquire a lock on a shared resource, with only one thread being able to acquire the lock at a time. + +## Detailed Explanation of Lockable Object Pattern with Real-World Examples + +![Lockable Object](./etc/lockable-object.urm.png "Lockable Object class diagram") + +## When to Use the Lockable Object Pattern in Java + +* Use the Lockable Object pattern in Java when you need to prevent data corruption by multiple threads accessing a shared resource concurrently, ensuring thread safety and robust shared resource management. +* Suitable for systems where thread safety is critical and data integrity must be maintained across various operations. + +## Real-World Applications of Lockable Object Pattern in Java + +* Java’s synchronized keyword and the Lock interfaces in the java.util.concurrent.locks package implement lockable objects to manage synchronization. + +## Benefits and Trade-offs of Lockable Object Pattern + +Benefits: + +* Ensures data consistency and prevents race conditions. +* Provides clear structure for managing access to shared resources. -The Lockable Object pattern is ideal for non distributed applications, that needs to be thread-safe -and keeping their domain models in memory(in contrast to persisted models such as databases). +Trade-offs: -## Class diagram +* Can lead to decreased performance due to overhead of acquiring and releasing locks. +* Potential for deadlocks if not implemented and managed carefully. -![alt text](./etc/lockable-object.urm.png "Lockable Object class diagram") +## Related Java Design Patterns +* [Monitor Object](https://java-design-patterns.com/patterns/monitor/): Both patterns manage access to shared resources; Monitor Object combines synchronization and encapsulation of the condition variable. +* [Read/Write Lock](https://java-design-patterns.com/patterns/reader-writer-lock/): Specialization of Lockable Object for scenarios where read operations outnumber write operations. -## Credits +## References and Credits -* [Lockable Object - Chapter 10.3, J2EE Design Patterns, O'Reilly](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) diff --git a/lockable-object/pom.xml b/lockable-object/pom.xml index 432cd5b1b245..a96ec972e726 100644 --- a/lockable-object/pom.xml +++ b/lockable-object/pom.xml @@ -34,6 +34,14 @@ lockable-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java index 5ec59b69d14e..1c05f7d33428 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java @@ -42,13 +42,11 @@ * the request. * *

In this example, we create a new Lockable object with the SwordOfAragorn implementation of it. - * Afterwards we create 6 Creatures with the Elf, Orc and Human implementations and assign them each - * to a Fiend object and the Sword is the target object. Because there is only one Sword and it uses - * the Lockable Object pattern, only one creature can hold the sword at a given time. When the sword - * is locked, any other alive Fiends will try to lock, which will result in a race to lock the + * Afterward we create 6 Creatures with the Elf, Orc and Human implementations and assign them each + * to a Fiend object and the Sword is the target object. Because there is only one Sword, and it + * uses the Lockable Object pattern, only one creature can hold the sword at a given time. When the + * sword is locked, any other alive Fiends will try to lock, which will result in a race to lock the * sword. - * - * @author Noam Greenshtain */ @Slf4j public class App implements Runnable { diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java index dfbe1aeb45b5..dd1d2b8f7d9b 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java @@ -24,15 +24,14 @@ */ package com.iluwatar.lockableobject; -/** - * An exception regarding the locking process of a Lockable object. - */ +import java.io.Serial; + +/** An exception regarding the locking process of a Lockable object. */ public class LockingException extends RuntimeException { - private static final long serialVersionUID = 8556381044865867037L; + @Serial private static final long serialVersionUID = 8556381044865867037L; public LockingException(String message) { super(message); } - } diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java index 85a5d7df5f30..8e05ea6e684b 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java @@ -29,8 +29,8 @@ import lombok.extern.slf4j.Slf4j; /** - * An implementation of a Lockable object. This is the Sword of Aragorn and every creature wants - * to possess it! + * An implementation of a Lockable object. This is the Sword of Aragorn and every creature wants to + * possess it! */ @Slf4j public class SwordOfAragorn implements Lockable { diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java index 1a02942d8126..05b6db00129b 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java @@ -66,7 +66,7 @@ public boolean acquire(@NonNull Lockable lockable) { return false; } - /** Terminates the Creature and unlocks all of the Lockable that it posses. */ + /** Terminates the Creature and unlocks all the Lockable that it possesses. */ public synchronized void kill() { LOGGER.info("{} {} has been slayed!", type, name); for (Lockable lockable : instruments) { @@ -109,5 +109,4 @@ public synchronized void hit(int damage) { public synchronized boolean isAlive() { return getHealth() > 0; } - } diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java index 045269d90c6e..7996a0269f9a 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java @@ -24,6 +24,8 @@ */ package com.iluwatar.lockableobject.domain; +import lombok.Getter; + /** Attribute constants of each Creature implementation. */ public enum CreatureStats { ELF_HEALTH(90), @@ -33,13 +35,9 @@ public enum CreatureStats { HUMAN_HEALTH(60), HUMAN_DAMAGE(60); - int value; + @Getter final int value; - private CreatureStats(int value) { + CreatureStats(int value) { this.value = value; } - - public int getValue() { - return this.value; - } } diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Feind.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Feind.java index 08adcaee8114..4bee7024f2a3 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Feind.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Feind.java @@ -30,7 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** A Feind is a creature that all it wants is to posses a Lockable object. */ +/** A Feind is a creature that wants to possess a Lockable object. */ public class Feind implements Runnable { private final Creature creature; @@ -53,12 +53,7 @@ public Feind(@NonNull Creature feind, @NonNull Lockable target) { @Override public void run() { if (!creature.acquire(target)) { - try { - fightForTheSword(creature, target.getLocker(), target); - } catch (InterruptedException e) { - LOGGER.error(e.getMessage()); - Thread.currentThread().interrupt(); - } + fightForTheSword(creature, target.getLocker(), target); } else { LOGGER.info("{} has acquired the sword!", target.getLocker().getName()); } @@ -69,11 +64,9 @@ public void run() { * * @param reacher as the source creature. * @param holder as the foe. - * @param sword as the Lockable to posses. - * @throws InterruptedException in case of interruption. + * @param sword as the Lockable to possess. */ - private void fightForTheSword(Creature reacher, @NonNull Creature holder, Lockable sword) - throws InterruptedException { + private void fightForTheSword(Creature reacher, @NonNull Creature holder, Lockable sword) { LOGGER.info("A duel between {} and {} has been started!", reacher.getName(), holder.getName()); boolean randBool; while (this.target.isLocked() && reacher.isAlive() && holder.isAlive()) { diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Human.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Human.java index 1ba12aac02d7..c079ab376f9a 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Human.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Human.java @@ -28,7 +28,7 @@ public class Human extends Creature { /** - * A constructor that initializes the attributes of an human. + * A constructor that initializes the attributes of a human. * * @param name as the name of the creature. */ diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Orc.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Orc.java index 747c7a1aba1d..0f13fcc0d2c2 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Orc.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Orc.java @@ -24,7 +24,7 @@ */ package com.iluwatar.lockableobject.domain; -/** A Orc implementation of a Creature. */ +/** An Orc implementation of a Creature. */ public class Orc extends Creature { /** * A constructor that initializes the attributes of an orc. diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java index b8f9806a69a7..de7fc3678089 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java @@ -90,14 +90,14 @@ void testAcqusition() throws InterruptedException { Assertions.assertEquals(orc, sword.getLocker()); } - void killCreature(Creature source, Creature target) throws InterruptedException { + void killCreature(Creature source, Creature target) { while (target.isAlive()) { source.attack(target); } } @Test - void invalidDamageTest(){ + void invalidDamageTest() { Assertions.assertThrows(IllegalArgumentException.class, () -> elf.hit(-50)); } } diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java index 64f8f3bddd4b..26a97853e78a 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java @@ -29,17 +29,16 @@ class ExceptionsTest { - private String msg = "test"; + private static final String MSG = "test"; @Test - void testException(){ + void testException() { Exception e; - try{ - throw new LockingException(msg); - } - catch(LockingException ex){ + try { + throw new LockingException(MSG); + } catch (LockingException ex) { e = ex; } - Assertions.assertEquals(msg, e.getMessage()); + Assertions.assertEquals(MSG, e.getMessage()); } } diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java index 70b864a1c479..cedaf65b0d59 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java @@ -39,14 +39,14 @@ class FeindTest { private Lockable sword; @BeforeEach - void init(){ + void init() { elf = new Elf("Nagdil"); orc = new Orc("Ghandar"); sword = new SwordOfAragorn(); } @Test - void nullTests(){ + void nullTests() { Assertions.assertThrows(NullPointerException.class, () -> new Feind(null, null)); Assertions.assertThrows(NullPointerException.class, () -> new Feind(elf, null)); Assertions.assertThrows(NullPointerException.class, () -> new Feind(null, sword)); @@ -67,5 +67,4 @@ void testBaseCase() throws InterruptedException { sword.unlock(elf.isAlive() ? elf : orc); Assertions.assertNull(sword.getLocker()); } - } diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java index 766069b89165..b6db856853c4 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java @@ -34,7 +34,7 @@ class SubCreaturesTests { @Test - void statsTest(){ + void statsTest() { var elf = new Elf("Limbar"); var orc = new Orc("Dargal"); var human = new Human("Jerry"); diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java index e4b604e7052d..4c40f806b3cb 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java @@ -43,7 +43,7 @@ void basicSwordTest() { } @Test - void invalidLockerTest(){ + void invalidLockerTest() { var sword = new SwordOfAragorn(); Assertions.assertThrows(NullPointerException.class, () -> sword.lock(null)); Assertions.assertThrows(NullPointerException.class, () -> sword.unlock(null)); diff --git a/log-aggregation/README.md b/log-aggregation/README.md deleted file mode 100644 index f6d979517b9a..000000000000 --- a/log-aggregation/README.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Log aggregation -category: Architectural -language: en -tag: - - Microservices - - Extensibility ---- - -## Intent - -Centralize, streamline, and optimize the process of log management so that insights can be quickly -derived, problems can be swiftly identified and resolved, and the system's overall health can be -monitored efficiently. - -## Explanation - -Real-world example - -> AWS CloudWatch aggregates logs from various AWS services for monitoring and alerting. - - -In plain words - -> The primary goal is to consolidate logs from different sources, making them more accessible and -> actionable. Various tools and platforms, such as Elasticsearch-Logstash-Kibana (ELK) stack, -> Splunk, -> Graylog, and others, are employed in these real-world scenarios to facilitate log aggregation. - -Wikipedia says - -> You have applied the Microservice architecture pattern. The application consists of multiple -> services and service instances that are running on multiple machines. Requests often span multiple -> service instances. Each service instance generates writes information about what it is doing to a -> log file in a standardized format. The log file contains errors, warnings, information and debug -> messages. - - -## Class diagram - -![class diagram](./etc/log-aggregation.png) - -## Applicability - -1. Distributed Systems and Microservices - - In modern architectures where systems are split into smaller, independent microservices running across multiple servers or even data centers, aggregating logs from all these services is crucial for a holistic view of system health and activity. - -2. Troubleshooting and Debugging - - When system failures or unexpected behaviors occur, engineers need consolidated logs to trace and diagnose issues. Log aggregation makes this process efficient by collecting all relevant logs in one place. - -3. Security and Compliance Monitoring - - Many industries have regulatory requirements for log retention and analysis. Log aggregation helps in collecting, retaining, and analyzing logs for unauthorized access, potential breaches, and other security threats. - -4. Performance Monitoring - - Aggregated logs can be used to identify performance bottlenecks, slow database queries, or service endpoints experiencing high latencies. - -## Credits - -* [Pattern: Log aggregation](https://microservices.io/patterns/observability/application-logging.html) diff --git a/log-aggregation/pom.xml b/log-aggregation/pom.xml deleted file mode 100644 index 886d712ff403..000000000000 --- a/log-aggregation/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - - log-aggregation - - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-junit-jupiter - test - - - - 17 - 17 - UTF-8 - - - diff --git a/map-reduce/README.md b/map-reduce/README.md new file mode 100644 index 000000000000..2b0b150033bd --- /dev/null +++ b/map-reduce/README.md @@ -0,0 +1,231 @@ +--- +title: "MapReduce Pattern in Java" +shortTitle: MapReduce +description: "Learn the MapReduce pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge." +category: Performance optimization +language: en +tag: + - Data processing + - Code simplification + - Delegation + - Performance +--- + +## Also known as + +* Split-Apply-Combine Strategy +* Scatter-Gather Pattern + +## Intent of Map Reduce Design Pattern + +MapReduce aims to process and generate large datasets with a parallel, distributed algorithm on a cluster. It divides the workload into two main phases: Map and Reduce, allowing for efficient parallel processing of data. + +## Detailed Explanation of Map Reduce Pattern with Real-World Examples + +Real-world example + +> Imagine a large e-commerce company that wants to analyze its sales data across multiple regions. They have terabytes of transaction data stored across hundreds of servers. Using MapReduce, they can efficiently process this data to calculate total sales by product category. The Map function would process individual sales records, emitting key-value pairs of (category, sale amount). The Reduce function would then sum up all sale amounts for each category, producing the final result. + +In plain words + +> MapReduce splits a large problem into smaller parts, processes them in parallel, and then combines the results. + +Wikipedia says + +> "MapReduce is a programming model and associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster". +MapReduce consists of two main steps: +The "Map" step: The master node takes the input, divides it into smaller sub-problems, and distributes them to worker nodes. A worker node may do this again in turn, leading to a multi-level tree structure. The worker node processes the smaller problem, and passes the answer back to its master node. +The "Reduce" step: The master node then collects the answers to all the sub-problems and combines them in some way to form the output – the answer to the problem it was originally trying to solve. +This approach allows for efficient processing of vast amounts of data across multiple machines, making it a fundamental technique in big data analytics and distributed computing. + +## Programmatic Example of Map Reduce in Java + +### 1. Map Phase (Splitting & Processing Data) + +* The Mapper takes an input string, splits it into words, and counts occurrences. +* Output: A map {word → count} for each input line. +#### `Mapper.java` +```java +public class Mapper { + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} +``` +Example Input: ```"Hello world hello"``` +Output: ```{hello=2, world=1}``` + +### 2. Shuffle Phase (Grouping Data by Key) + +* The Shuffler collects key-value pairs from multiple mappers and groups values by key. +#### `Shuffler.java` +```java +public class Shuffler { + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} +``` +Example Input: +``` +[ + {"hello": 2, "world": 1}, + {"hello": 1, "java": 1} +] +``` +Output: +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +### 3. Reduce Phase (Aggregating Results) + +* The Reducer sums up occurrences of each word. +#### `Reducer.java` +```java +public class Reducer { + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} +``` +Example Input: +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` +Output: +``` +[ + {"hello": 3}, + {"world": 1}, + {"java": 1} +] +``` + +### 4. Running the Full MapReduce Process + +* The MapReduce class coordinates the three steps. +#### `MapReduce.java` +```java +public class MapReduce { + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } + + Map> grouped = Shuffler.shuffleAndSort(mapped); + + return Reducer.reduce(grouped); + } +} +``` + +### 4. Main Execution (Calling MapReduce) + +* The Main class executes the MapReduce pipeline and prints the final word count. +#### `Main.java` +```java + public static void main(String[] args) { + List inputs = Arrays.asList( + "Hello world hello", + "MapReduce is fun", + "Hello from the other side", + "Hello world" + ); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } +} +``` + +Output: +``` +hello: 4 +world: 2 +the: 1 +other: 1 +side: 1 +mapreduce: 1 +is: 1 +from: 1 +fun: 1 +``` + +## When to Use the Map Reduce Pattern in Java + +Use MapReduce when: +* Processing large datasets that don't fit into a single machine's memory +* Performing computations that can be parallelized +* Dealing with fault-tolerant and distributed computing scenarios +* Analyzing log files, web crawl data, or scientific data + +## Map Reduce Pattern Java Tutorials + +* [MapReduce Tutorial(Apache Hadoop)](https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html) +* [MapReduce Example(Simplilearn)](https://www.youtube.com/watch?v=l2clwKnrtO8) + +## Benefits and Trade-offs of Map Reduce Pattern + +Benefits: + +* Scalability: Can process vast amounts of data across multiple machines +* Fault-tolerance: Handles machine failures gracefully +* Simplicity: Abstracts complex distributed computing details + +Trade-offs: + +* Overhead: Not efficient for small datasets due to setup and coordination costs +* Limited flexibility: Not suitable for all types of computations or algorithms +* Latency: Batch-oriented nature may not be suitable for real-time processing needs + +## Real-World Applications of Map Reduce Pattern in Java + +* Google's original implementation for indexing web pages +* Hadoop MapReduce for big data processing +* Log analysis in large-scale systems +* Genomic sequence analysis in bioinformatics + +## Related Java Design Patterns + +* Chaining Pattern +* Master-Worker Pattern +* Pipeline Pattern + +## References and Credits + +* [What is MapReduce](https://www.ibm.com/think/topics/mapreduce) +* [Wy MapReduce is not dead](https://www.codemotion.com/magazine/ai-ml/big-data/mapreduce-not-dead-heres-why-its-still-ruling-in-the-cloud/) +* [Scalabe Distributed Data Processing Solutions](https://tcpp.cs.gsu.edu/curriculum/?q=system%2Ffiles%2Fch07.pdf) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U) diff --git a/map-reduce/etc/map-reduce.png b/map-reduce/etc/map-reduce.png new file mode 100644 index 000000000000..caf764553291 Binary files /dev/null and b/map-reduce/etc/map-reduce.png differ diff --git a/map-reduce/etc/map-reduce.urm.puml b/map-reduce/etc/map-reduce.urm.puml new file mode 100644 index 000000000000..508f1fd3363d --- /dev/null +++ b/map-reduce/etc/map-reduce.urm.puml @@ -0,0 +1,24 @@ +@startuml +package com.iluwatar { + class Main { + + Main() + + main(args : String[]) {static} + } + class MapReduce { + + MapReduce() + + mapReduce(inputs : List) : List> {static} + } + class Mapper { + + Mapper() + + map(input : String) : Map {static} + } + class Reducer { + + Reducer() + + reduce(grouped : Map>) : List> {static} + } + class Shuffler { + + Shuffler() + + shuffleAndSort(mapped : List>) : Map> {static} + } +} +@enduml \ No newline at end of file diff --git a/map-reduce/pom.xml b/map-reduce/pom.xml new file mode 100644 index 000000000000..4778f1bdb7ca --- /dev/null +++ b/map-reduce/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + map-reduce + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.mapreduce.Main + + + + + + + + + diff --git a/map-reduce/src/main/java/com/iluwatar/Main.java b/map-reduce/src/main/java/com/iluwatar/Main.java new file mode 100644 index 000000000000..98e6381235ae --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Main.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +/** + * The Main class serves as the entry point for executing the MapReduce program. It processes a list + * of text inputs, applies the MapReduce pattern, and prints the results. + */ +public class Main { + private static final Logger logger = Logger.getLogger(Main.class.getName()); + + /** + * The main method initiates the MapReduce process and displays the word count results. + * + * @param args Command-line arguments (not used). + */ + public static void main(String[] args) { + List inputs = + Arrays.asList( + "Hello world hello", "MapReduce is fun", "Hello from the other side", "Hello world"); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + logger.info(entry.getKey() + ": " + entry.getValue()); + } + } +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java b/map-reduce/src/main/java/com/iluwatar/MapReduce.java similarity index 59% rename from reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java rename to map-reduce/src/main/java/com/iluwatar/MapReduce.java index f349ea3828b8..49b6f0a1d3d6 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java +++ b/map-reduce/src/main/java/com/iluwatar/MapReduce.java @@ -22,37 +22,36 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.reader.writer.lock.utils; +package com.iluwatar; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.AppenderBase; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; -import org.slf4j.LoggerFactory; +import java.util.Map; /** - * InMemory Log Appender Util. + * The MapReduce class orchestrates the MapReduce process, calling the Mapper, Shuffler, and Reducer + * components. */ -public class InMemoryAppender extends AppenderBase { - private final List log = new LinkedList<>(); - - public InMemoryAppender(Class clazz) { - ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); - start(); +public class MapReduce { + private MapReduce() { + throw new UnsupportedOperationException( + "MapReduce is a utility class and cannot be instantiated."); } - public InMemoryAppender() { - ((Logger) LoggerFactory.getLogger("root")).addAppender(this); - start(); - } + /** + * Executes the MapReduce process on the given list of input strings. + * + * @param inputs List of input strings to be processed. + * @return A list of word counts sorted in descending order. + */ + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } - @Override - protected void append(ILoggingEvent eventObject) { - log.add(eventObject); - } + Map> grouped = Shuffler.shuffleAndSort(mapped); - public boolean logContains(String message) { - return log.stream().anyMatch(event -> event.getFormattedMessage().contains(message)); + return Reducer.reduce(grouped); } } diff --git a/map-reduce/src/main/java/com/iluwatar/Mapper.java b/map-reduce/src/main/java/com/iluwatar/Mapper.java new file mode 100644 index 000000000000..c048c5e4a6d1 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Mapper.java @@ -0,0 +1,57 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.HashMap; +import java.util.Map; + +/** + * The Mapper class is responsible for processing an input string and generating a map of word + * occurrences. + */ +public class Mapper { + private Mapper() { + throw new UnsupportedOperationException( + "Mapper is a utility class and cannot be instantiated."); + } + + /** + * Splits a given input string into words and counts their occurrences. + * + * @param input The input string to be mapped. + * @return A map where keys are words and values are their respective counts. + */ + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Reducer.java b/map-reduce/src/main/java/com/iluwatar/Reducer.java new file mode 100644 index 000000000000..fbfc8d09d61a --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Reducer.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** The Reducer class is responsible for aggregating word counts from the shuffled data. */ +public class Reducer { + private Reducer() { + throw new UnsupportedOperationException( + "Reducer is a utility class and cannot be instantiated."); + } + + /** + * Sums the occurrences of each word and sorts the results in descending order. + * + * @param grouped A map where keys are words and values are lists of their occurrences. + * @return A sorted list of word counts in descending order. + */ + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Shuffler.java b/map-reduce/src/main/java/com/iluwatar/Shuffler.java new file mode 100644 index 000000000000..cf58bc9019e3 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Shuffler.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** The Shuffler class is responsible for grouping word occurrences from multiple mappers. */ +public class Shuffler { + + private Shuffler() { + throw new UnsupportedOperationException( + "Shuffler is a utility class and cannot be instantiated."); + } + + /** + * Merges multiple word count maps into a single grouped map. + * + * @param mapped List of maps containing word counts from the mapping phase. + * @return A map where keys are words and values are lists of their occurrences across inputs. + */ + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java b/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java new file mode 100644 index 000000000000..6abaa2142c7d --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.Test; + +class MapReduceTest { + + @Test + void testMapReduce() { + List inputs = + Arrays.asList("Hello world hello", "MapReduce is fun", "Hello from the other side"); + + List> result = MapReduce.mapReduce(inputs); + + assertEquals("hello", result.get(0).getKey()); // hello = 3 + assertEquals(3, result.get(0).getValue()); + assertEquals(1, result.get(1).getValue()); + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/MapperTest.java b/map-reduce/src/test/java/com/iluwatar/MapperTest.java new file mode 100644 index 000000000000..7ebd48407042 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/MapperTest.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; +import org.junit.jupiter.api.Test; + +class MapperTest { + + @Test + void testMapSingleSentence() { + String input = "Hello world hello"; + Map result = Mapper.map(input); + + assertEquals(2, result.get("hello")); + assertEquals(1, result.get("world")); + } + + @Test + void testMapCaseInsensitivity() { + String input = "HeLLo WoRLd hello WORLD"; + Map result = Mapper.map(input); + + assertEquals(2, result.get("hello")); + assertEquals(2, result.get("world")); + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/ReducerTest.java b/map-reduce/src/test/java/com/iluwatar/ReducerTest.java new file mode 100644 index 000000000000..903b3828c8d7 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/ReducerTest.java @@ -0,0 +1,74 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.Test; + +class ReducerTest { + + @Test + void testReduceWithMultipleWords() { + Map> input = new HashMap<>(); + input.put("apple", Arrays.asList(2, 3, 1)); + input.put("banana", Arrays.asList(1, 1)); + input.put("cherry", List.of(4)); + + List> result = Reducer.reduce(input); + + assertEquals(3, result.size()); + assertEquals("apple", result.get(0).getKey()); + assertEquals(6, result.get(0).getValue()); + assertEquals("cherry", result.get(1).getKey()); + assertEquals(4, result.get(1).getValue()); + assertEquals("banana", result.get(2).getKey()); + assertEquals(2, result.get(2).getValue()); + } + + @Test + void testReduceWithEmptyInput() { + Map> input = new HashMap<>(); + + List> result = Reducer.reduce(input); + + assertTrue(result.isEmpty()); + } + + @Test + void testReduceWithTiedCounts() { + Map> input = new HashMap<>(); + input.put("tie1", Arrays.asList(2, 2)); + input.put("tie2", Arrays.asList(1, 3)); + + List> result = Reducer.reduce(input); + + assertEquals(2, result.size()); + assertEquals(4, result.get(0).getValue()); + assertEquals(4, result.get(1).getValue()); + // Note: The order of tie1 and tie2 is not guaranteed in case of a tie + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java b/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java new file mode 100644 index 000000000000..3362f7ea20a5 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.Test; + +class ShufflerTest { + + @Test + void testShuffleAndSort() { + List> mappedData = + Arrays.asList(Map.of("hello", 1, "world", 2), Map.of("hello", 2, "java", 1)); + + Map> grouped = Shuffler.shuffleAndSort(mappedData); + + assertEquals(Arrays.asList(1, 2), grouped.get("hello")); + assertEquals(List.of(2), grouped.get("world")); + assertEquals(List.of(1), grouped.get("java")); + } +} diff --git a/marker/.gitignore b/marker-interface/.gitignore similarity index 100% rename from marker/.gitignore rename to marker-interface/.gitignore diff --git a/marker-interface/README.md b/marker-interface/README.md new file mode 100644 index 000000000000..1d242f5bc190 --- /dev/null +++ b/marker-interface/README.md @@ -0,0 +1,128 @@ +--- +title: "Marker Interface Pattern in Java: Defining Behavior Through Empty Interfaces" +shortTitle: Marker Interface +description: "Explore the Marker Interface pattern in Java, its benefits, real-world examples, and common uses. Learn how to use marker interfaces for metadata and special class behaviors." +category: Structural +language: en +tag: + - Encapsulation + - Interface + - Polymorphism +--- + +## Also known as + +* Marker +* Tagging Interface + +## Intent of Marker Interface Design Pattern + +The Marker Interface pattern in Java is used to convey metadata about a class in a type-safe manner. Interfaces in Java that have no method declarations are known as marker interfaces. They are used to indicate that a class implementing such an interface possesses some special behavior or capability. + +## Detailed Explanation of Marker Interface Pattern with Real-World Examples + +Real-world example + +> Consider a scenario in a Java library system where certain books are rare and need special handling procedures, such as restricted check-outs or specific storage conditions. Analogous to the Marker Interface pattern, we could have a marker interface called `RareBook`. Books in the library catalog that implement this interface are flagged as requiring special treatment but don't necessarily have different methods from other books. +> +> When a library staff member processes transactions or handles storage, the system checks if a book implements the `RareBook` interface. If it does, the system automatically enforces rules like "Do not allow check-outs for more than three days" or "Store in a temperature-controlled environment." This use of the marker interface effectively communicates special requirements without altering how books are generally managed, serving simply as a marker for special conditions. + +In plain words + +> The Marker Interface design pattern in Java uses empty interfaces to signal or define certain properties and behaviors of objects in a type-safe way, without requiring specific method implementations. + +Wikipedia says + +> The marker interface pattern is a design pattern in computer science, used with languages that provide run-time type information about objects. It provides a means to associate metadata with a class where the language does not have explicit support for such metadata. +> +> To use this pattern, a class implements a marker interface (also called tagging interface) which is an empty interface, and methods that interact with instances of that class test for the existence of the interface. Whereas a typical interface specifies functionality (in the form of method declarations) that an implementing class must support, a marker interface need not do so. The mere presence of such an interface indicates specific behavior on the part of the implementing class. Hybrid interfaces, which both act as markers and specify required methods, are possible but may prove confusing if improperly used. + +## Programmatic Example of Marker Interface Pattern in Java + +The Marker Interface design pattern is a design pattern in computer science that is used with languages that provide run-time type information about objects. It provides a means to associate metadata with a class where the language does not have explicit support for such metadata. + +In the given Java code example, the Permission interface acts as a marker interface. Classes that implement this interface are marked as having special permissions. Let's break down the code to understand how this pattern is implemented. + +First, we define the `Permission` interface. This interface doesn't have any methods, making it a marker interface. + +```java +public interface Permission { + // This is a marker interface and does not contain any methods +} +``` + +Next, we have two classes `Guard` and `Thief` that represent different types of characters in our application. The `Guard` class implements the `Permission` interface, indicating that objects of this class have special permissions. + +```java +public class Guard implements Permission { + public void enter() { + // Implementation of enter method + } +} +``` + +On the other hand, the `Thief` class does not implement the `Permission` interface, indicating that objects of this class do not have special permissions. + +```java +public class Thief { + public void steal() { + // Implementation of steal method + } + + public void doNothing() { + // Implementation of doNothing method + } +} +``` + +In the `main` method of the `App` class, we create instances of `Guard` and `Thief`. We then use the `instanceof` operator to check if these objects implement the `Permission` interface. If an object implements the `Permission` interface, it is allowed to perform certain actions. If not, it is restricted from performing those actions. + +```java +public class App { + public static void main(String[] args) { + final var logger = LoggerFactory.getLogger(App.class); + var guard = new Guard(); + var thief = new Thief(); + + if (guard instanceof Permission) { + guard.enter(); + } else { + logger.info("You have no permission to enter, please leave this area"); + } + + if (thief instanceof Permission) { + thief.steal(); + } else { + thief.doNothing(); + } + } +} +``` + +In this way, the Marker Interface pattern allows us to associate metadata (in this case, special permissions) with a class in a type-safe manner. + +## When to Use the Marker Interface Pattern in Java + +Marker interfaces are applicable in scenarios where you want to impose a special behavior or capability on a class, but don't want to force the class to define specific methods. This pattern is commonly used to indicate that a class conforms to a particular contract without needing to implement methods. + +## Real-World Applications of Marker Interface Pattern in Java + +* java.io.Serializable: Classes that implement this interface are capable of being serialized by the Java runtime. +* java.lang.Cloneable: Classes that implement this interface can be cloned using the clone method in Java. + +## Benefits and Trade-offs of Marker Interface Pattern + +Benefits: + +* Enables type checking at compile time, allowing developers to use polymorphism to write cleaner and more flexible code. +* Allows the addition of metadata to classes without altering their actual behavior. + +Trade-offs: + +* Can lead to empty interfaces in the codebase, which some may consider as not clean or clear in purpose. +* Does not enforce any method implementations, which can lead to runtime errors if not properly handled. + +## References and Credits + +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) diff --git a/marker-interface/etc/MarkerDiagram.png b/marker-interface/etc/MarkerDiagram.png new file mode 100644 index 000000000000..6ed4f9c56780 Binary files /dev/null and b/marker-interface/etc/MarkerDiagram.png differ diff --git a/marker/etc/MarkerDiagram.ucls b/marker-interface/etc/MarkerDiagram.ucls similarity index 100% rename from marker/etc/MarkerDiagram.ucls rename to marker-interface/etc/MarkerDiagram.ucls diff --git a/api-gateway/etc/api-gateway.urm.puml b/marker-interface/etc/marker-interface.urm.puml similarity index 100% rename from api-gateway/etc/api-gateway.urm.puml rename to marker-interface/etc/marker-interface.urm.puml diff --git a/marker/etc/marker.urm.puml b/marker-interface/etc/marker.urm.puml similarity index 100% rename from marker/etc/marker.urm.puml rename to marker-interface/etc/marker.urm.puml diff --git a/marker/pom.xml b/marker-interface/pom.xml similarity index 89% rename from marker/pom.xml rename to marker-interface/pom.xml index 9b0572e7cb1c..f564d6756133 100644 --- a/marker/pom.xml +++ b/marker-interface/pom.xml @@ -32,8 +32,16 @@ 1.26.0-SNAPSHOT 4.0.0 - marker + marker-interface + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -42,7 +50,7 @@ org.hamcrest hamcrest-core - 1.3 + 3.0 test diff --git a/marker/src/main/java/App.java b/marker-interface/src/main/java/App.java similarity index 95% rename from marker/src/main/java/App.java rename to marker-interface/src/main/java/App.java index 6b20bae948a3..c1f36664b495 100644 --- a/marker/src/main/java/App.java +++ b/marker-interface/src/main/java/App.java @@ -22,18 +22,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -import org.slf4j.Logger; + import org.slf4j.LoggerFactory; /** * Created by Alexis on 28-Apr-17. With Marker interface idea is to make empty interface and extend * it. Basically it is just to identify the special objects from normal objects. Like in case of * serialization , objects that need to be serialized must implement serializable interface (it is - * empty interface) and down the line writeObject() method must be checking if it is a instance of + * empty interface) and down the line writeObject() method must be checking if it is an instance of * serializable or not. * *

Marker interface vs annotation Marker interfaces and marker annotations both have their uses, - * neither of them is obsolete or always better then the other one. If you want to define a type + * neither of them is obsolete or always better than the other one. If you want to define a type * that does not have any new methods associated with it, a marker interface is the way to go. If * you want to mark program elements other than classes and interfaces, to allow for the possibility * of adding more information to the marker in the future, or to fit the marker into a framework @@ -66,4 +66,3 @@ public static void main(String[] args) { } } } - diff --git a/marker/src/main/java/Guard.java b/marker-interface/src/main/java/Guard.java similarity index 97% rename from marker/src/main/java/Guard.java rename to marker-interface/src/main/java/Guard.java index 8852e830293f..469ee2e7734f 100644 --- a/marker/src/main/java/Guard.java +++ b/marker-interface/src/main/java/Guard.java @@ -24,9 +24,7 @@ */ import lombok.extern.slf4j.Slf4j; -/** - * Class defining Guard. - */ +/** Class defining Guard. */ @Slf4j public class Guard implements Permission { diff --git a/marker/src/main/java/Permission.java b/marker-interface/src/main/java/Permission.java similarity index 91% rename from marker/src/main/java/Permission.java rename to marker-interface/src/main/java/Permission.java index 21f751f72814..bdaf18375e91 100644 --- a/marker/src/main/java/Permission.java +++ b/marker-interface/src/main/java/Permission.java @@ -22,8 +22,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/** - * Interface without any methods Marker interface is based on that assumption. - */ -public interface Permission { -} +/** Interface without any methods Marker interface is based on that assumption. */ +public interface Permission {} diff --git a/marker/src/main/java/Thief.java b/marker-interface/src/main/java/Thief.java similarity index 97% rename from marker/src/main/java/Thief.java rename to marker-interface/src/main/java/Thief.java index d412d3fa0c4a..ae6bec874671 100644 --- a/marker/src/main/java/Thief.java +++ b/marker-interface/src/main/java/Thief.java @@ -24,9 +24,7 @@ */ import lombok.extern.slf4j.Slf4j; -/** - * Class defining Thief. - */ +/** Class defining Thief. */ @Slf4j public class Thief { diff --git a/marker/src/test/java/AppTest.java b/marker-interface/src/test/java/AppTest.java similarity index 94% rename from marker/src/test/java/AppTest.java rename to marker-interface/src/test/java/AppTest.java index 1d70483204b9..4600eed4a063 100644 --- a/marker/src/test/java/AppTest.java +++ b/marker-interface/src/test/java/AppTest.java @@ -22,17 +22,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/marker/src/test/java/GuardTest.java b/marker-interface/src/test/java/GuardTest.java similarity index 98% rename from marker/src/test/java/GuardTest.java rename to marker-interface/src/test/java/GuardTest.java index 9470689bc1ee..7ae34f63d026 100644 --- a/marker/src/test/java/GuardTest.java +++ b/marker-interface/src/test/java/GuardTest.java @@ -27,9 +27,7 @@ import org.junit.jupiter.api.Test; -/** - * Guard test - */ +/** Guard test */ class GuardTest { @Test @@ -37,4 +35,4 @@ void testGuard() { var guard = new Guard(); assertThat(guard, instanceOf(Permission.class)); } -} \ No newline at end of file +} diff --git a/marker/src/test/java/ThiefTest.java b/marker-interface/src/test/java/ThiefTest.java similarity index 98% rename from marker/src/test/java/ThiefTest.java rename to marker-interface/src/test/java/ThiefTest.java index 91f31293007f..130dd12fcb25 100644 --- a/marker/src/test/java/ThiefTest.java +++ b/marker-interface/src/test/java/ThiefTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Thief test - */ +/** Thief test */ class ThiefTest { @Test void testThief() { var thief = new Thief(); assertThat(thief, not(instanceOf(Permission.class))); } -} \ No newline at end of file +} diff --git a/master-worker-pattern/README.md b/master-worker-pattern/README.md deleted file mode 100644 index 32f239078517..000000000000 --- a/master-worker-pattern/README.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Master-Worker -category: Concurrency -language: en -tag: - - Performance ---- - -## Also known as - -> Master-slave or Map-reduce - -## Intent - -> Used for centralised parallel processing. - -## Class diagram -![alt text](./etc/master-worker-pattern.urm.png "Master-Worker pattern class diagram") - -## Explanation - -Real World Example ->Imagine you have a large stack of papers to grade as a teacher. You decide to hire several teaching assistants to help you. You, as the "master," distribute different papers to each teaching assistant, they grade them independently, and return the graded papers to you. Finally, you collect all the graded papers, review them, and calculate the final grades. This process of dividing work among assistants, parallel processing, and aggregating results is similar to the Master-Worker pattern. - -In Plain Words ->The Master-Worker pattern is like a boss (the master) assigning tasks to workers and then combining the results. It's a way to efficiently break down a big job into smaller pieces that can be done concurrently. - -Wikipedia Says ->According to [Wikipedia](https://en.wikipedia.org/wiki/Master/slave_(technology)), the Master-Worker pattern, also known as Master-Slave or Map-Reduce, is a design pattern used in software engineering for parallel processing. In this pattern, a "master" component divides a complex task into smaller subtasks and assigns them to multiple "worker" components. Each worker processes its assigned subtask independently, and the master collects and combines the results to produce the final output. - -Programmatic Example - -```java -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; - -// Define a task that workers (teaching assistants) will perform -class GradingWorkerTask implements Callable { - private int paperNumber; - - public GradingWorkerTask(int paperNumber) { - this.paperNumber = paperNumber; - } - - @Override - public Integer call() throws Exception { - // Simulate grading a paper (e.g., assigning a score) - int score = (int) (Math.random() * 100); // Assign a random score - // Simulate some grading time - Thread.sleep((int) (Math.random() * 1000)); - System.out.println("Graded paper #" + paperNumber + " with a score of " + score); - return score; - } -} -``` - -```java -public class PaperGrading { - public static void main(String[] args) { - int totalPapersToGrade = 20; // Total number of papers to grade - int numberOfTeachingAssistants = 3; // Number of teaching assistants - - // Create a thread pool with a fixed number of teaching assistants (workers) - ExecutorService executor = Executors.newFixedThreadPool(numberOfTeachingAssistants); - - // Create and submit grading tasks to the executor for each paper - List> results = new ArrayList<>(); - for (int paperNumber = 1; paperNumber <= totalPapersToGrade; paperNumber++) { - results.add(executor.submit(new GradingWorkerTask(paperNumber))); - } - - // Shutdown the executor to prevent new tasks from being submitted - executor.shutdown(); - - // Collect and analyze the grading results - int totalScore = 0; - for (Future result : results) { - try { - // Get the score of each graded paper and calculate the total score - totalScore += result.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - - double averageScore = (double) totalScore / totalPapersToGrade; - - System.out.println("Grading completed."); - System.out.println("Total papers graded: " + totalPapersToGrade); - System.out.println("Total score: " + totalScore); - System.out.println("Average score: " + averageScore); - } -} -``` - -## Applicability -This pattern can be used when data can be divided into multiple parts, all of which need to go through the same computation to give a result, which need to be aggregated to get the final result. - -## Explanation -In this pattern, parallel processing is performed using a system consisting of a master and some number of workers, where a master divides the work among the workers, gets the result back from them and assimilates all the results to give final result. The only communication is between the master and the worker - none of the workers communicate among one another and the user only communicates with the master to get the required job done. The master has to maintain a record of how the divided data has been distributed, how many workers have finished their work and returned a result, and the results themselves to be able to aggregate the data correctly. - -## Credits - -* [Master-Worker Pattern](https://docs.gigaspaces.com/sbp/master-worker-pattern.html) -* [The Master-Slave Design Pattern](https://www.cs.sjsu.edu/~pearce/oom/patterns/behavioral/masterslave.htm) diff --git a/master-worker/README.md b/master-worker/README.md new file mode 100644 index 000000000000..1b8f7cfef3b3 --- /dev/null +++ b/master-worker/README.md @@ -0,0 +1,149 @@ +--- +title: "Master-Worker Pattern in Java: Coordinating Concurrent Processing with Ease" +shortTitle: Master-Worker +description: "Discover the Master-Worker design pattern in Java. Learn how it improves concurrency, scalability, and performance through parallel task processing. Includes real-world examples and code snippets." +category: Concurrency +language: en +tag: + - Multithreading + - Performance + - Scalability +--- + +## Also known as + +* Master-Slave +* Controller-Worker + +## Intent of Master-Worker Design Pattern + +The Master-Worker design pattern is designed to perform parallel computations by distributing tasks between a master process and multiple worker processes. This pattern enhances concurrency, performance, and scalability in software systems. + +## Detailed Explanation of Master-Worker Pattern with Real-World Examples + +Real-world example + +> The Master-Worker pattern optimizes parallel task processing and throughput. For instance, in a restaurant kitchen, the head chef (master) delegates tasks to line cooks (workers), who work concurrently to prepare the order. The head chef receives the orders from the dining area and breaks down each order into specific tasks, such as grilling meat, preparing salads, and cooking desserts. Each task is assigned to a different line cook based on their expertise and current workload. The line cooks work in parallel to prepare their portion of the order, while the head chef oversees the process, ensuring everything is prepared correctly and timely. Once each component of the order is ready, the head chef gathers all parts, gives them a final check, and then plates the dishes for service. This kitchen operation mimics the Master-Worker pattern by distributing and managing tasks to optimize efficiency and output. + +In plain words + +> The Master-Worker pattern involves a master process delegating tasks to multiple worker processes, which execute them concurrently and report back, optimizing parallel task processing and throughput. + +Wikipedia says + +> Master–slave is a model of asymmetric communication or control where one device or process (the master) controls one or more other devices or processes (the slaves) and serves as their communication hub. In some systems, a master is selected from a group of eligible devices, with the other devices acting in the role of slaves. + +## Programmatic Example of Master-Worker Pattern in Java + +In the provided code, the `MasterWorker` class initiates the concurrent computation process. The `Master` class divides the work among `Worker` objects, each performing its task in parallel, thus optimizing task processing and enhancing system efficiency. + +```java +// The MasterWorker class acts as the main entry point for the Master-Worker system. +public class MasterWorker { + private Master master; + + public MasterWorker(Master master) { + this.master = master; + } + + public Result getResult(Input input) { + return master.computeResult(input); + } +} +``` + +In this code, the `MasterWorker` class is initialized with a `Master` object. The `getResult` method is used to start the computation process. + +```java +// The Master class is responsible for dividing the work among the workers. +public abstract class Master { + protected List workers; + + public Master(List workers) { + this.workers = workers; + } + + public abstract Result computeResult(Input input); +} +``` + +The `Master` class has a list of `Worker` objects. The `computeResult` method is abstract and should be implemented in a subclass to define how the work is divided and how the results are aggregated. + +```java +// The Worker class is responsible for performing the actual computation. +public abstract class Worker extends Thread { + protected Input input; + + public void setInput(Input input) { + this.input = input; + } + + public abstract Result compute(); +} +``` + +The `Worker` class extends `Thread`, allowing it to perform computations in parallel. The `compute` method is abstract and should be implemented in a subclass to define the actual computation logic. + +```java +// The Input and Result classes are used to encapsulate the input data and the result data. +public abstract class Input { + public final T data; + + public Input(T data) { + this.data = data; + } + + public abstract List> divideData(int num); +} + +public abstract class Result { + public final T data; + + public Result(T data) { + this.data = data; + } +} +``` + +The `Input` class has a `divideData` method that is used to divide the input data into subtasks. The `Result` class simply encapsulates the result data. + +## When to Use the Master-Worker Pattern in Java + +* Suitable for scenarios where a task can be decomposed into smaller, independent tasks. +* Useful in applications requiring concurrent execution to enhance performance. +* Applicable in distributed computing where tasks need to be processed by multiple processors or machines. + +## Master-Worker Pattern Java Tutorials + +* [Master-Worker Pattern (Gigaspaces)](https://docs.gigaspaces.com/sbp/master-worker-pattern.html) + +## Real-World Applications of Master-Worker Pattern in Java + +* Implemented in distributed systems to manage tasks across different computing resources. +* Used in server architectures to process multiple client requests simultaneously. +* Utilized in scientific computation frameworks where large datasets require parallel processing. + +## Benefits and Trade-offs of Master-Worker Pattern + +Benefits: + +* Enhances performance by parallelizing tasks. +* Increases responsiveness of systems handling large volumes of requests. +* Provides a clear separation of concerns between task coordination and task execution, simplifying design. + +Trade-offs: + +* Complexity in managing synchronization and state consistency between master and workers. +* Overhead of managing communication between master and workers, especially in distributed environments. + +## Related Java Design Patterns + +* Task Parallelism and Data Parallelism: Master-Worker utilizes these patterns to divide work into tasks or data segments. +* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Similar in structure but focuses on balancing production and consumption rates; Master-Worker is more about task distribution and aggregation. +* [Pipeline](https://java-design-patterns.com/patterns/pipeline/): Both organize processing steps but Pipeline arranges them linearly whereas Master-Worker may not impose such a sequence. + +## References and Credits + +* [Distributed Systems: Principles and Paradigms](https://amzn.to/3UN2vbH) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) diff --git a/master-worker-pattern/etc/master-worker-pattern.urm.png b/master-worker/etc/master-worker-pattern.urm.png similarity index 100% rename from master-worker-pattern/etc/master-worker-pattern.urm.png rename to master-worker/etc/master-worker-pattern.urm.png diff --git a/master-worker-pattern/etc/master-worker-pattern.urm.puml b/master-worker/etc/master-worker-pattern.urm.puml similarity index 100% rename from master-worker-pattern/etc/master-worker-pattern.urm.puml rename to master-worker/etc/master-worker-pattern.urm.puml diff --git a/master-worker/etc/master-worker.urm.puml b/master-worker/etc/master-worker.urm.puml new file mode 100644 index 000000000000..d5fc9e5d0967 --- /dev/null +++ b/master-worker/etc/master-worker.urm.puml @@ -0,0 +1,78 @@ +@startuml +package com.iluwatar.masterworker.system.systemmaster { + class ArrayTransposeMaster { + + ArrayTransposeMaster(numOfWorkers : int) + ~ aggregateData() : ArrayResult + ~ setWorkers(num : int) : ArrayList + } + abstract class Master { + - allResultData : Hashtable> + - expectedNumResults : int + - finalResult : Result + - numOfWorkers : int + - workers : List + ~ Master(numOfWorkers : int) + ~ aggregateData() : Result {abstract} + - collectResult(data : Result, workerId : int) + - divideWork(input : Input) + + doWork(input : Input) + ~ getAllResultData() : Hashtable> + ~ getExpectedNumResults() : int + + getFinalResult() : Result + ~ getWorkers() : List + + receiveData(data : Result, w : Worker) + ~ setWorkers(int) : List {abstract} + } +} +package com.iluwatar.masterworker.system { + class ArrayTransposeMasterWorker { + + ArrayTransposeMasterWorker() + ~ setMaster(numOfWorkers : int) : Master + } + abstract class MasterWorker { + - master : Master + + MasterWorker(numOfWorkers : int) + + getResult(input : Input) : Result + ~ setMaster(int) : Master {abstract} + } +} +package com.iluwatar.masterworker { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class ArrayInput { + + ArrayInput(data : int[][]) + + divideData(num : int) : List> + ~ makeDivisions(data : int[][], num : int) : int[] {static} + } + class ArrayResult { + + ArrayResult(data : int[][]) + } + class ArrayUtilityMethods { + - LOGGER : Logger {static} + - RANDOM : SecureRandom {static} + + ArrayUtilityMethods() + + arraysSame(a1 : int[], a2 : int[]) : boolean {static} + + createRandomIntMatrix(rows : int, columns : int) : int[][] {static} + + matricesSame(m1 : int[][], m2 : int[][]) : boolean {static} + + printMatrix(matrix : int[][]) {static} + } + abstract class Input { + + data : T + + Input(data : T) + + divideData(int) : List> {abstract} + } + abstract class Result { + + data : T + + Result(data : T) + } +} +Master --> "-finalResult" Result +MasterWorker --> "-master" Master +ArrayInput --|> Input +ArrayResult --|> Result +ArrayTransposeMasterWorker --|> MasterWorker +ArrayTransposeMaster --|> Master +@enduml \ No newline at end of file diff --git a/master-worker-pattern/pom.xml b/master-worker/pom.xml similarity index 89% rename from master-worker-pattern/pom.xml rename to master-worker/pom.xml index 84d2e2e5691b..630fbb6f5b84 100644 --- a/master-worker-pattern/pom.xml +++ b/master-worker/pom.xml @@ -32,8 +32,16 @@ java-design-patterns 1.26.0-SNAPSHOT - master-worker-pattern + master-worker + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/App.java b/master-worker/src/main/java/com/iluwatar/masterworker/App.java similarity index 96% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/App.java rename to master-worker/src/main/java/com/iluwatar/masterworker/App.java index 483729f725c7..fbe31eca8b76 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/App.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/App.java @@ -33,13 +33,14 @@ import lombok.extern.slf4j.Slf4j; /** - *

The Master-Worker pattern is used when the problem at hand can be solved by + * The Master-Worker pattern is used when the problem at hand can be solved by * dividing into multiple parts which need to go through the same computation and may need to be * aggregated to get final result. Parallel processing is performed using a system consisting of a * master and some number of workers, where a master divides the work among the workers, gets the * result back from them and assimilates all the results to give final result. The only * communication is between the master and the worker - none of the workers communicate among one - * another and the user only communicates with the master to get required job done.

+ * another and the user only communicates with the master to get required job done. + * *

In our example, we have generic abstract classes {@link MasterWorker}, {@link Master} and * {@link Worker} which have to be extended by the classes which will perform the specific job at * hand (in this case finding transpose of matrix, done by {@link ArrayTransposeMasterWorker}, @@ -52,9 +53,8 @@ * result. We also have 2 abstract classes {@link Input} and {@link Result}, which contain the input * data and result data respectively. The Input class also has an abstract method divideData which * defines how the data is to be divided into segments. These classes are extended by {@link - * ArrayInput} and {@link ArrayResult}.

+ * ArrayInput} and {@link ArrayResult}. */ - @Slf4j public class App { @@ -63,7 +63,6 @@ public class App { * * @param args command line args */ - public static void main(String[] args) { var mw = new ArrayTransposeMasterWorker(); var rows = 10; @@ -78,5 +77,4 @@ public static void main(String[] args) { LOGGER.info("Please enter non-zero input"); } } - } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayInput.java b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayInput.java similarity index 89% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayInput.java rename to master-worker/src/main/java/com/iluwatar/masterworker/ArrayInput.java index 84e7aef20fc3..3d492aa08089 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayInput.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayInput.java @@ -28,10 +28,7 @@ import java.util.Arrays; import java.util.List; -/** - * Class ArrayInput extends abstract class {@link Input} and contains data of type int[][]. - */ - +/** Class ArrayInput extends abstract class {@link Input} and contains data of type int[][]. */ public class ArrayInput extends Input { public ArrayInput(int[][] data) { @@ -39,13 +36,13 @@ public ArrayInput(int[][] data) { } static int[] makeDivisions(int[][] data, int num) { - var initialDivision = data.length / num; //equally dividing + var initialDivision = data.length / num; // equally dividing var divisions = new int[num]; Arrays.fill(divisions, initialDivision); if (initialDivision * num != data.length) { var extra = data.length - initialDivision * num; var l = 0; - //equally dividing extra among all parts + // equally dividing extra among all parts while (extra > 0) { divisions[l] = divisions[l] + 1; extra--; @@ -66,7 +63,7 @@ public List> divideData(int num) { } else { var divisions = makeDivisions(this.data, num); var result = new ArrayList>(num); - var rowsDone = 0; //number of rows divided so far + var rowsDone = 0; // number of rows divided so far for (var i = 0; i < num; i++) { var rows = divisions[i]; if (rows != 0) { @@ -76,7 +73,7 @@ public List> divideData(int num) { var dividedInput = new ArrayInput(divided); result.add(dividedInput); } else { - break; //rest of divisions will also be 0 + break; // rest of divisions will also be 0 } } return result; diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayResult.java b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayResult.java similarity index 93% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayResult.java rename to master-worker/src/main/java/com/iluwatar/masterworker/ArrayResult.java index a81fd6c3d234..fa99b5ce6c1f 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayResult.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayResult.java @@ -24,10 +24,7 @@ */ package com.iluwatar.masterworker; -/** - * Class ArrayResult extends abstract class {@link Result} and contains data of type int[][]. - */ - +/** Class ArrayResult extends abstract class {@link Result} and contains data of type int[][]. */ public class ArrayResult extends Result { public ArrayResult(int[][] data) { diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java similarity index 92% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java rename to master-worker/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java index 865c6f229010..8d3e9e7904ad 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java @@ -27,10 +27,7 @@ import java.security.SecureRandom; import lombok.extern.slf4j.Slf4j; -/** - * Class ArrayUtilityMethods has some utility methods for matrices and arrays. - */ - +/** Class ArrayUtilityMethods has some utility methods for matrices and arrays. */ @Slf4j public class ArrayUtilityMethods { @@ -40,9 +37,8 @@ public class ArrayUtilityMethods { * Method arraysSame compares 2 arrays @param a1 and @param a2 and @return whether their values * are equal (boolean). */ - public static boolean arraysSame(int[] a1, int[] a2) { - //compares if 2 arrays have the same value + // compares if 2 arrays have the same value if (a1.length != a2.length) { return false; } else { @@ -63,7 +59,6 @@ public static boolean arraysSame(int[] a1, int[] a2) { * Method matricesSame compares 2 matrices @param m1 and @param m2 and @return whether their * values are equal (boolean). */ - public static boolean matricesSame(int[][] m1, int[][] m2) { if (m1.length != m2.length) { return false; @@ -90,19 +85,16 @@ public static int[][] createRandomIntMatrix(int rows, int columns) { var matrix = new int[rows][columns]; for (var i = 0; i < rows; i++) { for (var j = 0; j < columns; j++) { - //filling cells in matrix + // filling cells in matrix matrix[i][j] = RANDOM.nextInt(10); } } return matrix; } - /** - * Method printMatrix prints input matrix @param matrix. - */ - + /** Method printMatrix prints input matrix @param matrix. */ public static void printMatrix(int[][] matrix) { - //prints out int[][] + // prints out int[][] for (var ints : matrix) { for (var j = 0; j < matrix[0].length; j++) { LOGGER.info(ints[j] + " "); @@ -110,5 +102,4 @@ public static void printMatrix(int[][] matrix) { LOGGER.info(""); } } - } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Input.java b/master-worker/src/main/java/com/iluwatar/masterworker/Input.java similarity index 99% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/Input.java rename to master-worker/src/main/java/com/iluwatar/masterworker/Input.java index 6dd46865b1b0..4e7c73d8633e 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Input.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/Input.java @@ -32,7 +32,6 @@ * * @param T will be type of data. */ - public abstract class Input { public final T data; diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Result.java b/master-worker/src/main/java/com/iluwatar/masterworker/Result.java similarity index 99% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/Result.java rename to master-worker/src/main/java/com/iluwatar/masterworker/Result.java index 61450d65694d..cc0bd4682101 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/Result.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/Result.java @@ -29,7 +29,6 @@ * * @param T will be type of data. */ - public abstract class Result { public final T data; diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java similarity index 99% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java rename to master-worker/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java index dcbd47a3f842..0ebef7dd82ff 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java @@ -31,7 +31,6 @@ * Class ArrayTransposeMasterWorker extends abstract class {@link MasterWorker} and specifically * solves the problem of finding transpose of input array. */ - public class ArrayTransposeMasterWorker extends MasterWorker { public ArrayTransposeMasterWorker() { diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java similarity index 95% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java rename to master-worker/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java index 8027161b813c..5f785ebff144 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java @@ -28,10 +28,7 @@ import com.iluwatar.masterworker.Result; import com.iluwatar.masterworker.system.systemmaster.Master; -/** - * The abstract MasterWorker class which contains reference to master. - */ - +/** The abstract MasterWorker class which contains reference to master. */ public abstract class MasterWorker { private final Master master; @@ -46,4 +43,3 @@ public Result getResult(Input input) { return this.master.getFinalResult(); } } - diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java similarity index 95% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java rename to master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java index da3e6c5d2e34..50ee1c2b16fe 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java @@ -35,7 +35,6 @@ * Class ArrayTransposeMaster extends abstract class {@link Master} and contains definition of * aggregateData, which will obtain final result from all data obtained and for setWorkers. */ - public class ArrayTransposeMaster extends Master { public ArrayTransposeMaster(int numOfWorkers) { super(numOfWorkers); @@ -43,7 +42,7 @@ public ArrayTransposeMaster(int numOfWorkers) { @Override ArrayList setWorkers(int num) { - //i+1 will be id + // i+1 will be id return IntStream.range(0, num) .mapToObj(i -> new ArrayTransposeWorker(this, i + 1)) .collect(Collectors.toCollection(() -> new ArrayList<>(num))); @@ -60,20 +59,19 @@ ArrayResult aggregateData() { columns += ((ArrayResult) elements.nextElement()).data[0].length; } var resultData = new int[rows][columns]; - var columnsDone = 0; //columns aggregated so far + var columnsDone = 0; // columns aggregated so far var workers = this.getWorkers(); for (var i = 0; i < this.getExpectedNumResults(); i++) { - //result obtained from ith worker + // result obtained from ith worker var worker = workers.get(i); var workerId = worker.getWorkerId(); var work = ((ArrayResult) allResultData.get(workerId)).data; for (var m = 0; m < work.length; m++) { - //m = row number, n = columns number + // m = row number, n = columns number System.arraycopy(work[m], 0, resultData[m], columnsDone, work[0].length); } columnsDone += work[0].length; } return new ArrayResult(resultData); } - } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java similarity index 94% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java rename to master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java index ac0cece38ca5..791de82c9266 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java @@ -29,6 +29,7 @@ import com.iluwatar.masterworker.system.systemworkers.Worker; import java.util.Hashtable; import java.util.List; +import lombok.Getter; /** * The abstract Master class which contains private fields numOfWorkers (number of workers), workers @@ -36,13 +37,12 @@ * number of results), allResultData (hashtable of results obtained from workers, mapped by their * ids) and finalResult (aggregated from allResultData). */ - public abstract class Master { private final int numOfWorkers; private final List workers; private final Hashtable> allResultData; private int expectedNumResults; - private Result finalResult; + @Getter private Result finalResult; Master(int numOfWorkers) { this.numOfWorkers = numOfWorkers; @@ -52,10 +52,6 @@ public abstract class Master { this.finalResult = null; } - public Result getFinalResult() { - return this.finalResult; - } - Hashtable> getAllResultData() { return this.allResultData; } @@ -79,7 +75,7 @@ private void divideWork(Input input) { if (dividedInput != null) { this.expectedNumResults = dividedInput.size(); for (var i = 0; i < this.expectedNumResults; i++) { - //ith division given to ith worker in this.workers + // ith division given to ith worker in this.workers this.workers.get(i).setReceivedData(this, dividedInput.get(i)); this.workers.get(i).start(); } @@ -94,14 +90,14 @@ private void divideWork(Input input) { } public void receiveData(Result data, Worker w) { - //check if can receive..if yes: + // check if we can receive... if yes: collectResult(data, w.getWorkerId()); } private void collectResult(Result data, int workerId) { this.allResultData.put(workerId, data); if (this.allResultData.size() == this.expectedNumResults) { - //all data received + // all data received this.finalResult = aggregateData(); } } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java similarity index 93% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java rename to master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java index 110a15a1ae99..0d83fd3eb1e3 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java @@ -32,7 +32,6 @@ * Class ArrayTransposeWorker extends abstract class {@link Worker} and defines method * executeOperation(), to be performed on data received from master. */ - public class ArrayTransposeWorker extends Worker { public ArrayTransposeWorker(Master master, int id) { @@ -41,14 +40,14 @@ public ArrayTransposeWorker(Master master, int id) { @Override ArrayResult executeOperation() { - //number of rows in result matrix is equal to number of columns in input matrix and vice versa + // number of rows in result matrix is equal to number of columns in input matrix and vice versa var arrayInput = (ArrayInput) this.getReceivedData(); final var rows = arrayInput.data[0].length; final var cols = arrayInput.data.length; var resultData = new int[rows][cols]; for (var i = 0; i < cols; i++) { for (var j = 0; j < rows; j++) { - //flipping element positions along diagonal + // flipping element positions along diagonal resultData[j][i] = arrayInput.data[i][j]; } } diff --git a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java similarity index 92% rename from master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java rename to master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java index 6777dba27ac5..0f154c1953d8 100644 --- a/master-worker-pattern/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java @@ -27,15 +27,15 @@ import com.iluwatar.masterworker.Input; import com.iluwatar.masterworker.Result; import com.iluwatar.masterworker.system.systemmaster.Master; +import lombok.Getter; /** * The abstract Worker class which extends Thread class to enable parallel processing. Contains * fields master(holding reference to master), workerId (unique id) and receivedData(from master). */ - public abstract class Worker extends Thread { private final Master master; - private final int workerId; + @Getter private final int workerId; private Input receivedData; Worker(Master master, int id) { @@ -44,16 +44,12 @@ public abstract class Worker extends Thread { this.receivedData = null; } - public int getWorkerId() { - return this.workerId; - } - Input getReceivedData() { return this.receivedData; } public void setReceivedData(Master m, Input i) { - //check if ready to receive..if yes: + // check if we are ready to receive... if yes: this.receivedData = i; } @@ -63,7 +59,7 @@ private void sendToMaster(Result data) { this.master.receiveData(data, this); } - public void run() { //from Thread class + public void run() { // from Thread class var work = executeOperation(); sendToMaster(work); } diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java similarity index 76% rename from master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java rename to master-worker/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java index 2f259573e2be..30cee36ce34e 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java @@ -30,10 +30,7 @@ import java.util.Random; import org.junit.jupiter.api.Test; -/** - * Testing divideData method in {@link ArrayInput} class. - */ - +/** Testing divideData method in {@link ArrayInput} class. */ class ArrayInputTest { @Test @@ -49,14 +46,14 @@ void divideDataTest() { } var i = new ArrayInput(inputMatrix); var table = i.divideData(4); - var division1 = new int[][]{inputMatrix[0], inputMatrix[1], inputMatrix[2]}; - var division2 = new int[][]{inputMatrix[3], inputMatrix[4], inputMatrix[5]}; - var division3 = new int[][]{inputMatrix[6], inputMatrix[7]}; - var division4 = new int[][]{inputMatrix[8], inputMatrix[9]}; - assertTrue(matricesSame(table.get(0).data, division1) - && matricesSame(table.get(1).data, division2) - && matricesSame(table.get(2).data, division3) - && matricesSame(table.get(3).data, division4)); + var division1 = new int[][] {inputMatrix[0], inputMatrix[1], inputMatrix[2]}; + var division2 = new int[][] {inputMatrix[3], inputMatrix[4], inputMatrix[5]}; + var division3 = new int[][] {inputMatrix[6], inputMatrix[7]}; + var division4 = new int[][] {inputMatrix[8], inputMatrix[9]}; + assertTrue( + matricesSame(table.get(0).data, division1) + && matricesSame(table.get(1).data, division2) + && matricesSame(table.get(2).data, division3) + && matricesSame(table.get(3).data, division4)); } - } diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java similarity index 86% rename from master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java rename to master-worker/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java index 2e2a1bc54e05..2c3cbaa7595b 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java @@ -28,24 +28,20 @@ import org.junit.jupiter.api.Test; -/** - * Testing utility methods in {@link ArrayUtilityMethods} class. - */ - +/** Testing utility methods in {@link ArrayUtilityMethods} class. */ class ArrayUtilityMethodsTest { @Test void arraysSameTest() { - var arr1 = new int[]{1, 4, 2, 6}; - var arr2 = new int[]{1, 4, 2, 6}; + var arr1 = new int[] {1, 4, 2, 6}; + var arr2 = new int[] {1, 4, 2, 6}; assertTrue(ArrayUtilityMethods.arraysSame(arr1, arr2)); } @Test void matricesSameTest() { - var matrix1 = new int[][]{{1, 4, 2, 6}, {5, 8, 6, 7}}; - var matrix2 = new int[][]{{1, 4, 2, 6}, {5, 8, 6, 7}}; + var matrix1 = new int[][] {{1, 4, 2, 6}, {5, 8, 6, 7}}; + var matrix2 = new int[][] {{1, 4, 2, 6}, {5, 8, 6, 7}}; assertTrue(ArrayUtilityMethods.matricesSame(matrix1, matrix2)); } - } diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java similarity index 80% rename from master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java rename to master-worker/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java index d8a014b61f51..9d037ada05f3 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java @@ -31,29 +31,28 @@ import com.iluwatar.masterworker.ArrayUtilityMethods; import org.junit.jupiter.api.Test; -/** - * Testing getResult method in {@link ArrayTransposeMasterWorker} class. - */ - +/** Testing getResult method in {@link ArrayTransposeMasterWorker} class. */ class ArrayTransposeMasterWorkerTest { @Test void getResultTest() { var atmw = new ArrayTransposeMasterWorker(); - var matrix = new int[][]{ - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5} - }; - var matrixTranspose = new int[][]{ - {1, 1, 1, 1, 1}, - {2, 2, 2, 2, 2}, - {3, 3, 3, 3, 3}, - {4, 4, 4, 4, 4}, - {5, 5, 5, 5, 5} - }; + var matrix = + new int[][] { + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5} + }; + var matrixTranspose = + new int[][] { + {1, 1, 1, 1, 1}, + {2, 2, 2, 2, 2}, + {3, 3, 3, 3, 3}, + {4, 4, 4, 4, 4}, + {5, 5, 5, 5, 5} + }; var i = new ArrayInput(matrix); var r = (ArrayResult) atmw.getResult(i); assertTrue(ArrayUtilityMethods.matricesSame(r.data, matrixTranspose)); diff --git a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java similarity index 91% rename from master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java rename to master-worker/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java index a6deb3a83eab..b1fedbd97faf 100644 --- a/master-worker-pattern/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java @@ -31,22 +31,18 @@ import com.iluwatar.masterworker.system.systemmaster.ArrayTransposeMaster; import org.junit.jupiter.api.Test; -/** - * Testing executeOperation method in {@link ArrayTransposeWorker} class. - */ - +/** Testing executeOperation method in {@link ArrayTransposeWorker} class. */ class ArrayTransposeWorkerTest { @Test void executeOperationTest() { var atm = new ArrayTransposeMaster(1); var atw = new ArrayTransposeWorker(atm, 1); - var matrix = new int[][]{{2, 4}, {3, 5}}; - var matrixTranspose = new int[][]{{2, 3}, {4, 5}}; + var matrix = new int[][] {{2, 4}, {3, 5}}; + var matrixTranspose = new int[][] {{2, 3}, {4, 5}}; var i = new ArrayInput(matrix); atw.setReceivedData(atm, i); var r = atw.executeOperation(); assertTrue(ArrayUtilityMethods.matricesSame(r.data, matrixTranspose)); } - } diff --git a/mediator/README.md b/mediator/README.md index 16ff6ec07af4..628d976e072e 100644 --- a/mediator/README.md +++ b/mediator/README.md @@ -1,52 +1,43 @@ --- -title: Mediator +title: "Mediator Pattern in Java: Simplifying Object Communications in Complex Systems" +shortTitle: Mediator +description: "Learn how the Mediator Design Pattern reduces complexity in object communication and improves system maintainability with a centralized mediator in Java. Explore examples and implementation." category: Behavioral language: en tag: - - Gang Of Four - - Decoupling + - Decoupling + - Gang Of Four + - Messaging + - Object composition --- -## Intent +## Also known as -Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling -by keeping objects from referring to each other explicitly, and it lets you vary their interaction -independently. +* Controller -## Explanation +## Intent of Mediator Design Pattern + +The Mediator design pattern is intended to reduce the complexity of communication between multiple objects or classes in a system. It achieves this by providing a centralized mediator class that handles the interactions between different classes, thus reducing their direct dependencies on each other. + +## Detailed Explanation of Mediator Pattern with Real-World Examples Real-world example -> Rogue, wizard, hobbit, and hunter have decided to join their forces and travel in the same -> party. To avoid coupling each member with each other, they use the party interface to -> communicate with each other. +> Imagine an air traffic control system at a busy airport, where the air traffic controller acts as a mediator. In this scenario, numerous airplanes wish to take off, land, or navigate around the airport's airspace. Instead of each pilot communicating directly with every other pilot, which could lead to confusion and potential accidents, all communication goes through the air traffic controller. The controller receives requests, processes them, and gives clear, organized instructions to each pilot. This centralized system reduces the complexity of communications and ensures safety and efficiency in managing the airport's operations. This is analogous to the Mediator design pattern in software, where a central mediator class handles and coordinates the interaction between different objects or systems. In plain words -> Mediator decouples a set of classes by forcing their communications flow through a mediating -> object. +> Mediator decouples a set of classes by forcing their communications flow through a mediating object. Wikipedia says -> In software engineering, the mediator pattern defines an object that encapsulates how a set of -> objects interact. This pattern is considered to be a behavioral pattern due to the way it can -> alter the program's running behavior. In object-oriented programming, programs often consist of -> many classes. Business logic and computation are distributed among these classes. However, as -> more classes are added to a program, especially during maintenance and/or refactoring, the -> problem of communication between these classes may become more complex. This makes the program -> harder to read and maintain. Furthermore, it can become difficult to change the program, since -> any change may affect code in several other classes. With the mediator pattern, communication -> between objects is encapsulated within a mediator object. Objects no longer communicate directly -> with each other, but instead communicate through the mediator. This reduces the dependencies -> between communicating objects, thereby reducing coupling. +> In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior. In object-oriented programming, programs often consist of many classes. Business logic and computation are distributed among these classes. However, as more classes are added to a program, especially during maintenance and/or refactoring, the problem of communication between these classes may become more complex. This makes the program harder to read and maintain. Furthermore, it can become difficult to change the program, since any change may affect code in several other classes. With the mediator pattern, communication between objects is encapsulated within a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby reducing coupling. -**Programmatic Example** +## Programmatic Example of Mediator Pattern in Java -In this example, the mediator encapsulates how a set of objects interact. Instead of referring to -each other directly they use the mediator interface. +In this example, the mediator encapsulates how a set of objects interact. Instead of referring to each other directly, they use the mediator interface. -The party members `Rogue`, `Wizard`, `Hobbit`, and `Hunter` all inherit from the `PartyMemberBase` -implementing the `PartyMember` interface. +The party members `Rogue`, `Wizard`, `Hobbit`, and `Hunter` all inherit from the `PartyMemberBase`implementing the `PartyMember` interface. ```java public interface PartyMember { @@ -135,6 +126,8 @@ public class PartyImpl implements Party { Here's a demo showing the mediator pattern in action. ```java +public static void main(String[] args) { + // create party and members Party party = new PartyImpl(); var hobbit = new Hobbit(); @@ -154,38 +147,39 @@ Here's a demo showing the mediator pattern in action. wizard.act(Action.TALE); rogue.act(Action.GOLD); hunter.act(Action.HUNT); +} ``` Here's the console output from running the example. ``` -Hobbit joins the party -Wizard joins the party -Rogue joins the party -Hunter joins the party -Hobbit spotted enemies -Wizard runs for cover -Rogue runs for cover -Hunter runs for cover -Wizard tells a tale -Hobbit comes to listen -Rogue comes to listen -Hunter comes to listen -Rogue found gold -Hobbit takes his share of the gold -Wizard takes his share of the gold -Hunter takes his share of the gold -Hunter hunted a rabbit -Hobbit arrives for dinner -Wizard arrives for dinner -Rogue arrives for dinner +14:05:15.081 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hobbit joins the party +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Wizard joins the party +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Rogue joins the party +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hunter joins the party +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hobbit spotted enemies +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Wizard runs for cover +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Rogue runs for cover +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hunter runs for cover +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Wizard tells a tale +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hobbit comes to listen +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Rogue comes to listen +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hunter comes to listen +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Rogue found gold +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hobbit takes his share of the gold +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Wizard takes his share of the gold +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hunter takes his share of the gold +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hunter hunted a rabbit +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Hobbit arrives for dinner +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Wizard arrives for dinner +14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Rogue arrives for dinner ``` -## Class diagram +## Detailed Explanation of Mediator Pattern with Real-World Examples -![alt text](./etc/mediator_1.png "Mediator") +![Mediator](./etc/mediator_1.png "Mediator") -## Applicability +## When to Use the Mediator Pattern in Java Use the Mediator pattern when @@ -193,15 +187,34 @@ Use the Mediator pattern when * Reusing an object is difficult because it refers to and communicates with many other objects * A behavior that's distributed between several classes should be customizable without a lot of subclassing -## Known uses +## Real-World Applications of Mediator Pattern in Java * All scheduleXXX() methods of [java.util.Timer](http://docs.oracle.com/javase/8/docs/api/java/util/Timer.html) * [java.util.concurrent.Executor#execute()](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable-) * submit() and invokeXXX() methods of [java.util.concurrent.ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) * scheduleXXX() methods of [java.util.concurrent.ScheduledExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html) * [java.lang.reflect.Method#invoke()](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-) +* Java Message Service (JMS) uses mediators to handle message exchanges between clients and servers. +* JavaBeans property change support class (java.beans.PropertyChangeSupport) acts as a mediator by handling communication between beans regarding property changes. + +## Benefits and Trade-offs of Mediator Pattern + +Benefits: + +* Reduces coupling between components of a program, fostering better organization and easier maintenance. +* Centralizes control. The mediator pattern centralizes the control logic, making it easier to comprehend and manage. + +Trade-offs: + +* Mediator can become a god object coupled with all classes in the system, gaining too much responsibility and complexity. + +## Related Java Design Patterns + +* [Observer](https://java-design-patterns.com/patterns/observer/): Often used together, where the mediator pattern can use the observer pattern to notify various objects about state changes. Mediators effectively act as a channel of communication managed by an observer. +* [Facade](https://java-design-patterns.com/patterns/facade/): Mediator simplifies communication between components, similar to how a facade simplifies a subsystem interface, but a mediator’s colleagues can communicate back to the mediator. +* [Command](https://java-design-patterns.com/patterns/command/): Commands can be mediated as they are dispatched to their receivers, encapsulating a request as an object. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) diff --git a/mediator/pom.xml b/mediator/pom.xml index 6fa9bbaf405b..737f818838b2 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -34,6 +34,14 @@ mediator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/mediator/src/main/java/com/iluwatar/mediator/Action.java b/mediator/src/main/java/com/iluwatar/mediator/Action.java index 307aba9f5a30..d61873e69d70 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Action.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Action.java @@ -24,9 +24,9 @@ */ package com.iluwatar.mediator; -/** - * Action enumeration. - */ +import lombok.Getter; + +/** Action enumeration. */ public enum Action { HUNT("hunted a rabbit", "arrives for dinner"), TALE("tells a tale", "comes to listen"), @@ -35,17 +35,13 @@ public enum Action { NONE("", ""); private final String title; - private final String description; + @Getter private final String description; Action(String title, String description) { this.title = title; this.description = description; } - public String getDescription() { - return description; - } - public String toString() { return title; } diff --git a/mediator/src/main/java/com/iluwatar/mediator/App.java b/mediator/src/main/java/com/iluwatar/mediator/App.java index c5e4c64cdd57..aaab69f4d23b 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/App.java +++ b/mediator/src/main/java/com/iluwatar/mediator/App.java @@ -41,9 +41,8 @@ * the mediator. This reduces the dependencies between communicating objects, thereby lowering the * coupling. * - *

In this example the mediator encapsulates how a set of objects ({@link PartyMember}) - * interact. Instead of referring to each other directly they use the mediator ({@link Party}) - * interface. + *

In this example the mediator encapsulates how a set of objects ({@link PartyMember}) interact. + * Instead of referring to each other directly they use the mediator ({@link Party}) interface. */ public class App { diff --git a/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java b/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java index effe0dd15a3e..c0b5fc967bcc 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java @@ -1,37 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Hobbit party member. - */ -public class Hobbit extends PartyMemberBase { - - @Override - public String toString() { - return "Hobbit"; - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Hobbit party member. */ +public class Hobbit extends PartyMemberBase { + + @Override + public String toString() { + return "Hobbit"; + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Hunter.java b/mediator/src/main/java/com/iluwatar/mediator/Hunter.java index 783cd1aa9435..ddf0c2810440 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Hunter.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Hunter.java @@ -1,36 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Hunter party member. - */ -public class Hunter extends PartyMemberBase { - - @Override - public String toString() { - return "Hunter"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Hunter party member. */ +public class Hunter extends PartyMemberBase { + + @Override + public String toString() { + return "Hunter"; + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Party.java b/mediator/src/main/java/com/iluwatar/mediator/Party.java index 387f4809f2cf..b7c87f162a68 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Party.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Party.java @@ -1,36 +1,33 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Party interface. - */ -public interface Party { - - void addMember(PartyMember member); - - void act(PartyMember actor, Action action); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Party interface. */ +public interface Party { + + void addMember(PartyMember member); + + void act(PartyMember actor, Action action); +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java index c61e39faf123..92e37506f9d9 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java @@ -1,55 +1,53 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -import java.util.ArrayList; -import java.util.List; - -/** - * Party implementation. - */ -public class PartyImpl implements Party { - - private final List members; - - public PartyImpl() { - members = new ArrayList<>(); - } - - @Override - public void act(PartyMember actor, Action action) { - for (var member : members) { - if (!member.equals(actor)) { - member.partyAction(action); - } - } - } - - @Override - public void addMember(PartyMember member) { - members.add(member); - member.joinedParty(this); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +import java.util.ArrayList; +import java.util.List; + +/** Party implementation. */ +public class PartyImpl implements Party { + + private final List members; + + public PartyImpl() { + members = new ArrayList<>(); + } + + @Override + public void act(PartyMember actor, Action action) { + for (var member : members) { + if (!member.equals(actor)) { + member.partyAction(action); + } + } + } + + @Override + public void addMember(PartyMember member) { + members.add(member); + member.joinedParty(this); + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java b/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java index dc1aec4e4b62..4fad4e85b646 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java @@ -24,9 +24,7 @@ */ package com.iluwatar.mediator; -/** - * Interface for party members interacting with {@link Party}. - */ +/** Interface for party members interacting with {@link Party}. */ public interface PartyMember { void joinedParty(Party party); diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java b/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java index 21909cd3c8d1..8a67b8f59f7c 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java @@ -1,59 +1,56 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -import lombok.extern.slf4j.Slf4j; - -/** - * Abstract base class for party members. - */ -@Slf4j -public abstract class PartyMemberBase implements PartyMember { - - protected Party party; - - @Override - public void joinedParty(Party party) { - LOGGER.info("{} joins the party", this); - this.party = party; - } - - @Override - public void partyAction(Action action) { - LOGGER.info("{} {}", this, action.getDescription()); - } - - @Override - public void act(Action action) { - if (party != null) { - LOGGER.info("{} {}", this, action); - party.act(this, action); - } - } - - @Override - public abstract String toString(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +import lombok.extern.slf4j.Slf4j; + +/** Abstract base class for party members. */ +@Slf4j +public abstract class PartyMemberBase implements PartyMember { + + protected Party party; + + @Override + public void joinedParty(Party party) { + LOGGER.info("{} joins the party", this); + this.party = party; + } + + @Override + public void partyAction(Action action) { + LOGGER.info("{} {}", this, action.getDescription()); + } + + @Override + public void act(Action action) { + if (party != null) { + LOGGER.info("{} {}", this, action); + party.act(this, action); + } + } + + @Override + public abstract String toString(); +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Rogue.java b/mediator/src/main/java/com/iluwatar/mediator/Rogue.java index 2724b46e8425..5edeff28fbf8 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Rogue.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Rogue.java @@ -1,37 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Rogue party member. - */ -public class Rogue extends PartyMemberBase { - - @Override - public String toString() { - return "Rogue"; - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Rogue party member. */ +public class Rogue extends PartyMemberBase { + + @Override + public String toString() { + return "Rogue"; + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Wizard.java b/mediator/src/main/java/com/iluwatar/mediator/Wizard.java index d43b95e390e1..126766ea9a95 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Wizard.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Wizard.java @@ -1,37 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Wizard party member. - */ -public class Wizard extends PartyMemberBase { - - @Override - public String toString() { - return "Wizard"; - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Wizard party member. */ +public class Wizard extends PartyMemberBase { + + @Override + public String toString() { + return "Wizard"; + } +} diff --git a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java index 47d2727cc6e0..85be64afe7ac 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.mediator; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java index dd89ee8734a4..7353bc83b354 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java @@ -30,11 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/19/15 - 10:00 PM - * - * @author Jeroen Meulemeester - */ +/** PartyImplTest */ class PartyImplTest { /** @@ -59,5 +55,4 @@ void testPartyAction() { verifyNoMoreInteractions(partyMember1, partyMember2); } - } diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java index 72270f2f7595..46be9593ae79 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java @@ -42,11 +42,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.LoggerFactory; -/** - * Date: 12/19/15 - 10:13 PM - * - * @author Jeroen Meulemeester - */ +/** PartyMemberTest */ class PartyMemberTest { static Stream dataProvider() { @@ -54,8 +50,7 @@ static Stream dataProvider() { Arguments.of((Supplier) Hobbit::new), Arguments.of((Supplier) Hunter::new), Arguments.of((Supplier) Rogue::new), - Arguments.of((Supplier) Wizard::new) - ); + Arguments.of((Supplier) Wizard::new)); } private InMemoryAppender appender; @@ -70,9 +65,7 @@ void tearDown() { appender.stop(); } - /** - * Verify if a party action triggers the correct output to the std-Out - */ + /** Verify if a party action triggers the correct output to the std-Out */ @ParameterizedTest @MethodSource("dataProvider") void testPartyAction(Supplier memberSupplier) { @@ -80,15 +73,13 @@ void testPartyAction(Supplier memberSupplier) { for (final var action : Action.values()) { member.partyAction(action); - assertEquals(member.toString() + " " + action.getDescription(), appender.getLastMessage()); + assertEquals(member + " " + action.getDescription(), appender.getLastMessage()); } assertEquals(Action.values().length, appender.getLogSize()); } - /** - * Verify if a member action triggers the expected interactions with the party class - */ + /** Verify if a member action triggers the expected interactions with the party class */ @ParameterizedTest @MethodSource("dataProvider") void testAct(Supplier memberSupplier) { @@ -99,20 +90,18 @@ void testAct(Supplier memberSupplier) { final var party = mock(Party.class); member.joinedParty(party); - assertEquals(member.toString() + " joins the party", appender.getLastMessage()); + assertEquals(member + " joins the party", appender.getLastMessage()); for (final var action : Action.values()) { member.act(action); - assertEquals(member.toString() + " " + action.toString(), appender.getLastMessage()); + assertEquals(member + " " + action.toString(), appender.getLastMessage()); verify(party).act(member, action); } assertEquals(Action.values().length + 1, appender.getLogSize()); } - /** - * Verify if {@link PartyMemberBase#toString()} generate the expected output - */ + /** Verify if {@link PartyMemberBase#toString()} generate the expected output */ @ParameterizedTest @MethodSource("dataProvider") void testToString(Supplier memberSupplier) { @@ -142,6 +131,4 @@ public String getLastMessage() { return log.get(log.size() - 1).getFormattedMessage(); } } - - } diff --git a/memento/README.md b/memento/README.md index 7055d2bf12b6..c1c5372b295d 100644 --- a/memento/README.md +++ b/memento/README.md @@ -1,38 +1,44 @@ --- -title: Memento +title: "Memento Pattern in Java: Preserving Object State for Undo Operations" +shortTitle: Memento +description: "Learn how to implement the Memento design pattern in Java to capture and restore object state without violating encapsulation. Ideal for undo functionality in applications." category: Behavioral language: en tag: - - Gang of Four + - Encapsulation + - Gang of Four + - Memory management + - Object composition + - State tracking + - Undo --- ## Also known as -Token +* Snapshot +* Token -## Intent +## Intent of Memento Design Pattern -Without violating encapsulation, capture and externalize an object's internal state so that the -object can be restored to this state later. +The Memento design pattern in Java allows developers to capture and restore an object's internal state without violating encapsulation. -## Explanation +## Detailed Explanation of Memento Pattern with Real-World Examples Real-world example -> We are working on an astrology application where we need to analyze star properties over time. We -> are creating snapshots of star states using the Memento pattern. +> A text editor application can utilize the Memento design pattern in Java to enable undo and redo functionalities. By capturing the current state of a document as a memento each time a change is made, the application can easily restore the document to any previous state. The snapshots are stored in a history list. When the user clicks the undo button, the editor restores the document to the state saved in the most recent memento. This process allows users to revert to previous versions of their document without exposing or altering the internal data structures of the editor. In plain words -> Memento pattern captures object internal state making it easy to store and restore objects in any -> point of time. +> Memento pattern captures object internal state making it easy to store and restore objects in any point of time. Wikipedia says -> The memento pattern is a software design pattern that provides the ability to restore an object to -> its previous state (undo via rollback). +> The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback). -**Programmatic Example** +## Programmatic Example of Memento Pattern in Java + +In our astrology application, we use the Memento pattern to capture and restore the state of star objects. Each state is saved as a memento, allowing us to revert to previous states as needed. Let's first define the types of stars we are capable to handle. @@ -43,12 +49,11 @@ public enum StarType { WHITE_DWARF("white dwarf"), SUPERNOVA("supernova"), DEAD("dead star"); - ... + // ... } ``` -Next, let's jump straight to the essentials. Here's the `Star` class along with the mementos that we -need to manipulate. Especially pay attention to `getMemento` and `setMemento` methods. +Next, let's jump straight to the essentials. Here's the `Star` class along with the mementos that we need to manipulate. Especially pay attention to `getMemento` and `setMemento` methods. ```java public interface StarMemento { @@ -110,7 +115,7 @@ public class Star { private int massTons; // setters and getters -> - ... + // ... } } ``` @@ -118,7 +123,9 @@ public class Star { And finally here's how we use the mementos to store and restore star states. ```java - var states = new Stack<>(); +public static void main(String[] args) { + var states = new Stack(); + var star = new Star(StarType.SUN, 10000000, 500000); LOGGER.info(star.toString()); states.add(star.getMemento()); @@ -133,43 +140,56 @@ And finally here's how we use the mementos to store and restore star states. states.add(star.getMemento()); star.timePasses(); LOGGER.info(star.toString()); - while (states.size() > 0) { - star.setMemento(states.pop()); - LOGGER.info(star.toString()); + while (!states.isEmpty()) { + star.setMemento(states.pop()); + LOGGER.info(star.toString()); } +} ``` Program output: ``` -sun age: 10000000 years mass: 500000 tons -red giant age: 20000000 years mass: 4000000 tons -white dwarf age: 40000000 years mass: 32000000 tons -supernova age: 80000000 years mass: 256000000 tons -dead star age: 160000000 years mass: 2048000000 tons -supernova age: 80000000 years mass: 256000000 tons -white dwarf age: 40000000 years mass: 32000000 tons -red giant age: 20000000 years mass: 4000000 tons -sun age: 10000000 years mass: 500000 tons +14:09:15.878 [main] INFO com.iluwatar.memento.App -- sun age: 10000000 years mass: 500000 tons +14:09:15.880 [main] INFO com.iluwatar.memento.App -- red giant age: 20000000 years mass: 4000000 tons +14:09:15.880 [main] INFO com.iluwatar.memento.App -- white dwarf age: 40000000 years mass: 32000000 tons +14:09:15.880 [main] INFO com.iluwatar.memento.App -- supernova age: 80000000 years mass: 256000000 tons +14:09:15.880 [main] INFO com.iluwatar.memento.App -- dead star age: 160000000 years mass: 2048000000 tons +14:09:15.880 [main] INFO com.iluwatar.memento.App -- supernova age: 80000000 years mass: 256000000 tons +14:09:15.880 [main] INFO com.iluwatar.memento.App -- white dwarf age: 40000000 years mass: 32000000 tons +14:09:15.881 [main] INFO com.iluwatar.memento.App -- red giant age: 20000000 years mass: 4000000 tons +14:09:15.881 [main] INFO com.iluwatar.memento.App -- sun age: 10000000 years mass: 500000 tons ``` -## Class diagram +## When to Use the Memento Pattern in Java -![alt text](./etc/memento.png "Memento") +Use the Memento pattern when -## Applicability +* You need to capture an object's state in Java and restore it later without exposing its internal structure. This is crucial for maintaining encapsulation and simplifying the management of object states. +* A direct interface to obtaining the state would expose implementation details and break the object's encapsulation. -Use the Memento pattern when +## Real-World Applications of Memento Pattern in Java + +The Memento pattern is used in various Java applications, including the java.util.Date and java.util.Calendar classes, which can revert to previous states. It's also common in text editors and graphic editors for undo mechanisms. + +## Benefits and Trade-offs of Memento Pattern + +Benefits: + +* Preserves encapsulation boundaries. +* Simplifies the originator by removing the need to manage version history or undo functionality directly. + +Trade-offs: -* A snapshot of an object's state must be saved so that it can be restored to that state later, and -* A direct interface to obtaining the state would expose implementation details and break the -object's encapsulation +* Can be expensive in terms of memory if a large number of states are saved. +* Care must be taken to manage the lifecycle of mementos to avoid memory leaks. -## Known uses +## Related Java Design Patterns -* [java.util.Date](http://docs.oracle.com/javase/8/docs/api/java/util/Date.html) +* [Command](https://java-design-patterns.com/patterns/command/): Often used together; commands store state for undoing operations in mementos. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Mementos may use prototyping to store the state. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) diff --git a/memento/pom.xml b/memento/pom.xml index 6f4314d54adf..6ec5ca5d010c 100644 --- a/memento/pom.xml +++ b/memento/pom.xml @@ -34,6 +34,14 @@ memento + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/memento/src/main/java/com/iluwatar/memento/App.java b/memento/src/main/java/com/iluwatar/memento/App.java index f7f686567981..d47bc20db84a 100644 --- a/memento/src/main/java/com/iluwatar/memento/App.java +++ b/memento/src/main/java/com/iluwatar/memento/App.java @@ -47,9 +47,7 @@ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var states = new Stack(); @@ -67,7 +65,7 @@ public static void main(String[] args) { states.add(star.getMemento()); star.timePasses(); LOGGER.info(star.toString()); - while (states.size() > 0) { + while (!states.isEmpty()) { star.setMemento(states.pop()); LOGGER.info(star.toString()); } diff --git a/memento/src/main/java/com/iluwatar/memento/Star.java b/memento/src/main/java/com/iluwatar/memento/Star.java index a1d1fca251ad..8f806b1e1fe1 100644 --- a/memento/src/main/java/com/iluwatar/memento/Star.java +++ b/memento/src/main/java/com/iluwatar/memento/Star.java @@ -24,27 +24,24 @@ */ package com.iluwatar.memento; -/** - * Star uses "mementos" to store and restore state. - */ +import lombok.Getter; +import lombok.Setter; + +/** Star uses "mementos" to store and restore state. */ public class Star { private StarType type; private int ageYears; private int massTons; - /** - * Constructor. - */ + /** Constructor. */ public Star(StarType startType, int startAge, int startMass) { this.type = startType; this.ageYears = startAge; this.massTons = startMass; } - /** - * Makes time pass for the star. - */ + /** Makes time pass for the star. */ public void timePasses() { ageYears *= 2; massTons *= 8; @@ -57,8 +54,7 @@ public void timePasses() { ageYears *= 2; massTons = 0; } - default -> { - } + default -> {} } } @@ -82,37 +78,13 @@ public String toString() { return String.format("%s age: %d years mass: %d tons", type.toString(), ageYears, massTons); } - /** - * StarMemento implementation. - */ + /** StarMemento implementation. */ + @Getter + @Setter private static class StarMementoInternal implements StarMemento { private StarType type; private int ageYears; private int massTons; - - public StarType getType() { - return type; - } - - public void setType(StarType type) { - this.type = type; - } - - public int getAgeYears() { - return ageYears; - } - - public void setAgeYears(int ageYears) { - this.ageYears = ageYears; - } - - public int getMassTons() { - return massTons; - } - - public void setMassTons(int massTons) { - this.massTons = massTons; - } } } diff --git a/memento/src/main/java/com/iluwatar/memento/StarMemento.java b/memento/src/main/java/com/iluwatar/memento/StarMemento.java index 3a355a1ea2c6..b0214ad1acc2 100644 --- a/memento/src/main/java/com/iluwatar/memento/StarMemento.java +++ b/memento/src/main/java/com/iluwatar/memento/StarMemento.java @@ -1,32 +1,28 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.memento; - -/** - * External interface to memento. - */ -public interface StarMemento { - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.memento; + +/** External interface to memento. */ +public interface StarMemento {} diff --git a/memento/src/main/java/com/iluwatar/memento/StarType.java b/memento/src/main/java/com/iluwatar/memento/StarType.java index 69bc9b9becaf..41e1659ef0e5 100644 --- a/memento/src/main/java/com/iluwatar/memento/StarType.java +++ b/memento/src/main/java/com/iluwatar/memento/StarType.java @@ -24,9 +24,7 @@ */ package com.iluwatar.memento; -/** - * StarType enumeration. - */ +/** StarType enumeration. */ public enum StarType { SUN("sun"), RED_GIANT("red giant"), diff --git a/memento/src/test/java/com/iluwatar/memento/AppTest.java b/memento/src/test/java/com/iluwatar/memento/AppTest.java index b175f57ef27c..ad7ea0019c68 100644 --- a/memento/src/test/java/com/iluwatar/memento/AppTest.java +++ b/memento/src/test/java/com/iluwatar/memento/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.memento; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/memento/src/test/java/com/iluwatar/memento/StarTest.java b/memento/src/test/java/com/iluwatar/memento/StarTest.java index 85bebd5290c4..4411efaab3fa 100644 --- a/memento/src/test/java/com/iluwatar/memento/StarTest.java +++ b/memento/src/test/java/com/iluwatar/memento/StarTest.java @@ -28,16 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/20/15 - 10:08 AM - * - * @author Jeroen Meulemeester - */ +/** StarTest */ class StarTest { - /** - * Verify the stages of a dying sun, without going back in time - */ + /** Verify the stages of a dying sun, without going back in time */ @Test void testTimePasses() { final var star = new Star(StarType.SUN, 1, 2); @@ -62,9 +56,7 @@ void testTimePasses() { assertEquals("dead star age: 256 years mass: 0 tons", star.toString()); } - /** - * Verify some stage of a dying sun, but go back in time to test the memento - */ + /** Verify some stage of a dying sun, but go back in time to test the memento */ @Test void testSetMemento() { final var star = new Star(StarType.SUN, 1, 2); @@ -93,7 +85,5 @@ void testSetMemento() { star.setMemento(firstMemento); assertEquals("sun age: 1 years mass: 2 tons", star.toString()); - } - } diff --git a/metadata-mapping/README.md b/metadata-mapping/README.md index e51fc46c7bd3..bd2342709a32 100644 --- a/metadata-mapping/README.md +++ b/metadata-mapping/README.md @@ -1,20 +1,25 @@ --- -title: Metadata Mapping -category: Architectural +title: "Metadata Mapping Pattern in Java: Bridging Objects and Data Stores Seamlessly" +shortTitle: Metadata Mapping +description: "Explore the Metadata Mapping Design Pattern for managing the mapping between database records and objects in Java applications. Learn implementation with Hibernate, use cases, benefits, and best practices." +category: Data access language: en tag: - - Data access + - Decoupling + - Enterprise patterns + - Object mapping + - Persistence --- -## Intent +## Intent of Metadata Mapping Design Pattern -Holds details of object-relational mapping in the metadata. +Metadata Mapping Design Pattern is designed to manage the mapping between database records and Java objects in a way that keeps the database schema and object model decoupled and manageable. -## Explanation +## Detailed Explanation of Metadata Mapping Pattern with Real-World Examples -Real world example +Real-world example -> Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code. +> An analogous real-world example of the Metadata Mapping design pattern can be seen in online retail systems. In such systems, products often have varying attributes depending on their category. For instance, electronics might have attributes like battery life and screen size, while clothing might have attributes like size and fabric type. Using Metadata Mapping, the system can dynamically map these varying attributes to the product objects without modifying the underlying class structure. This flexibility allows for easy updates and management of product attributes as new categories and attributes are introduced, ensuring that the system can evolve with the changing product landscape. In plain words @@ -24,7 +29,9 @@ Wikipedia says > Create a "virtual [object database](https://en.wikipedia.org/wiki/Object_database)" that can be used from within the programming language. -**Programmatic Example** +## Programmatic Example of Metadata Mapping Pattern in Java + +Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code. We give an example about visiting the information of `user_account` table in `h2` database. Firstly, we create `user_account` table with `h2`: @@ -40,9 +47,6 @@ public class DatabaseUtil { + " PRIMARY KEY (`id`)\n" + ");"; - /** - * Create database. - */ static { LOGGER.info("create h2 database"); var source = new JdbcDataSource(); @@ -67,11 +71,6 @@ public class User { private String username; private String password; - /** - * Get a user. - * @param username user name - * @param password user password - */ public User(String username, String password) { this.username = username; this.password = password; @@ -130,10 +129,6 @@ Then we can get access to the table just like an object with `Hibernate`, here's public class UserService { private static final SessionFactory factory = HibernateUtil.getSessionFactory(); - /** - * List all users. - * @return list of users - */ public List listUser() { LOGGER.info("list all users."); List users = new ArrayList<>(); @@ -151,7 +146,7 @@ public class UserService { } // other CRUDs -> - ... + // ... public void close() { HibernateUtil.shutdown(); @@ -159,21 +154,110 @@ public class UserService { } ``` -## Class diagram +Here is our `App` class with `main` function for running the example. + +```java +@Slf4j +public class App { + + public static void main(String[] args) { + // get service + var userService = new UserService(); + // use create service to add users + for (var user : generateSampleUsers()) { + var id = userService.createUser(user); + LOGGER.info("Add user" + user + "at" + id + "."); + } + // use list service to get users + var users = userService.listUser(); + LOGGER.info(String.valueOf(users)); + // use get service to get a user + var user = userService.getUser(1); + LOGGER.info(String.valueOf(user)); + // change password of user 1 + user.setPassword("new123"); + // use update service to update user 1 + userService.updateUser(1, user); + // use delete service to delete user 2 + userService.deleteUser(2); + // close service + userService.close(); + } + + public static List generateSampleUsers() { + final var user1 = new User("ZhangSan", "zhs123"); + final var user2 = new User("LiSi", "ls123"); + final var user3 = new User("WangWu", "ww123"); + return List.of(user1, user2, user3); + } +} +``` + +Console output: + +``` +14:44:17.792 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.12.Final +14:44:17.977 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final} +14:44:18.216 [main] WARN o.hibernate.orm.connections.pooling - HHH10001002: Using Hibernate built-in connection pool (not for production use!) +14:44:18.217 [main] INFO o.hibernate.orm.connections.pooling - HHH10001005: using driver [org.h2.Driver] at URL [jdbc:h2:mem:metamapping] +14:44:18.217 [main] INFO o.hibernate.orm.connections.pooling - HHH10001001: Connection properties: {} +14:44:18.217 [main] INFO o.hibernate.orm.connections.pooling - HHH10001003: Autocommit mode: false +14:44:18.219 [main] INFO o.h.e.j.c.i.DriverManagerConnectionProviderImpl - HHH000115: Hibernate connection pool size: 1 (min=1) +14:44:18.276 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.H2Dialect +14:44:18.463 [main] INFO o.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@73a8e994] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode. +14:44:18.465 [main] INFO o.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@7affc159] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode. +14:44:18.470 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] +14:44:18.473 [main] INFO c.i.metamapping.service.UserService - create user: ZhangSan +14:44:18.508 [main] INFO c.i.metamapping.service.UserService - create user ZhangSan at 1 +14:44:18.508 [main] INFO com.iluwatar.metamapping.App - Add userUser(id=1, username=ZhangSan, password=zhs123)at1. +14:44:18.508 [main] INFO c.i.metamapping.service.UserService - create user: LiSi +14:44:18.509 [main] INFO c.i.metamapping.service.UserService - create user LiSi at 2 +14:44:18.509 [main] INFO com.iluwatar.metamapping.App - Add userUser(id=2, username=LiSi, password=ls123)at2. +14:44:18.509 [main] INFO c.i.metamapping.service.UserService - create user: WangWu +14:44:18.512 [main] INFO c.i.metamapping.service.UserService - create user WangWu at 3 +14:44:18.512 [main] INFO com.iluwatar.metamapping.App - Add userUser(id=3, username=WangWu, password=ww123)at3. +14:44:18.512 [main] INFO c.i.metamapping.service.UserService - list all users. +14:44:18.542 [main] INFO com.iluwatar.metamapping.App - [User(id=1, username=ZhangSan, password=zhs123), User(id=2, username=LiSi, password=ls123), User(id=3, username=WangWu, password=ww123)] +14:44:18.542 [main] INFO c.i.metamapping.service.UserService - get user at: 1 +14:44:18.545 [main] INFO com.iluwatar.metamapping.App - User(id=1, username=ZhangSan, password=zhs123) +14:44:18.545 [main] INFO c.i.metamapping.service.UserService - update user at 1 +14:44:18.548 [main] INFO c.i.metamapping.service.UserService - delete user at: 2 +14:44:18.550 [main] INFO o.h.t.s.i.SchemaDropperImpl$DelayedDropActionImpl - HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down' +14:44:18.550 [main] INFO o.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@7b5cc918] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode. +14:44:18.551 [main] INFO o.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:mem:metamapping] +``` + +## When to Use the Metadata Mapping Pattern in Java + +Use the Metadata Mapping Design Pattern when you need to bridge the gap between an object-oriented domain model and a relational database in Java applications, without hard-coding database queries into the domain logic. + +## Real-World Applications of Metadata Mapping Pattern in Java + +* Object-Relational Mapping (ORM) frameworks like Hibernate, JPA, EclipseLink, and MyBatis frequently utilize the Metadata Mapping Design Pattern to map Java objects to database tables. +* Mapping database rows to domain objects in enterprise applications. -![metamapping](./etc/metamapping.png) +## Benefits and Trade-offs of Metadata Mapping Pattern -## Applicability +Benefits: -Use the Metadata Mapping when: +* Decouples object model and database schema, allowing independent evolution. +* Reduces boilerplate code associated with data access. +* Centralizes mapping logic, making changes more manageable. -- you want reduce the amount of work needed to handle database mapping. +Trade-offs: -## Known uses +* Adds complexity due to an additional layer of abstraction. +* Can impact performance if not properly optimized. -[Hibernate](https://hibernate.org/), [EclipseLink](https://www.eclipse.org/eclipselink/), [MyBatis](https://blog.mybatis.org/)...... +## Related Java Design Patterns -## Credits +* [Data Mapper](https://java-design-patterns.com/patterns/data-mapper/): Metadata Mapping is often used within the broader Data Mapper pattern to facilitate the mapping process. +* Active Record: Differently from Active Record, Metadata Mapping separates the data access logic from the domain entities. +* [Repository](https://java-design-patterns.com/patterns/repository/): Works well with the Repository pattern by abstracting data access further, allowing more complex domain logic to be cleanly separated from data mapping. -- [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +## References and Credits +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Pro JPA 2: Mastering the Java Persistence API](https://amzn.to/4b7UoMC) diff --git a/metadata-mapping/pom.xml b/metadata-mapping/pom.xml index a501808061f7..7aa802d7ff2a 100644 --- a/metadata-mapping/pom.xml +++ b/metadata-mapping/pom.xml @@ -37,6 +37,14 @@ metadata-mapping + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -45,14 +53,17 @@ org.hibernate hibernate-core + 6.6.11.Final javax.xml.bind jaxb-api + 2.4.0-b180830.0359 org.glassfish.jaxb jaxb-runtime + 4.0.5 com.h2database diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java index 0bc79f96f125..6b5624ac54f9 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java @@ -32,20 +32,17 @@ import org.hibernate.service.ServiceRegistry; /** - * Metadata Mapping specifies the mapping - * between classes and tables so that - * we could treat a table of any database like a Java class. + * Metadata Mapping specifies the mapping between classes and tables so that we could treat a table + * of any database like a Java class. + * + *

With hibernate, we achieve list/create/update/delete/get operations: 1)Create the H2 Database + * in {@link DatabaseUtil}. 2)Hibernate resolve hibernate.cfg.xml and generate service like + * save/list/get/delete. For learning metadata mapping pattern, we go deeper into Hibernate here: + * a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml b)create session factory to + * generate session interacting with database c)generate session with factory pattern d)create query + * object or use basic api with session, hibernate will convert all query to database query + * according to metadata 3)We encapsulate hibernate service in {@link UserService} for our use. * - *

With hibernate, we achieve list/create/update/delete/get operations: - * 1)Create the H2 Database in {@link DatabaseUtil}. - * 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete. - * For learning metadata mapping pattern, we go deeper into Hibernate here: - * a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml - * b)create session factory to generate session interacting with database - * c)generate session with factory pattern - * d)create query object or use basic api with session, - * hibernate will convert all query to database query according to metadata - * 3)We encapsulate hibernate service in {@link UserService} for our use. * @see org.hibernate.cfg.Configuration#configure(String) * @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry) * @see org.hibernate.internal.SessionFactoryImpl#openSession() @@ -56,9 +53,8 @@ public class App { * Program entry point. * * @param args command line args. - * @throws Exception if any error occurs. */ - public static void main(String[] args) throws Exception { + public static void main(String[] args) { // get service var userService = new UserService(); // use create service to add users @@ -93,4 +89,4 @@ public static List generateSampleUsers() { final var user3 = new User("WangWu", "ww123"); return List.of(user1, user2, user3); } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java index 73a7009f6303..7d2275234ec1 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java @@ -28,9 +28,7 @@ import lombok.Setter; import lombok.ToString; -/** - * User Entity. - */ +/** User Entity. */ @Setter @Getter @ToString @@ -43,6 +41,7 @@ public User() {} /** * Get a user. + * * @param username user name * @param password user password */ @@ -50,4 +49,4 @@ public User(String username, String password) { this.username = username; this.password = password; } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java index b44c461c662b..e1943a6a944b 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java @@ -32,15 +32,14 @@ import org.hibernate.HibernateException; import org.hibernate.SessionFactory; -/** - * Service layer for user. - */ +/** Service layer for user. */ @Slf4j public class UserService { private static final SessionFactory factory = HibernateUtil.getSessionFactory(); /** * List all users. + * * @return list of users */ public List listUser() { @@ -49,8 +48,8 @@ public List listUser() { try (var session = factory.openSession()) { var tx = session.beginTransaction(); List userIter = session.createQuery("FROM User").list(); - for (var iterator = userIter.iterator(); iterator.hasNext();) { - users.add(iterator.next()); + for (User user : userIter) { + users.add(user); } tx.commit(); } catch (HibernateException e) { @@ -61,6 +60,7 @@ public List listUser() { /** * Add a user. + * * @param user user entity * @return user id */ @@ -80,6 +80,7 @@ public int createUser(User user) { /** * Update user. + * * @param id user id * @param user new user entity */ @@ -97,6 +98,7 @@ public void updateUser(Integer id, User user) { /** * Delete user. + * * @param id user id */ public void deleteUser(Integer id) { @@ -113,6 +115,7 @@ public void deleteUser(Integer id) { /** * Get user. + * * @param id user id * @return deleted user */ @@ -129,10 +132,8 @@ public User getUser(Integer id) { return user; } - /** - * Close hibernate. - */ + /** Close hibernate. */ public void close() { HibernateUtil.shutdown(); } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java index 5f5dddcbc833..9c3c8469df55 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java @@ -28,13 +28,12 @@ import lombok.extern.slf4j.Slf4j; import org.h2.jdbcx.JdbcDataSource; -/** - * Create h2 database. - */ +/** Create h2 database. */ @Slf4j public class DatabaseUtil { private static final String DB_URL = "jdbc:h2:mem:metamapping"; - private static final String CREATE_SCHEMA_SQL = """ + private static final String CREATE_SCHEMA_SQL = + """ DROP TABLE IF EXISTS `user_account`;CREATE TABLE `user_account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, @@ -42,9 +41,7 @@ public class DatabaseUtil { PRIMARY KEY (`id`) );"""; - /** - * Hide constructor. - */ + /** Hide constructor. */ private DatabaseUtil() {} static { @@ -57,4 +54,4 @@ private DatabaseUtil() {} LOGGER.error("unable to create h2 data source", e); } } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java index 40159d4a2789..54c0b3c36640 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java @@ -24,25 +24,23 @@ */ package com.iluwatar.metamapping.utils; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -/** - * Manage hibernate. - */ +/** Manage hibernate. */ @Slf4j public class HibernateUtil { - private static final SessionFactory sessionFactory = buildSessionFactory(); + @Getter private static final SessionFactory sessionFactory = buildSessionFactory(); - /** - * Hide constructor. - */ + /** Hide constructor. */ private HibernateUtil() {} /** * Build session factory. + * * @return session factory */ private static SessionFactory buildSessionFactory() { @@ -50,20 +48,9 @@ private static SessionFactory buildSessionFactory() { return new Configuration().configure().buildSessionFactory(); } - /** - * Get session factory. - * @return session factory - */ - public static SessionFactory getSessionFactory() { - return sessionFactory; - } - - /** - * Close session factory. - */ + /** Close session factory. */ public static void shutdown() { // Close caches and connection pools getSessionFactory().close(); } - -} \ No newline at end of file +} diff --git a/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java index 6697296f7b3d..2b159b52617a 100644 --- a/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java +++ b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java @@ -24,21 +24,18 @@ */ package com.iluwatar.metamapping; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that metadata mapping example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that metadata mapping example runs without errors. */ class AppTest { /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. */ @Test void shouldExecuteMetaMappingWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/microservices-aggregrator/README.md b/microservices-aggregrator/README.md new file mode 100644 index 000000000000..e77d997b9aeb --- /dev/null +++ b/microservices-aggregrator/README.md @@ -0,0 +1,144 @@ +--- +title: "Microservices Aggregator Pattern in Java: Building Efficient Composite Services in Java" +shortTitle: Microservices Aggregator +description: "Learn about the Microservices Aggregator Design Pattern with Java examples. Understand its intent, real-world applications, benefits, and trade-offs for scalable system design." +category: Architectural +language: en +tag: + - API design + - Client-server + - Data processing + - Decoupling + - Integration + - Microservices + - Scalability +--- + +## Also known as + +* API Composition + +## Intent of Microservices Aggregator Design Pattern + +The Microservices Aggregator pattern helps aggregate responses from multiple microservices into a single unified response, optimizing client-server interactions in scalable systems. + +## Detailed Explanation of Microservices Aggregator Pattern with Real-World Examples + +Real-world example + +> In a travel booking platform, an Aggregator Microservice consolidates data from flights, hotels, and car rentals microservices, providing a seamless user experience and enhancing scalability. Instead of the user making separate requests to each service, the platform employs an Aggregator Microservice. This microservice calls each of these services, collects their responses, and then consolidates the information into a single, unified response that is sent back to the user. This simplifies the user experience by providing all necessary travel details in one place and reduces the number of direct interactions the user needs to have with the underlying services. + +In plain words + +> Microservices Aggregator collects pieces of data from various microservices and returns an aggregate for processing. + +Stack Overflow says + +> Microservices Aggregator invokes multiple services to achieve the functionality required by the application. + +Architecture diagram + +![Microservices Aggregator Architecture Diagram](./etc/microservices-aggregator-architecture-diagram.png) + +## Programmatic Example of Microservices Aggregator Pattern in Java + +Our web marketplace utilizes an Aggregator microservice to fetch combined product and inventory information from separate microservices, ensuring efficient data processing and improved system performance. + +Let's start from the data model. Here's our `Product`. + +```java +public class Product { + private String title; + private int productInventories; + // Other properties and methods... +} +``` + +Next we can introduce our `Aggregator` microservice. It contains clients `ProductInformationClient` and `ProductInventoryClient` for calling respective microservices. + +```java + +@RestController +public class Aggregator { + + @Resource + private ProductInformationClient informationClient; + + @Resource + private ProductInventoryClient inventoryClient; + + @RequestMapping(path = "/product", method = RequestMethod.GET) + public Product getProduct() { + + 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 default error inventory + product.setProductInventories(requireNonNullElse(productInventory, -1)); + + return product; + } +} +``` + +Here's the essence of information microservice implementation. Inventory microservice is similar, it just returns inventory counts. + +```java + +@RestController +public class InformationController { + @RequestMapping(value = "/information", method = RequestMethod.GET) + public String getProductTitle() { + return "The Product Title."; + } +} +``` + +Now calling our `Aggregator` REST API returns the product information. + +```bash +# Example bash call +curl http://localhost:50004/product + +# Example output +{"title":"The Product Title.","productInventories":5} +``` + +## When to Use the Microservices Aggregator Pattern in Java + +The Microservices Aggregator pattern is ideal for scenarios requiring composite responses from multiple microservices, such as in e-commerce and dashboard applications where aggregated data enhances user experience and system efficiency. + +## Benefits and Trade-offs of Microservices Aggregator Pattern + +Benefits: + +* Simplified Client: Clients interact with just one service rather than managing calls to multiple microservices, which simplifies client-side logic. +* Reduced Latency: By aggregating responses, the number of network calls is reduced, which can improve the application's overall latency. +* Decoupling: Clients are decoupled from the individual microservices, allowing for more flexibility in changing the microservices landscape without impacting clients. +* Centralized Logic: Aggregation allows for centralized transformation and logic application on the data collected from various services, which can be more efficient than handling it in the client or spreading it across multiple services. + +Trade-offs: + +* Single Point of Failure: The aggregator service can become a bottleneck or a single point of failure if not designed with high availability and scalability in mind. +* Complexity: Implementing an aggregator can introduce complexity, especially in terms of data aggregation logic and error handling when dealing with multiple services. + +## Related Java Design Patterns + +* [API Gateway](https://java-design-patterns.com/patterns/microservices-api-gateway/): The Microservices Aggregator pattern is often used in conjunction with an API Gateway, which provides a single entry point for clients to access multiple microservices. +* [Composite](https://java-design-patterns.com/patterns/composite/): The Microservices Aggregator pattern can be seen as a form of the Composite pattern, where the composite is the aggregated response from multiple microservices. +* [Facade](https://java-design-patterns.com/patterns/facade/): The Microservices Aggregator pattern can be seen as a form of the Facade pattern, where the facade is the aggregator service that provides a simplified interface to the client. + +## References and Credits + +* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/43aGpSR) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3y6yv1z) +* [Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services](https://amzn.to/3T9g9Uj) +* [Microservice Architecture: Aligning Principles, Practices, and Culture](https://amzn.to/3T9jZNi) +* [Microservices Patterns: With examples in Java](https://amzn.to/4a5LHkP) +* [Pattern: API Composition](https://microservices.io/patterns/data/api-composition.html) +* [Production-Ready Microservices: Building Standardized Systems Across an Engineering Organization](https://amzn.to/4a0Vk4c) +* [Microservice Design Patterns (Arun Gupta)](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/) diff --git a/microservices-aggregrator/aggregator-service/etc/aggregator-service.png b/microservices-aggregrator/aggregator-service/etc/aggregator-service.png new file mode 100644 index 000000000000..75ee82328bbe Binary files /dev/null and b/microservices-aggregrator/aggregator-service/etc/aggregator-service.png differ diff --git a/aggregator-microservices/aggregator-service/etc/aggregator-service.urm.puml b/microservices-aggregrator/aggregator-service/etc/aggregator-service.urm.puml similarity index 100% rename from aggregator-microservices/aggregator-service/etc/aggregator-service.urm.puml rename to microservices-aggregrator/aggregator-service/etc/aggregator-service.urm.puml diff --git a/aggregator-microservices/aggregator-service/pom.xml b/microservices-aggregrator/aggregator-service/pom.xml similarity index 97% rename from aggregator-microservices/aggregator-service/pom.xml rename to microservices-aggregrator/aggregator-service/pom.xml index 89584e738f95..fe83826910ce 100644 --- a/aggregator-microservices/aggregator-service/pom.xml +++ b/microservices-aggregrator/aggregator-service/pom.xml @@ -27,7 +27,7 @@ --> - aggregator-microservices + microservices-aggregrator com.iluwatar 1.26.0-SNAPSHOT @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java similarity index 90% rename from aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java rename to microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java index 6931b9a3b299..0fd19e7efbd8 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java @@ -26,7 +26,7 @@ import static java.util.Objects.requireNonNullElse; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -37,11 +37,9 @@ @RestController public class Aggregator { - @Resource - private ProductInformationClient informationClient; + @Resource private ProductInformationClient informationClient; - @Resource - private ProductInventoryClient inventoryClient; + @Resource private ProductInventoryClient inventoryClient; /** * Retrieves product data. @@ -55,13 +53,12 @@ public Product getProduct() { var productTitle = informationClient.getProductTitle(); var productInventory = inventoryClient.getProductInventories(); - //Fallback to error message + // Fallback to error message product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed")); - //Fallback to default error inventory + // Fallback to default error inventory product.setProductInventories(requireNonNullElse(productInventory, -1)); return product; } - } diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java similarity index 97% rename from aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java rename to microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java index 95f7c587d92a..26424eb9c98e 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java @@ -27,9 +27,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -/** - * Spring Boot EntryPoint Class. - */ +/** Spring Boot EntryPoint Class. */ @SpringBootApplication public class App { diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java similarity index 89% rename from aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java rename to microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java index 56dc0155fa86..66626d814871 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java @@ -27,22 +27,14 @@ import lombok.Getter; import lombok.Setter; -/** - * Encapsulates all the data for a Product that clients will request. - */ +/** Encapsulates all the data for a Product that clients will request. */ @Getter @Setter public class Product { - /** - * The title of the product. - */ + /** The title of the product. */ private String title; - - /** - * The inventories of the product. - */ + /** The inventories of the product. */ private int productInventories; - } diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java similarity index 96% rename from aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java rename to microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java index 1260e4e306ff..d183656edc09 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java @@ -24,11 +24,8 @@ */ package com.iluwatar.aggregator.microservices; -/** - * Interface for the Information micro-service. - */ +/** Interface for the Information micro-service. */ public interface ProductInformationClient { String getProductTitle(); - } diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java similarity index 90% rename from aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java rename to microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java index a41627cb342d..2bda000b397f 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java @@ -32,19 +32,18 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -/** - * An adapter to communicate with information micro-service. - */ +/** An adapter to communicate with information micro-service. */ @Slf4j @Component public class ProductInformationClientImpl implements ProductInformationClient { @Override public String getProductTitle() { - var request = HttpRequest.newBuilder() - .GET() - .uri(URI.create("http://localhost:51515/information")) - .build(); + var request = + HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:51515/information")) + .build(); var client = HttpClient.newHttpClient(); try { var httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java similarity index 96% rename from aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java rename to microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java index 4e0ffe2edb84..02ebac8732d6 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.aggregator.microservices; -/** - * Interface to Inventory micro-service. - */ +/** Interface to Inventory micro-service. */ public interface ProductInventoryClient { Integer getProductInventories(); diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java similarity index 91% rename from aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java rename to microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java index 7549693d07c4..d5a918e4629d 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java @@ -32,9 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -/** - * An adapter to communicate with inventory micro-service. - */ +/** An adapter to communicate with inventory micro-service. */ @Slf4j @Component public class ProductInventoryClientImpl implements ProductInventoryClient { @@ -43,10 +41,11 @@ public class ProductInventoryClientImpl implements ProductInventoryClient { public Integer getProductInventories() { var response = ""; - var request = HttpRequest.newBuilder() - .GET() - .uri(URI.create("http://localhost:51516/inventories")) - .build(); + var request = + HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:51516/inventories")) + .build(); var client = HttpClient.newHttpClient(); try { var httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); diff --git a/aggregator-microservices/aggregator-service/src/main/resources/application.properties b/microservices-aggregrator/aggregator-service/src/main/resources/application.properties similarity index 100% rename from aggregator-microservices/aggregator-service/src/main/resources/application.properties rename to microservices-aggregrator/aggregator-service/src/main/resources/application.properties diff --git a/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java b/microservices-aggregrator/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java similarity index 88% rename from aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java rename to microservices-aggregrator/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java index 2ac246d9fbdb..914e15dad28d 100644 --- a/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java +++ b/microservices-aggregrator/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java @@ -24,37 +24,30 @@ */ package com.iluwatar.aggregator.microservices; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -/** - * Test Aggregation of domain objects - */ +/** Test Aggregation of domain objects */ class AggregatorTest { - @InjectMocks - private Aggregator aggregator; + @InjectMocks private Aggregator aggregator; - @Mock - private ProductInformationClient informationClient; + @Mock private ProductInformationClient informationClient; - @Mock - private ProductInventoryClient inventoryClient; + @Mock private ProductInventoryClient inventoryClient; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); } - /** - * Tests getting the data for a desktop client - */ + /** Tests getting the data for a desktop client */ @Test void testGetProduct() { var title = "The Product Title."; @@ -68,5 +61,4 @@ void testGetProduct() { assertEquals(title, testProduct.getTitle()); assertEquals(inventories, testProduct.getProductInventories()); } - } diff --git a/microservices-aggregrator/etc/aggregator-microservices.urm.puml b/microservices-aggregrator/etc/aggregator-microservices.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-aggregrator/etc/aggregator-microservices.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/microservices-aggregrator/etc/microservices-aggregator-architecture-diagram.png b/microservices-aggregrator/etc/microservices-aggregator-architecture-diagram.png new file mode 100644 index 000000000000..34d5113e5a3a Binary files /dev/null and b/microservices-aggregrator/etc/microservices-aggregator-architecture-diagram.png differ diff --git a/microservices-aggregrator/etc/microservices-aggregrator.urm.puml b/microservices-aggregrator/etc/microservices-aggregrator.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-aggregrator/etc/microservices-aggregrator.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/aggregator-microservices/information-microservice/etc/information-microservice.urm.puml b/microservices-aggregrator/information-microservice/etc/information-microservice.urm.puml similarity index 100% rename from aggregator-microservices/information-microservice/etc/information-microservice.urm.puml rename to microservices-aggregrator/information-microservice/etc/information-microservice.urm.puml diff --git a/aggregator-microservices/information-microservice/pom.xml b/microservices-aggregrator/information-microservice/pom.xml similarity index 96% rename from aggregator-microservices/information-microservice/pom.xml rename to microservices-aggregrator/information-microservice/pom.xml index fd4c364a3f21..5b27df62516d 100644 --- a/aggregator-microservices/information-microservice/pom.xml +++ b/microservices-aggregrator/information-microservice/pom.xml @@ -27,7 +27,7 @@ --> - aggregator-microservices + microservices-aggregrator com.iluwatar 1.26.0-SNAPSHOT @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java similarity index 93% rename from aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java rename to microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java index 6c4905340211..68ff8856eb9b 100644 --- a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java +++ b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java @@ -27,9 +27,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -/** - * Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. - */ +/** Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. */ @SpringBootApplication public class InformationApplication { diff --git a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java similarity index 95% rename from aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java rename to microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java index 88d11e5f4703..cc826e497195 100644 --- a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java +++ b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java @@ -27,9 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -/** - * Controller providing endpoints to retrieve information about products. - */ +/** Controller providing endpoints to retrieve information about products. */ @RestController public class InformationController { diff --git a/aggregator-microservices/information-microservice/src/main/resources/application.properties b/microservices-aggregrator/information-microservice/src/main/resources/application.properties similarity index 100% rename from aggregator-microservices/information-microservice/src/main/resources/application.properties rename to microservices-aggregrator/information-microservice/src/main/resources/application.properties diff --git a/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java b/microservices-aggregrator/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java similarity index 97% rename from aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java rename to microservices-aggregrator/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java index d85e1ce0c94a..3f344d9d8d39 100644 --- a/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java +++ b/microservices-aggregrator/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.information.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Information Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test for Information Rest Controller */ class InformationControllerTest { @Test @@ -39,5 +37,4 @@ void shouldGetProductTitle() { var title = infoController.getProductTitle(); assertEquals("The Product Title.", title); } - } diff --git a/aggregator-microservices/inventory-microservice/etc/inventory-microservice.urm.puml b/microservices-aggregrator/inventory-microservice/etc/inventory-microservice.urm.puml similarity index 100% rename from aggregator-microservices/inventory-microservice/etc/inventory-microservice.urm.puml rename to microservices-aggregrator/inventory-microservice/etc/inventory-microservice.urm.puml diff --git a/aggregator-microservices/inventory-microservice/pom.xml b/microservices-aggregrator/inventory-microservice/pom.xml similarity index 96% rename from aggregator-microservices/inventory-microservice/pom.xml rename to microservices-aggregrator/inventory-microservice/pom.xml index 7f52bcc4a655..64203b09edcf 100644 --- a/aggregator-microservices/inventory-microservice/pom.xml +++ b/microservices-aggregrator/inventory-microservice/pom.xml @@ -27,7 +27,7 @@ --> - aggregator-microservices + microservices-aggregrator com.iluwatar 1.26.0-SNAPSHOT @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java similarity index 93% rename from aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java rename to microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java index 7d1762655de1..5627c37c9161 100644 --- a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java +++ b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java @@ -27,14 +27,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -/** - * Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. - */ +/** Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. */ @SpringBootApplication public class InventoryApplication { public static void main(String[] args) { SpringApplication.run(InventoryApplication.class, args); } - } diff --git a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java similarity index 95% rename from aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java rename to microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java index 374da55c66fc..bf5abd510bc0 100644 --- a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java +++ b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java @@ -27,9 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -/** - * Controller providing endpoints to retrieve product inventories. - */ +/** Controller providing endpoints to retrieve product inventories. */ @RestController public class InventoryController { @@ -42,5 +40,4 @@ public class InventoryController { public int getProductInventories() { return 5; } - } diff --git a/aggregator-microservices/inventory-microservice/src/main/resources/application.properties b/microservices-aggregrator/inventory-microservice/src/main/resources/application.properties similarity index 100% rename from aggregator-microservices/inventory-microservice/src/main/resources/application.properties rename to microservices-aggregrator/inventory-microservice/src/main/resources/application.properties diff --git a/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java b/microservices-aggregrator/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java similarity index 97% rename from aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java rename to microservices-aggregrator/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java index 1bb692042dbf..27b41594d30b 100644 --- a/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java +++ b/microservices-aggregrator/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.inventory.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test Inventory Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test Inventory Rest Controller */ class InventoryControllerTest { @Test diff --git a/aggregator-microservices/pom.xml b/microservices-aggregrator/pom.xml similarity index 97% rename from aggregator-microservices/pom.xml rename to microservices-aggregrator/pom.xml index c5b4db33cada..bf1edb1c8b76 100644 --- a/aggregator-microservices/pom.xml +++ b/microservices-aggregrator/pom.xml @@ -32,7 +32,7 @@ 1.26.0-SNAPSHOT 4.0.0 - aggregator-microservices + microservices-aggregrator pom information-microservice diff --git a/microservices-api-gateway/README.md b/microservices-api-gateway/README.md new file mode 100644 index 000000000000..72b9b521a31d --- /dev/null +++ b/microservices-api-gateway/README.md @@ -0,0 +1,177 @@ +--- +title: "Microservices API Gateway Pattern in Java: Simplifying Service Access with a Unified Endpoint" +shortTitle: Microservice API Gateway +description: "Learn how the API Gateway pattern simplifies client-side development, enhances security, and optimizes communication in microservices architecture. Explore examples, benefits, and best practices." +category: Integration +language: en +tag: + - API design + - Cloud distributed + - Decoupling + - Enterprise patterns + - Integration + - Microservices + - Scalability + - Security +--- + +## Intent of Microservices API Gateway Design Pattern + +The API Gateway design pattern aims to provide a unified interface to a set of microservices within a microservices architecture. It acts as a single entry point for clients, routing requests to the appropriate microservices and aggregating results, thereby simplifying the client-side code. + +## Also known as + +* API Facade +* Backend for Frontends (BFF) + +## Detailed Explanation of Microservices API Gateway Pattern with Real-World Examples + +Real-world example + +> In a large e-commerce platform, an API Gateway is used as the single entry point for all client requests, simplifying client-side development. When a user visits the site or uses the mobile app, their requests for product information, user authentication, order processing, and payment are all routed through the API Gateway. The API Gateway handles tasks such as user authentication, rate limiting to prevent abuse, and logging for monitoring purposes, enhancing overall security optimization. This setup simplifies the client interface and ensures that all backend microservices can evolve independently without affecting the client directly, thereby enhancing microservices communication. This also enhances security by providing a centralized point to enforce policies and monitor traffic. + +In plain words + +> For a system implemented using microservices architecture, API Gateway is the single entry point that aggregates the calls to the individual microservices. + +Wikipedia says + +> API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to support authentication, authorization, security, audit and regulatory compliance. + +## Programmatic Example of Microservice API Gateway in Java + +This implementation shows what the API Gateway pattern could look like for an e-commerce site. The`ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which it uses to populate the `MobileProduct`. + +Here's the Image microservice implementation. + +```java +public interface ImageClient { + 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(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +Here's the Price microservice implementation. + +```java +public interface PriceClient { + 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(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +Here we can see how API Gateway maps the requests to the microservices. + +```java +public class ApiGateway { + + @Resource + private ImageClient imageClient; + + @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 = "/mobile", method = RequestMethod.GET) + public MobileProduct getProductMobile() { + var mobileProduct = new MobileProduct(); + mobileProduct.setPrice(priceClient.getPrice()); + return mobileProduct; + } +} +``` + +## When to Use the Microservices API Gateway Pattern in Java + +* When building a microservices architecture, and there's a need to abstract the complexity of microservices from the client. +* When multiple microservices need to be consumed in a single request. +* For authentication, authorization, and security enforcement at a single point. +* To optimize communication between clients and services, especially in a cloud environment. + +## Microservices API Gateway Pattern Java Tutorials + +* [Exploring the New Spring Cloud Gateway (Baeldung)](https://www.baeldung.com/spring-cloud-gateway) +* [Spring Cloud - Gateway(tutorialspoint)](https://www.tutorialspoint.com/spring_cloud/spring_cloud_gateway.htm) +* [Getting Started With Spring Cloud Gateway (DZone)](https://dzone.com/articles/getting-started-with-spring-cloud-gateway) + +## Benefits and Trade-offs of Microservices API Gateway Pattern + +Benefits: + +* Decouples client from microservices, allowing services to evolve independently. +* Simplifies client by aggregating requests to multiple services. +* Centralized location for cross-cutting concerns like security, logging, and rate limiting. +* Potential for performance optimizations like caching and request compression. + +Trade-offs: + +* Introduces a single point of failure, although this can be mitigated with high availability setups. +* Can become a bottleneck if not properly scaled. +* Adds complexity in terms of deployment and management. + +## Real-World Applications of Microservices API Gateway Pattern in Java + +* E-commerce platforms where multiple services (product info, pricing, inventory) are aggregated for a single view. +* Mobile applications that consume various backend services but require a simplified interface for ease of use. +* Cloud-native applications that leverage multiple microservices architectures. + +## Related Java Design Patterns + +* [Aggregator Microservice](https://java-design-patterns.com/patterns/microservices-aggregator/) - The API Gateway pattern is often used in conjunction with the Aggregator Microservice pattern to provide a unified interface to a set of microservices. +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) - API Gateways can use the Circuit Breaker pattern to prevent cascading failures when calling multiple microservices. +* [Proxy](https://java-design-patterns.com/patterns/proxy/) - The API Gateway pattern is a specialized form of the Proxy pattern, where the gateway acts as a single entry point for clients, routing requests to the appropriate microservices and aggregating results. + +## References and Credits + +* [Building Microservices](https://amzn.to/3UACtrU) +* [Cloud Native Patterns: Designing change-tolerant software](https://amzn.to/3uV12WN) +* [Designing Data-Intensive Applications](https://amzn.to/3PfRk7Y) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) +* [API Gateway (microservices.io)](http://microservices.io/patterns/apigateway.html) +* [Building Microservices: Using an API Gateway (nginx)](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) diff --git a/api-gateway/api-gateway-service/etc/api-gateway-service.urm.puml b/microservices-api-gateway/api-gateway-service/etc/api-gateway-service.urm.puml similarity index 100% rename from api-gateway/api-gateway-service/etc/api-gateway-service.urm.puml rename to microservices-api-gateway/api-gateway-service/etc/api-gateway-service.urm.puml diff --git a/api-gateway/api-gateway-service/pom.xml b/microservices-api-gateway/api-gateway-service/pom.xml similarity index 97% rename from api-gateway/api-gateway-service/pom.xml rename to microservices-api-gateway/api-gateway-service/pom.xml index b997c5d98935..b84bebe6f237 100644 --- a/api-gateway/api-gateway-service/pom.xml +++ b/microservices-api-gateway/api-gateway-service/pom.xml @@ -27,7 +27,7 @@ --> - api-gateway + microservices-api-gateway com.iluwatar 1.26.0-SNAPSHOT @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java similarity index 94% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java index 59fbe7ddd4fe..c4110509747c 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java @@ -24,7 +24,7 @@ */ package com.iluwatar.api.gateway; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -34,11 +34,9 @@ @RestController public class ApiGateway { - @Resource - private ImageClient imageClient; + @Resource private ImageClient imageClient; - @Resource - private PriceClient priceClient; + @Resource private PriceClient priceClient; /** * Retrieves product information that desktop clients need. diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java similarity index 92% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java index 9ccf8696afec..4754ba0a55a2 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java @@ -36,14 +36,14 @@ * sometime in the future) or if the location (host and port) of a microservice changes, then every * client that makes use of those microservices must be updated. * - *

The intent of the API Gateway pattern is to alleviate some of these issues. In the API - * Gateway pattern, an additional entity (the API Gateway) is placed between the client and the + *

The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway + * pattern, an additional entity (the API Gateway) is placed between the client and the * microservices. The job of the API Gateway is to aggregate the calls to the microservices. Rather * than the client calling each microservice individually, the client calls the API Gateway a single * time. The API Gateway then calls each of the microservices that the client needs. * - *

This implementation shows what the API Gateway pattern could look like for an e-commerce - * site. The {@link ApiGateway} makes calls to the Image and Price microservices using the {@link + *

This implementation shows what the API Gateway pattern could look like for an e-commerce site. + * The {@link ApiGateway} makes calls to the Image and Price microservices using the {@link * ImageClientImpl} and {@link PriceClientImpl} respectively. Customers viewing the site on a * desktop device can see both price information and an image of a product, so the {@link * ApiGateway} calls both of the microservices and aggregates the data in the {@link DesktopProduct} diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java similarity index 88% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java index 85917dc1c5e7..40cc795fd9a0 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java @@ -27,21 +27,14 @@ import lombok.Getter; import lombok.Setter; -/** - * Encapsulates all of the information that a desktop client needs to display a product. - */ +/** Encapsulates all of the information that a desktop client needs to display a product. */ @Getter @Setter public class DesktopProduct { - /** - * The price of the product. - */ + /** The price of the product. */ private String price; - /** - * The path to the image of the product. - */ + /** The path to the image of the product. */ private String imagePath; - } diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java similarity index 94% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java index 74250271435b..9f7e08c89f43 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.api.gateway; -/** - * An interface used to communicate with the Image microservice. - */ +/** An interface used to communicate with the Image microservice. */ public interface ImageClient { String getImagePath(); } diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java similarity index 93% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java index 86008b74a01c..4e618b2aa0a1 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java @@ -33,9 +33,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -/** - * An adapter to communicate with the Image microservice. - */ +/** An adapter to communicate with the Image microservice. */ @Slf4j @Component public class ImageClientImpl implements ImageClient { @@ -48,10 +46,8 @@ 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(); + var httpGet = + HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:50005/image-path")).build(); try { LOGGER.info("Sending request to fetch image path"); diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java similarity index 91% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java index f621da1a575f..ed42248eadee 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java @@ -27,14 +27,10 @@ import lombok.Getter; import lombok.Setter; -/** - * Encapsulates all of the information that mobile client needs to display a product. - */ +/** Encapsulates all of the information that mobile client needs to display a product. */ @Getter @Setter public class MobileProduct { - /** - * The price of the product. - */ + /** The price of the product. */ private String price; } diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java similarity index 94% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java index 5294dbd6dc4a..003fa478be2f 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.api.gateway; -/** - * An interface used to communicate with the Price microservice. - */ +/** An interface used to communicate with the Price microservice. */ public interface PriceClient { String getPrice(); } diff --git a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java similarity index 93% rename from api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java rename to microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java index b94bdb4a17d3..47fb0617db6c 100644 --- a/api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java @@ -33,10 +33,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; - -/** - * An adapter to communicate with the Price microservice. - */ +/** An adapter to communicate with the Price microservice. */ @Slf4j @Component public class PriceClientImpl implements PriceClient { @@ -49,10 +46,8 @@ 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(); + var httpGet = + HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:50006/price")).build(); try { LOGGER.info("Sending request to fetch price info"); diff --git a/api-gateway/api-gateway-service/src/main/resources/application.properties b/microservices-api-gateway/api-gateway-service/src/main/resources/application.properties similarity index 100% rename from api-gateway/api-gateway-service/src/main/resources/application.properties rename to microservices-api-gateway/api-gateway-service/src/main/resources/application.properties diff --git a/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java b/microservices-api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java similarity index 88% rename from api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java rename to microservices-api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java index 0f1fa938e751..f177512f5976 100644 --- a/api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java +++ b/microservices-api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java @@ -33,28 +33,21 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Test API Gateway Pattern - */ +/** Test API Gateway Pattern */ class ApiGatewayTest { - @InjectMocks - private ApiGateway apiGateway; + @InjectMocks private ApiGateway apiGateway; - @Mock - private ImageClient imageClient; + @Mock private ImageClient imageClient; - @Mock - private PriceClient priceClient; + @Mock private PriceClient priceClient; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); } - /** - * Tests getting the data for a desktop client - */ + /** Tests getting the data for a desktop client */ @Test void testGetProductDesktop() { var imagePath = "/product-image.png"; @@ -68,9 +61,7 @@ void testGetProductDesktop() { assertEquals(imagePath, desktopProduct.getImagePath()); } - /** - * Tests getting the data for a mobile client - */ + /** Tests getting the data for a mobile client */ @Test void testGetProductMobile() { var price = "20"; diff --git a/microservices-api-gateway/etc/api-gateway.png b/microservices-api-gateway/etc/api-gateway.png new file mode 100644 index 000000000000..bb3ec2e2e1a8 Binary files /dev/null and b/microservices-api-gateway/etc/api-gateway.png differ diff --git a/api-gateway/etc/api-gateway.ucls b/microservices-api-gateway/etc/api-gateway.ucls similarity index 100% rename from api-gateway/etc/api-gateway.ucls rename to microservices-api-gateway/etc/api-gateway.ucls diff --git a/microservices-api-gateway/etc/api-gateway.urm.puml b/microservices-api-gateway/etc/api-gateway.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-api-gateway/etc/api-gateway.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/microservices-api-gateway/etc/microservices-api-gateway.urm.puml b/microservices-api-gateway/etc/microservices-api-gateway.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-api-gateway/etc/microservices-api-gateway.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/api-gateway/image-microservice/etc/image-microservice.png b/microservices-api-gateway/image-microservice/etc/image-microservice.png similarity index 100% rename from api-gateway/image-microservice/etc/image-microservice.png rename to microservices-api-gateway/image-microservice/etc/image-microservice.png diff --git a/api-gateway/image-microservice/etc/image-microservice.ucls b/microservices-api-gateway/image-microservice/etc/image-microservice.ucls similarity index 100% rename from api-gateway/image-microservice/etc/image-microservice.ucls rename to microservices-api-gateway/image-microservice/etc/image-microservice.ucls diff --git a/api-gateway/image-microservice/etc/image-microservice.urm.puml b/microservices-api-gateway/image-microservice/etc/image-microservice.urm.puml similarity index 100% rename from api-gateway/image-microservice/etc/image-microservice.urm.puml rename to microservices-api-gateway/image-microservice/etc/image-microservice.urm.puml diff --git a/api-gateway/image-microservice/pom.xml b/microservices-api-gateway/image-microservice/pom.xml similarity index 96% rename from api-gateway/image-microservice/pom.xml rename to microservices-api-gateway/image-microservice/pom.xml index 4abaf32d443c..7c08fe2ec0e1 100644 --- a/api-gateway/image-microservice/pom.xml +++ b/microservices-api-gateway/image-microservice/pom.xml @@ -27,7 +27,7 @@ --> - api-gateway + microservices-api-gateway com.iluwatar 1.26.0-SNAPSHOT @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageApplication.java b/microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageApplication.java similarity index 100% rename from api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageApplication.java rename to microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageApplication.java diff --git a/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java b/microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java similarity index 96% rename from api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java rename to microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java index 0594a4fa9485..a737f0b8ddd1 100644 --- a/api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java +++ b/microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java @@ -28,10 +28,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; - -/** - * Exposes the Image microservice's endpoints. - */ +/** Exposes the Image microservice's endpoints. */ @Slf4j @RestController public class ImageController { diff --git a/api-gateway/image-microservice/src/main/resources/application.properties b/microservices-api-gateway/image-microservice/src/main/resources/application.properties similarity index 100% rename from api-gateway/image-microservice/src/main/resources/application.properties rename to microservices-api-gateway/image-microservice/src/main/resources/application.properties diff --git a/api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java b/microservices-api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java similarity index 97% rename from api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java rename to microservices-api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java index bfb0af75adce..f2ff0c747ec4 100644 --- a/api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java +++ b/microservices-api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.image.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Image Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test for Image Rest Controller */ class ImageControllerTest { @Test diff --git a/api-gateway/pom.xml b/microservices-api-gateway/pom.xml similarity index 97% rename from api-gateway/pom.xml rename to microservices-api-gateway/pom.xml index 8270b16a9bc3..18022d957ed2 100644 --- a/api-gateway/pom.xml +++ b/microservices-api-gateway/pom.xml @@ -32,7 +32,7 @@ 1.26.0-SNAPSHOT 4.0.0 - api-gateway + microservices-api-gateway pom image-microservice diff --git a/api-gateway/price-microservice/etc/price-microservice.png b/microservices-api-gateway/price-microservice/etc/price-microservice.png similarity index 100% rename from api-gateway/price-microservice/etc/price-microservice.png rename to microservices-api-gateway/price-microservice/etc/price-microservice.png diff --git a/api-gateway/price-microservice/etc/price-microservice.ucls b/microservices-api-gateway/price-microservice/etc/price-microservice.ucls similarity index 100% rename from api-gateway/price-microservice/etc/price-microservice.ucls rename to microservices-api-gateway/price-microservice/etc/price-microservice.ucls diff --git a/api-gateway/price-microservice/etc/price-microservice.urm.puml b/microservices-api-gateway/price-microservice/etc/price-microservice.urm.puml similarity index 100% rename from api-gateway/price-microservice/etc/price-microservice.urm.puml rename to microservices-api-gateway/price-microservice/etc/price-microservice.urm.puml diff --git a/api-gateway/price-microservice/pom.xml b/microservices-api-gateway/price-microservice/pom.xml similarity index 96% rename from api-gateway/price-microservice/pom.xml rename to microservices-api-gateway/price-microservice/pom.xml index 8cf51376e4c9..8e95948aff6e 100644 --- a/api-gateway/price-microservice/pom.xml +++ b/microservices-api-gateway/price-microservice/pom.xml @@ -27,7 +27,7 @@ --> - api-gateway + microservices-api-gateway com.iluwatar 1.26.0-SNAPSHOT @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceApplication.java b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceApplication.java similarity index 100% rename from api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceApplication.java rename to microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceApplication.java diff --git a/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java similarity index 89% rename from api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java rename to microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java index 87f107ea50d1..695d3aadb34e 100644 --- a/api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java +++ b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java @@ -24,18 +24,17 @@ */ package com.iluwatar.price.microservice; -import lombok.extern.slf4j.Slf4j; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; - -/** - * Exposes the Price microservice's endpoints. - */ -@Slf4j +/** Exposes the Price microservice's endpoints. */ @RestController +@RequiredArgsConstructor public class PriceController { + private final PriceService priceService; + /** * An endpoint for a user to retrieve a product's price. * @@ -43,7 +42,6 @@ public class PriceController { */ @GetMapping("/price") public String getPrice() { - LOGGER.info("Successfully found price info"); - return "20"; + return priceService.getPrice(); } } diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceService.java similarity index 82% rename from thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java rename to microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceService.java index f24bdc2cfbc4..253bab4e5f81 100644 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java +++ b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceService.java @@ -22,20 +22,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; +package com.iluwatar.price.microservice; -/** - * Date: 12/30/15 - 18:23 PM - * - * @author Jeroen Meulemeester - */ -class CoffeeMakingTaskTest extends TaskTest { +/** Service to get a product's price. */ +public interface PriceService { /** - * Create a new test instance + * Getting the price of a product. + * + * @return A product's price */ - public CoffeeMakingTaskTest() { - super(CoffeeMakingTask::new, 100); - } - + String getPrice(); } diff --git a/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceServiceImpl.java b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceServiceImpl.java new file mode 100644 index 000000000000..1338313fe2de --- /dev/null +++ b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceServiceImpl.java @@ -0,0 +1,41 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.price.microservice; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** {@inheritDoc} */ +@Service +@Slf4j +public class PriceServiceImpl implements PriceService { + + /** {@inheritDoc} */ + @Override + public String getPrice() { + LOGGER.info("Successfully found price info"); + return "20"; + } +} diff --git a/api-gateway/price-microservice/src/main/resources/application.properties b/microservices-api-gateway/price-microservice/src/main/resources/application.properties similarity index 100% rename from api-gateway/price-microservice/src/main/resources/application.properties rename to microservices-api-gateway/price-microservice/src/main/resources/application.properties diff --git a/api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java similarity index 92% rename from api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java rename to microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java index 081e35946f1b..eeaa0c28d377 100644 --- a/api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java +++ b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java @@ -24,18 +24,16 @@ */ package com.iluwatar.price.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Price Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test for Price Rest Controller */ class PriceControllerTest { @Test - void testgetPrice() { - var priceController = new PriceController(); + void getPriceTest() { + var priceController = new PriceController(new PriceServiceImpl()); var price = priceController.getPrice(); assertEquals("20", price); } diff --git a/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceServiceTest.java b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceServiceTest.java new file mode 100644 index 000000000000..830ac7d18423 --- /dev/null +++ b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceServiceTest.java @@ -0,0 +1,40 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.price.microservice; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** Test for Price Service */ +class PriceServiceTest { + + @Test + void getPriceTest() { + var priceService = new PriceServiceImpl(); + var price = priceService.getPrice(); + assertEquals("20", price); + } +} diff --git a/microservices-client-side-ui-composition/README.md b/microservices-client-side-ui-composition/README.md new file mode 100644 index 000000000000..c11b7990308c --- /dev/null +++ b/microservices-client-side-ui-composition/README.md @@ -0,0 +1,136 @@ +--- +title: "Client-Side UI Composition Pattern: Assembling Modular UIs in Microservices Architecture" +shortTitle: Client-Side UI Composition Pattern +description: "Learn how the Client-Side UI Composition pattern allows the assembly of modular UIs on the client side, enabling independent teams to develop, deploy, and scale UI components in a microservices architecture. Discover the benefits, implementation examples, and best practices." +category: User Interface +language: en +tag: + - Micro Frontends + - API Gateway + - Asynchronous Data Fetching + - UI Integration + - Microservices Architecture + - Scalability +--- + +## **Intent of Client-Side UI Composition Design Pattern** + +The Client-Side UI Composition Pattern allows the assembly of UIs on the client side by composing independent UI components (Micro Frontends). Each component is developed, tested, and deployed independently by separate teams, ensuring flexibility and scalability in microservices architecture. + +--- + +## **Also Known As** + +- Micro Frontends +- Modular UI Assembly +- Client-Side Integration + +--- + +## **Detailed Explanation of Client-Side UI Composition Pattern with Real-World Examples** + +### **Real-world Example** +> In a SaaS dashboard, a client-side composition pattern enables various independent modules like “Billing,” “Reports,” and “Account Settings” to be developed and deployed by separate teams. These modules are composed into a unified interface for the user, with each module independently fetching data from its respective microservice. + +### **In Plain Words** +> The Client-Side UI Composition pattern breaks down the user interface into smaller, independent parts that can be developed, maintained, and scaled separately by different teams. + +### **Wikipedia says** +>UI composition refers to the practice of building a user interface from modular components, each responsible for fetching its own data and rendering its own content. This approach enables faster development cycles, easier maintenance, and better scalability in large systems. +--- + +## **Programmatic Example of Client-Side UI Composition in JavaScript** + +In this example, an e-commerce platform composes its frontend by integrating three independent modules: **CartService**, **ProductService**, and **OrderService**. Each module is served by a microservice and fetched on the client side through an API Gateway. + +`ApiGateway` Implementation + +```java +public class ApiGateway { + + private final Map routes = new HashMap<>(); + + public void registerRoute(String path, FrontendComponent component) { + routes.put(path, component); + } + + public String handleRequest(String path, Map params) { + if (routes.containsKey(path)) { + return routes.get(path).fetchData(params); + } else { + return "404 Not Found"; + } + } +} + +``` + +`FrontendComponent` Implementation +```java +public interface FrontendComponent { + String fetchData(Map params); +} +``` +## Example components +```java +public class ProductComponent implements FrontendComponent { + @Override + public String fetchData(Map params) { + return "Displaying Products: " + params.getOrDefault("category", "all"); + } +} + +public class CartComponent implements FrontendComponent { + @Override + public String fetchData(Map params) { + return "Displaying Cart for User: " + params.getOrDefault("userId", "unknown"); + } +} +``` +This approach dynamically assembles different UI components based on the route provided in the client-side request. Each component fetches its data asynchronously and renders it within the main interface. + +--- + +## **When to Use the Client-Side UI Composition Pattern** + +- When you have a microservices architecture where multiple teams develop different parts of the frontend. +- When you need to scale and deploy different UI modules independently. +- When you want to integrate multiple data sources or services into a cohesive frontend. + +--- + +## **Client-Side UI Composition Pattern JavaScript Tutorials** + +- [Micro Frontends in Action (O'Reilly)](https://www.oreilly.com/library/view/micro-frontends-in/9781617296873/) +- [Micro Frontends with React (ThoughtWorks)](https://www.thoughtworks.com/insights/articles/building-micro-frontends-using-react) +- [API Gateway in Microservices (Spring Cloud)](https://spring.io/guides/gs/gateway/) + +--- + +## **Benefits and Trade-offs of Client-Side UI Composition Pattern** + +### **Benefits**: +- **Modularity**: Each UI component is independent and can be developed by separate teams. +- **Scalability**: Micro Frontends allow for independent deployment and scaling of each component. +- **Flexibility**: Teams can choose different technologies to build components, offering flexibility in development. +- **Asynchronous Data Fetching**: Components can load data individually, improving performance. + +### **Trade-offs**: +- **Increased Complexity**: Managing multiple micro frontends can increase overall system complexity. +- **Client-Side Performance**: Depending on the number of micro frontends, it may introduce a performance overhead due to multiple asynchronous requests. +- **Integration Overhead**: Client-side integration logic can become complex as the number of components grows. + +--- + +## **Related Design Patterns** + +- [Microservices API Gateway Pattern](https://java-design-patterns.com/patterns/microservices-api-gateway/) – API Gateway serves as a routing mechanism for client-side UI requests. +- [Backend for Frontend (BFF)](https://microservices.io/patterns/apigateway.html) – BFF pattern helps build custom backends for different UI experiences. + +--- + +## **References and Credits** + +- [Micro Frontends Architecture (Microfrontends.org)](https://micro-frontends.org/) +- [Building Microservices with Micro Frontends](https://martinfowler.com/articles/micro-frontends.html) +- [Client-Side UI Composition (Microservices.io)](https://microservices.io/patterns/client-side-ui-composition.html) diff --git a/microservices-client-side-ui-composition/etc/client-side-ui-composition.urm.puml b/microservices-client-side-ui-composition/etc/client-side-ui-composition.urm.puml new file mode 100644 index 000000000000..ef88e9c22ac7 --- /dev/null +++ b/microservices-client-side-ui-composition/etc/client-side-ui-composition.urm.puml @@ -0,0 +1,31 @@ +@startuml client_side_ui_composition_updated +skinparam classAttributeIconSize 0 + +class ApiGateway { + +registerRoute(path: String, component: FrontendComponent) + +handleRequest(path: String, params: Map): String +} + +class FrontendComponent { + +fetchData(params: Map): String + #getData(params: Map): String +} + +class ProductFrontend { + +getData(params: Map): String +} + +class CartFrontend { + +getData(params: Map): String +} + +class ClientSideIntegrator { + +composeUI(path: String, params: Map) +} + +ApiGateway --> FrontendComponent +FrontendComponent <|-- ProductFrontend +FrontendComponent <|-- CartFrontend +ClientSideIntegrator --> ApiGateway + +@enduml diff --git a/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition.urm.puml b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition.urm.puml new file mode 100644 index 000000000000..0820d3e45a17 --- /dev/null +++ b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition.urm.puml @@ -0,0 +1,33 @@ +@startuml +package com.iluwatar.clientsideuicomposition { + class ApiGateway { + - routes : Map + + ApiGateway() + + handleRequest(path : String, params : Map) : String + + registerRoute(path : String, component : FrontendComponent) + } + class CartFrontend { + + CartFrontend() + # getData(params : Map) : String + } + class ClientSideIntegrator { + - LOGGER : Logger {static} + - apiGateway : ApiGateway + + ClientSideIntegrator(apiGateway : ApiGateway) + + composeUi(path : String, params : Map) + } + abstract class FrontendComponent { + + random : Random {static} + + FrontendComponent() + + fetchData(params : Map) : String + # getData(Map) : String {abstract} + } + class ProductFrontend { + + ProductFrontend() + # getData(params : Map) : String + } +} +ClientSideIntegrator --> "-apiGateway" ApiGateway +CartFrontend --|> FrontendComponent +ProductFrontend --|> FrontendComponent +@enduml \ No newline at end of file diff --git a/microservices-client-side-ui-composition/pom.xml b/microservices-client-side-ui-composition/pom.xml new file mode 100644 index 000000000000..2d5644a14633 --- /dev/null +++ b/microservices-client-side-ui-composition/pom.xml @@ -0,0 +1,58 @@ + + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + microservices-client-side-ui-composition + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + \ No newline at end of file diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ApiGateway.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ApiGateway.java new file mode 100644 index 000000000000..6fc1cc643a4f --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ApiGateway.java @@ -0,0 +1,73 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.HashMap; +import java.util.Map; + +/** + * ApiGateway class acts as a dynamic routing mechanism that forwards client requests to the + * appropriate frontend components based on dynamically registered routes. + * + *

This allows for flexible, runtime-defined routing without hardcoding specific paths. + */ +public class ApiGateway { + + // A map to store routes dynamically, where the key is the path and the value + // is the associated FrontendComponent + private final Map routes = new HashMap<>(); + + /** + * Registers a route dynamically at runtime. + * + * @param path the path to access the component (e.g., "/products") + * @param component the frontend component to be accessed at the given path + */ + public void registerRoute(String path, FrontendComponent component) { + routes.put(path, component); + } + + /** + * Handles a client request by routing it to the appropriate frontend component. + * + *

This method dynamically handles parameters passed with the request, which allows the + * frontend components to respond based on those parameters. + * + * @param path the path for which the request is made (e.g., "/products", "/cart") + * @param params a map of parameters that might influence the data fetching logic (e.g., filters, + * userId, categories, etc.) + * @return the data fetched from the appropriate component or "404 Not Found" if the path is not + * registered + */ + public String handleRequest(String path, Map params) { + if (routes.containsKey(path)) { + // Fetch data dynamically based on the provided parameters + return routes.get(path).fetchData(params); + } else { + // Return a 404 error if the path is not registered + return "404 Not Found"; + } + } +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/CartFrontend.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/CartFrontend.java new file mode 100644 index 000000000000..d2916408babd --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/CartFrontend.java @@ -0,0 +1,46 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; + +/** + * CartFrontend is a concrete implementation of FrontendComponent that simulates fetching shopping + * cart data based on the user. + */ +public class CartFrontend extends FrontendComponent { + + /** + * Fetches the current state of the shopping cart based on dynamic parameters like user ID. + * + * @param params parameters that influence the cart data, e.g., "userId" + * @return a string representing the items in the shopping cart for a given user + */ + @Override + protected String getData(Map params) { + String userId = params.getOrDefault("userId", "anonymous"); + return "Shopping Cart for user '" + userId + "': [Item 1, Item 2]"; + } +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ClientSideIntegrator.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ClientSideIntegrator.java new file mode 100644 index 000000000000..ca99747a7ddf --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ClientSideIntegrator.java @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * ClientSideIntegrator class simulates the client-side integration layer that dynamically assembles + * various frontend components into a cohesive user interface. + */ +@Slf4j +public class ClientSideIntegrator { + + private final ApiGateway apiGateway; + + /** + * Constructor that accepts an instance of ApiGateway to handle dynamic routing. + * + * @param apiGateway the gateway that routes requests to different frontend components + */ + public ClientSideIntegrator(ApiGateway apiGateway) { + this.apiGateway = apiGateway; + } + + /** + * Composes the user interface dynamically by fetching data from different frontend components + * based on provided parameters. + * + * @param path the route of the frontend component + * @param params a map of dynamic parameters to influence the data fetching + */ + public void composeUi(String path, Map params) { + // Fetch data dynamically based on the route and parameters + String data = apiGateway.handleRequest(path, params); + LOGGER.info("Composed UI Component for path '" + path + "':"); + LOGGER.info(data); + } +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/FrontendComponent.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/FrontendComponent.java new file mode 100644 index 000000000000..70399c31854f --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/FrontendComponent.java @@ -0,0 +1,63 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; +import java.util.Random; + +/** + * FrontendComponent is an abstract class representing an independent frontend component that + * fetches data dynamically based on the provided parameters. + */ +public abstract class FrontendComponent { + + public static final Random random = new Random(); + + /** + * Simulates asynchronous data fetching by introducing a random delay and then fetching the data + * based on dynamic input. + * + * @param params a map of parameters that may affect the data fetching logic + * @return the data fetched by the frontend component + */ + public String fetchData(Map params) { + try { + // Simulate delay in fetching data (e.g., network latency) + Thread.sleep(random.nextInt(1000)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + // Fetch and return the data based on the given parameters + return getData(params); + } + + /** + * Abstract method to be implemented by subclasses to return data based on parameters. + * + * @param params a map of parameters that may affect the data fetching logic + * @return the data for this specific component + */ + protected abstract String getData(Map params); +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ProductFrontend.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ProductFrontend.java new file mode 100644 index 000000000000..157f2aef3fd6 --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ProductFrontend.java @@ -0,0 +1,46 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; + +/** + * ProductFrontend is a concrete implementation of FrontendComponent that simulates fetching dynamic + * product data. + */ +public class ProductFrontend extends FrontendComponent { + + /** + * Fetches a list of products based on dynamic parameters such as category. + * + * @param params parameters that influence the data fetched, e.g., "category" + * @return a string representing a filtered list of products + */ + @Override + protected String getData(Map params) { + String category = params.getOrDefault("category", "all"); + return "Product List for category '" + category + "': [Product 1, Product 2, Product 3]"; + } +} diff --git a/microservices-client-side-ui-composition/src/test/java/com/iluwatar/clientsideuicomposition/ClientSideCompositionTest.java b/microservices-client-side-ui-composition/src/test/java/com/iluwatar/clientsideuicomposition/ClientSideCompositionTest.java new file mode 100644 index 000000000000..be5aef2f7aaf --- /dev/null +++ b/microservices-client-side-ui-composition/src/test/java/com/iluwatar/clientsideuicomposition/ClientSideCompositionTest.java @@ -0,0 +1,68 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + * ClientSideCompositionTest contains unit tests to validate dynamic route registration and UI + * composition. + */ +class ClientSideCompositionTest { + + /** Tests dynamic registration of frontend components and dynamic composition of UI. */ + @Test + void testClientSideUIComposition() { + // Create API Gateway and dynamically register frontend components + ApiGateway apiGateway = new ApiGateway(); + apiGateway.registerRoute("/products", new ProductFrontend()); + apiGateway.registerRoute("/cart", new CartFrontend()); + + // Create the Client-Side Integrator + ClientSideIntegrator integrator = new ClientSideIntegrator(apiGateway); + + // Dynamically pass parameters for data fetching + Map productParams = new HashMap<>(); + productParams.put("category", "electronics"); + + // Compose UI for products and cart with dynamic params + integrator.composeUi("/products", productParams); + + Map cartParams = new HashMap<>(); + cartParams.put("userId", "user123"); + integrator.composeUi("/cart", cartParams); + + // Validate the dynamically fetched data + String productData = apiGateway.handleRequest("/products", productParams); + String cartData = apiGateway.handleRequest("/cart", cartParams); + + assertTrue(productData.contains("Product List for category 'electronics'")); + assertTrue(cartData.contains("Shopping Cart for user 'user123'")); + } +} diff --git a/microservices-distributed-tracing/README.md b/microservices-distributed-tracing/README.md new file mode 100644 index 000000000000..14b34726fc10 --- /dev/null +++ b/microservices-distributed-tracing/README.md @@ -0,0 +1,201 @@ +--- +title: "Microservices Distributed Tracing Pattern: Enhancing Visibility in Service Communication" +shortTitle: Distributed Tracing in Microservices +description: "Learn how the Distributed Tracing pattern enhances visibility into service communication across microservices. Discover its benefits, implementation examples, and best practices." +category: Integration +language: en +tag: + - Distributed tracing + - Microservices architecture + - Service communication + - Performance monitoring + - Scalability + - Observability +--- + +## Intent of Microservices Distributed Tracing Design Pattern + +Distributed tracing aims to monitor and track requests as they flow through different services in a microservices architecture, providing insights into performance, dependencies, and failures. + +## Also known as + +* Distributed Request Tracing +* End-to-End Tracing + +## Detailed Explanation of Microservices Distributed Tracing Pattern with Real-World Examples + +Real-world example + +> In an e-commerce platform, distributed tracing is used to track a customer's request from the moment they add an item to the cart until the order is processed and shipped. This helps in identifying bottlenecks, errors, and latency issues across different services. + +In plain words + +> Distributed tracing allows you to follow a request's journey through all the services it interacts with, providing insights into system performance and aiding in debugging. + +Wikipedia says + +> Tracing in software engineering refers to the process of capturing and recording information about the execution of a software program. This information is typically used by programmers for debugging purposes, and additionally, depending on the type and detail of information contained in a trace log, by experienced system administrators or technical-support personnel and by software monitoring tools to diagnose common problems with software. + +## Programmatic Example of Microservices Distributed Tracing in Java + + +This implementation shows how an e-commerce platform's `OrderService` interacts with both `PaymentService` and `ProductService`. When a customer places an order, the `OrderService` calls the `PaymentService` to process the payment and the `ProductService` to check the product inventory. Distributed tracing logs are generated for each of these interactions and can be viewed in the Zipkin interface to monitor the flow and performance of requests across these services. + +Here's the `Order microservice` implementation. + +```java +@Slf4j +@RestController +public class OrderController { + + private final OrderService orderService; + + public OrderController(final OrderService orderService) { + this.orderService = orderService; + } + + @PostMapping("/order") + public ResponseEntity processOrder(@RequestBody(required = false) String request) { + LOGGER.info("Received order request: {}", request); + var result = orderService.processOrder(); + LOGGER.info("Order processed result: {}", result); + return ResponseEntity.ok(result); + } +} +``` +```java +@Slf4j +@Service +public class OrderService { + + private final RestTemplateBuilder restTemplateBuilder; + + public OrderService(final RestTemplateBuilder restTemplateBuilder) { + this.restTemplateBuilder = restTemplateBuilder; + } + + public String processOrder() { + if (validateProduct() && processPayment()) { + return "Order processed successfully"; + } + return "Order processing failed"; + } + + Boolean validateProduct() { + try { + ResponseEntity productValidationResult = restTemplateBuilder + .build() + .postForEntity("http://localhost:30302/product/validate", "validating product", + Boolean.class); + LOGGER.info("Product validation result: {}", productValidationResult.getBody()); + return productValidationResult.getBody(); + } catch (ResourceAccessException | HttpClientErrorException e) { + LOGGER.error("Error communicating with product service: {}", e.getMessage()); + return false; + } + } + + Boolean processPayment() { + try { + ResponseEntity paymentProcessResult = restTemplateBuilder + .build() + .postForEntity("http://localhost:30301/payment/process", "processing payment", + Boolean.class); + LOGGER.info("Payment processing result: {}", paymentProcessResult.getBody()); + return paymentProcessResult.getBody(); + } catch (ResourceAccessException | HttpClientErrorException e) { + LOGGER.error("Error communicating with payment service: {}", e.getMessage()); + return false; + } + } +} +``` + +Here's the `Payment microservice` implementation. + +```java + +@Slf4j +@RestController +public class PaymentController { + + @PostMapping("/payment/process") + public ResponseEntity payment(@RequestBody(required = false) String request) { + LOGGER.info("Received payment request: {}", request); + boolean result = true; + LOGGER.info("Payment result: {}", result); + return ResponseEntity.ok(result); + } +} +``` + +Here's the `Product microservice` implementation. + +```java +/** + * Controller for handling product validation requests. + */ +@Slf4j +@RestController +public class ProductController { + + /** + * Validates the product based on the request. + * + * @param request the request body containing product information (can be null) + * @return ResponseEntity containing the validation result (true) + */ + @PostMapping("/product/validate") + public ResponseEntity validateProduct(@RequestBody(required = false) String request) { + LOGGER.info("Received product validation request: {}", request); + boolean result = true; + LOGGER.info("Product validation result: {}", result); + return ResponseEntity.ok(result); + } +} +``` + +## When to Use the Microservices Distributed Tracing Pattern in Java + +* When you have a microservices architecture and need to monitor the flow of requests across multiple services. +* When troubleshooting performance issues or errors in a distributed system. +* When you need to gain insights into system bottlenecks and optimize overall performance. + + +## Microservices Distributed Tracing Pattern Java Tutorials + +* [Spring Boot - Tracing (Spring)](https://docs.spring.io/spring-boot/reference/actuator/tracing.html) +* [Reactive Observability (Spring Academy)](https://spring.academy/guides/microservices-observability-reactive-spring-boot-3) +* [Spring Cloud – Tracing Services with Zipkin (Baeldung)](https://dzone.com/articles/getting-started-with-spring-cloud-gateway) + +## Benefits and Trade-offs of Microservices Distributed Tracing Pattern + +Benefits: + +* Provides end-to-end visibility into requests. +* Helps in identifying performance bottlenecks. +* Aids in debugging and troubleshooting complex systems. + +Trade-offs: + +* Adds overhead to each request due to tracing data. +* Requires additional infrastructure (e.g., Zipkin, Jaeger) for collecting and visualizing traces. +* Can become complex to manage in large-scale systems. + +## Real-World Applications of Microservices Distributed Tracing Pattern in Java + +* Monitoring and troubleshooting e-commerce platforms. +* Performance monitoring in financial transaction systems. +* Observability in large-scale SaaS applications. + +## Related Java Design Patterns + +* [Log Aggregation Microservice](https://java-design-patterns.com/patterns/microservices-log-aggregation/) - Distributed tracing works well in conjunction with log aggregation to provide comprehensive observability and troubleshooting capabilities. +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) - Distributed tracing can be used alongside the Circuit Breaker pattern to monitor and handle failures gracefully, preventing cascading failures in microservices. +* [API Gateway Microservice](https://java-design-patterns.com/patterns/microservices-api-gateway/) - The API Gateway pattern can be integrated with distributed tracing to provide a single entry point for tracing requests across multiple microservices. + +## References and Credits + +* [Building Microservices](https://amzn.to/3UACtrU) +* [OpenTelemetry Documentation](https://opentelemetry.io/docs/) +* [Distributed tracing (microservices.io)](https://microservices.io/patterns/observability/distributed-tracing.html) diff --git a/microservices-distributed-tracing/etc/microservices-distributed-tracing.png b/microservices-distributed-tracing/etc/microservices-distributed-tracing.png new file mode 100644 index 000000000000..00f06108b60a Binary files /dev/null and b/microservices-distributed-tracing/etc/microservices-distributed-tracing.png differ diff --git a/microservices-distributed-tracing/etc/microservices-distributed-tracing.puml b/microservices-distributed-tracing/etc/microservices-distributed-tracing.puml new file mode 100644 index 000000000000..9c303f0dc1fa --- /dev/null +++ b/microservices-distributed-tracing/etc/microservices-distributed-tracing.puml @@ -0,0 +1,31 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { +package "Order Microservice" { + class OrderController { + +processOrder() + } + + class OrderService { + +validateProduct() + +processPayment() + } +} + +package "Payment Microservice" { + class PaymentController { + +processPayment() + } +} + +package "Product Microservice" { + class ProductController { + +validateProduct() + } +} + +OrderController --> OrderService +OrderService --> PaymentController : processPayment() +OrderService --> ProductController : validateProduct() +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/etc/microservices-distributed-tracing.urm.puml b/microservices-distributed-tracing/etc/microservices-distributed-tracing.urm.puml new file mode 100644 index 000000000000..9c303f0dc1fa --- /dev/null +++ b/microservices-distributed-tracing/etc/microservices-distributed-tracing.urm.puml @@ -0,0 +1,31 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { +package "Order Microservice" { + class OrderController { + +processOrder() + } + + class OrderService { + +validateProduct() + +processPayment() + } +} + +package "Payment Microservice" { + class PaymentController { + +processPayment() + } +} + +package "Product Microservice" { + class ProductController { + +validateProduct() + } +} + +OrderController --> OrderService +OrderService --> PaymentController : processPayment() +OrderService --> ProductController : validateProduct() +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/order-microservice/etc/order-microservice.png b/microservices-distributed-tracing/order-microservice/etc/order-microservice.png new file mode 100644 index 000000000000..d2951e1ad8db Binary files /dev/null and b/microservices-distributed-tracing/order-microservice/etc/order-microservice.png differ diff --git a/microservices-distributed-tracing/order-microservice/etc/order-microservice.puml b/microservices-distributed-tracing/order-microservice/etc/order-microservice.puml new file mode 100644 index 000000000000..013138441aca --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/etc/order-microservice.puml @@ -0,0 +1,15 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { +package "Order Microservice" { + class OrderController { + +processOrder() + } + + class OrderService { + +validateProduct() + +processPayment() + } +} +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/order-microservice/etc/order-microservice.urm.puml b/microservices-distributed-tracing/order-microservice/etc/order-microservice.urm.puml new file mode 100644 index 000000000000..013138441aca --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/etc/order-microservice.urm.puml @@ -0,0 +1,15 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { +package "Order Microservice" { + class OrderController { + +processOrder() + } + + class OrderService { + +validateProduct() + +processPayment() + } +} +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/order-microservice/pom.xml b/microservices-distributed-tracing/order-microservice/pom.xml new file mode 100644 index 000000000000..f55c175b921a --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/pom.xml @@ -0,0 +1,63 @@ + + + + + microservices-distributed-tracing + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + order-microservice + jar + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.order.microservice.com.iluwatar.product.microservice.Main + + + + + + + + + + + central + Maven Central Repository + https://repo.maven.apache.org/maven2 + + + diff --git a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/Main.java b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/Main.java new file mode 100644 index 000000000000..b6b6da79faae --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/Main.java @@ -0,0 +1,78 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.order.microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * With the Microservices pattern, a request often travels through multiple different microservices. + * Tracking the entire request flow across these services can be challenging, especially when trying + * to diagnose performance issues or failures. Distributed tracing addresses this challenge by + * providing end-to-end visibility into the lifecycle of a request as it passes through various + * microservices. + * + *

The intent of the Distributed Tracing pattern is to trace a request across different + * microservices, collecting detailed timing data and logs that help in understanding the flow, + * performance bottlenecks, and failure points in a distributed system. Each microservice involved + * in the request contributes to the tracing data, creating a comprehensive view of the request's + * journey. + * + *

This implementation demonstrates distributed tracing in a microservices architecture for an + * e-commerce platform. When a customer places an order, the {@link OrderService} interacts with + * both the payment-microservice to process the payment and the product-microservice to check the + * product inventory. Tracing logs are generated for each interaction, and these logs can be + * visualized using Zipkin. + * + *

To run Zipkin and view the tracing logs, you can use the following Docker command: + * + *

+ * {@code docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin }
+ * 
+ * + *

Start Zipkin with the command above. Once Zipkin is running, you can access the Zipkin UI at + * `...` to view the tracing logs and analyze the request flows + * across your microservices. + * + *

To place an order and generate tracing data, you can use the following curl command: + * + *

+ * {@code curl -X POST http://localhost:30300/order -H "Content-Type: application/json" -d '{"orderId": "123"}' }
+ * 
+ * + *

This command sends a POST request to create an order, which will trigger interactions with the + * payment and product microservices, generating tracing logs that can be viewed in Zipkin. + */ +@SpringBootApplication +public class Main { + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} diff --git a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderController.java b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderController.java new file mode 100644 index 000000000000..a7c67868c49a --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderController.java @@ -0,0 +1,62 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.order.microservice; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** This controller handles order processing by calling necessary microservices. */ +@Slf4j +@RestController +public class OrderController { + + private final OrderService orderService; + + /** + * Constructor to inject OrderService. + * + * @param orderService the service to process orders + */ + public OrderController(final OrderService orderService) { + this.orderService = orderService; + } + + /** + * Endpoint to process an order. + * + * @param request the order request body (can be null) + * @return ResponseEntity with a status message + */ + @PostMapping("/order") + public ResponseEntity processOrder(@RequestBody(required = false) String request) { + LOGGER.info("Received order request: {}", request); + var result = orderService.processOrder(); + LOGGER.info("Order processed result: {}", result); + return ResponseEntity.ok(result); + } +} diff --git a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderService.java b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderService.java new file mode 100644 index 000000000000..b4be09309d26 --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderService.java @@ -0,0 +1,102 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.order.microservice; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.ResourceAccessException; + +/** Service to handle order processing logic. */ +@Slf4j +@Service +public class OrderService { + + private final RestTemplateBuilder restTemplateBuilder; + + /** + * Constructor to inject RestTemplateBuilder. + * + * @param restTemplateBuilder the RestTemplateBuilder to build RestTemplate instances + */ + public OrderService(final RestTemplateBuilder restTemplateBuilder) { + this.restTemplateBuilder = restTemplateBuilder; + } + + /** + * Processes an order by calling {@link OrderService#validateProduct()} and {@link + * OrderService#processPayment()}. + * + * @return A string indicating whether the order was processed successfully or failed. + */ + public String processOrder() { + if (validateProduct() && processPayment()) { + return "Order processed successfully"; + } + return "Order processing failed"; + } + + /** + * Validates the product by calling the respective microservice. + * + * @return true if the product is valid, false otherwise. + */ + Boolean validateProduct() { + try { + ResponseEntity productValidationResult = + restTemplateBuilder + .build() + .postForEntity( + "http://localhost:30302/product/validate", "validating product", Boolean.class); + LOGGER.info("Product validation result: {}", productValidationResult.getBody()); + return productValidationResult.getBody(); + } catch (ResourceAccessException | HttpClientErrorException e) { + LOGGER.error("Error communicating with product service: {}", e.getMessage()); + return false; + } + } + + /** + * Validates the product by calling the respective microservice. + * + * @return true if the product is valid, false otherwise. + */ + Boolean processPayment() { + try { + ResponseEntity paymentProcessResult = + restTemplateBuilder + .build() + .postForEntity( + "http://localhost:30301/payment/process", "processing payment", Boolean.class); + LOGGER.info("Payment processing result: {}", paymentProcessResult.getBody()); + return paymentProcessResult.getBody(); + } catch (ResourceAccessException | HttpClientErrorException e) { + LOGGER.error("Error communicating with payment service: {}", e.getMessage()); + return false; + } + } +} diff --git a/microservices-distributed-tracing/order-microservice/src/main/resources/application.properties b/microservices-distributed-tracing/order-microservice/src/main/resources/application.properties new file mode 100644 index 000000000000..fd183251dfd6 --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/src/main/resources/application.properties @@ -0,0 +1,33 @@ +# +# The MIT License +# Copyright © 2014-2021 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +spring.application.name=order-microservice +server.port=30300 + +management.tracing.sampling.probability=1 +logging.pattern.level=%5p [${spring.zipkin.service.name:${spring.application.name:}},%X{traceId:-},%X{spanId:-}] +management.tracing.propagation.type=w3c +management.tracing.baggage.enabled=true +management.tracing.enabled=true +management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans + diff --git a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/MainTest.java b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/MainTest.java new file mode 100644 index 000000000000..97f9295ff0c1 --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/MainTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.order.microservice; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +/** Application test */ +class MainTest { + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> Main.main(new String[] {})); + } +} diff --git a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderControllerTest.java b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderControllerTest.java new file mode 100644 index 000000000000..05ecc03df0d7 --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderControllerTest.java @@ -0,0 +1,93 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.order.microservice; /* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.ResponseEntity; + +/** OrderControllerTest class to test the OrderController. */ +class OrderControllerTest { + + @InjectMocks private OrderController orderController; + + @Mock private OrderService orderService; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + } + + /** Test to process the order successfully. */ + @Test + void processOrderShouldReturnSuccessStatus() { + // Arrange + when(orderService.processOrder()).thenReturn("Order processed successfully"); + // Act + ResponseEntity response = orderController.processOrder("test order"); + // Assert + assertEquals("Order processed successfully", response.getBody()); + } + + /** Test to process the order with failure. */ + @Test + void ProcessOrderShouldReturnFailureStatusWhen() { + // Arrange + when(orderService.processOrder()).thenReturn("Order processing failed"); + // Act + ResponseEntity response = orderController.processOrder("test order"); + // Assert + assertEquals("Order processing failed", response.getBody()); + } +} diff --git a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderServiceTest.java b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderServiceTest.java new file mode 100644 index 000000000000..10a3fcacb670 --- /dev/null +++ b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderServiceTest.java @@ -0,0 +1,184 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.order.microservice; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; + +/** OrderServiceTest class to test the OrderService. */ +class OrderServiceTest { + + @InjectMocks private OrderService orderService; + + @Mock private RestTemplateBuilder restTemplateBuilder; + + @Mock private RestTemplate restTemplate; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + when(restTemplateBuilder.build()).thenReturn(restTemplate); + } + + /** Test to process the order successfully. */ + @Test + void testProcessOrder_Success() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + .thenReturn(ResponseEntity.ok(true)); + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + .thenReturn(ResponseEntity.ok(true)); + // Act + String result = orderService.processOrder(); + // Assert + assertEquals("Order processed successfully", result); + } + + /** Test to process the order with failure caused by product validation failure. */ + @Test + void testProcessOrder_FailureWithProductValidationFailure() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + .thenReturn(ResponseEntity.ok(false)); + // Act + String result = orderService.processOrder(); + // Assert + assertEquals("Order processing failed", result); + } + + /** Test to process the order with failure caused by payment processing failure. */ + @Test + void testProcessOrder_FailureWithPaymentProcessingFailure() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + .thenReturn(ResponseEntity.ok(true)); + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + .thenReturn(ResponseEntity.ok(false)); + // Act + String result = orderService.processOrder(); + // Assert + assertEquals("Order processing failed", result); + } + + /** Test to validate the product. */ + @Test + void testValidateProduct() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + .thenReturn(ResponseEntity.ok(true)); + // Act + Boolean result = orderService.validateProduct(); + // Assert + assertEquals(true, result); + } + + /** Test to process the payment. */ + @Test + void testProcessPayment() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + .thenReturn(ResponseEntity.ok(true)); + // Act + Boolean result = orderService.processPayment(); + // Assert + assertEquals(true, result); + } + + /** Test to validate the product with ResourceAccessException. */ + @Test + void testValidateProduct_ResourceAccessException() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + .thenThrow(new ResourceAccessException("Service unavailable")); + // Act + Boolean result = orderService.validateProduct(); + // Assert + assertEquals(false, result); + } + + /** Test to validate the product with HttpClientErrorException. */ + @Test + void testValidateProduct_HttpClientErrorException() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + .thenThrow( + new HttpClientErrorException( + org.springframework.http.HttpStatus.BAD_REQUEST, "Bad request")); + // Act + Boolean result = orderService.validateProduct(); + // Assert + assertEquals(false, result); + } + + /** Test to process the payment with ResourceAccessException. */ + @Test + void testProcessPayment_ResourceAccessException() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + .thenThrow(new ResourceAccessException("Service unavailable")); + // Act + Boolean result = orderService.processPayment(); + // Assert + assertEquals(false, result); + } + + /** Test to process the payment with HttpClientErrorException. */ + @Test + void testProcessPayment_HttpClientErrorException() { + // Arrange + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + .thenThrow( + new HttpClientErrorException( + org.springframework.http.HttpStatus.BAD_REQUEST, "Bad request")); + // Act + Boolean result = orderService.processPayment(); + // Assert + assertEquals(false, result); + } +} diff --git a/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.png b/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.png new file mode 100644 index 000000000000..991fcf462e46 Binary files /dev/null and b/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.png differ diff --git a/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.puml b/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.puml new file mode 100644 index 000000000000..06030c7e7eb9 --- /dev/null +++ b/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.puml @@ -0,0 +1,11 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { + +package "Payment Microservice" { + class PaymentController { + +processPayment() + } +} +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.urm.puml b/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.urm.puml new file mode 100644 index 000000000000..06030c7e7eb9 --- /dev/null +++ b/microservices-distributed-tracing/payment-microservice/etc/payment-microservice.urm.puml @@ -0,0 +1,11 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { + +package "Payment Microservice" { + class PaymentController { + +processPayment() + } +} +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/payment-microservice/pom.xml b/microservices-distributed-tracing/payment-microservice/pom.xml new file mode 100644 index 000000000000..5dd45b1086ef --- /dev/null +++ b/microservices-distributed-tracing/payment-microservice/pom.xml @@ -0,0 +1,56 @@ + + + + + microservices-distributed-tracing + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + payment-microservice + jar + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.payment.microservice.com.iluwatar.product.microservice.Main + + + + + + + + + diff --git a/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/Main.java b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/Main.java new file mode 100644 index 000000000000..506096f4e909 --- /dev/null +++ b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/Main.java @@ -0,0 +1,77 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.payment.microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * With the Microservices pattern, a request often travels through multiple different microservices. + * Tracking the entire request flow across these services can be challenging, especially when trying + * to diagnose performance issues or failures. Distributed tracing addresses this challenge by + * providing end-to-end visibility into the lifecycle of a request as it passes through various + * microservices. + * + *

The intent of the Distributed Tracing pattern is to trace a request across different + * microservices, collecting detailed timing data and logs that help in understanding the flow, + * performance bottlenecks, and failure points in a distributed system. Each microservice involved + * in the request contributes to the tracing data, creating a comprehensive view of the request's + * journey. + * + *

This implementation demonstrates distributed tracing in a microservices architecture for an + * e-commerce platform. When a customer places an order, the OrderService interacts with both the + * PaymentService to process the payment and the ProductService to check the product inventory. + * Tracing logs are generated for each interaction, and these logs can be visualized using Zipkin. + * + *

To run Zipkin and view the tracing logs, you can use the following Docker command: + * + *

+ * {@code docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin }
+ * 
+ * + *

Start Zipkin with the command above. Once Zipkin is running, you can access the Zipkin UI at + * http://localhost:9411 to view the tracing logs and analyze + * the request flows across your microservices. + * + *

To place an order and generate tracing data, you can use the following curl command: + * + *

+ * {@code curl -X POST http://localhost:30300/order -H "Content-Type: application/json" -d '{"orderId": "123"}' }
+ * 
+ * + *

This command sends a POST request to create an order, which will trigger interactions with the + * payment and product microservices, generating tracing logs that can be viewed in Zipkin. + */ +@SpringBootApplication +public class Main { + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} diff --git a/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/PaymentController.java b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/PaymentController.java new file mode 100644 index 000000000000..834c88c07942 --- /dev/null +++ b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/PaymentController.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.payment.microservice; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** Controller for handling payment processing requests. */ +@Slf4j +@RestController +public class PaymentController { + + /** + * Processes the payment based on the request. + * + * @param request the request body containing payment information (can be null) + * @return ResponseEntity containing the payment processing result (true) + */ + @PostMapping("/payment/process") + public ResponseEntity payment(@RequestBody(required = false) String request) { + LOGGER.info("Received payment request: {}", request); + boolean result = true; + LOGGER.info("Payment result: {}", result); + return ResponseEntity.ok(result); + } +} diff --git a/microservices-distributed-tracing/payment-microservice/src/main/resources/application.properties b/microservices-distributed-tracing/payment-microservice/src/main/resources/application.properties new file mode 100644 index 000000000000..5f24f5b603a4 --- /dev/null +++ b/microservices-distributed-tracing/payment-microservice/src/main/resources/application.properties @@ -0,0 +1,32 @@ +# +# The MIT License +# Copyright © 2014-2021 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +spring.application.name=payment-microservice +server.port=30301 + +management.tracing.sampling.probability=1 +logging.pattern.level=%5p [${spring.zipkin.service.name:${spring.application.name:}},%X{traceId:-},%X{spanId:-}] +management.tracing.propagation.type=w3c +management.tracing.baggage.enabled=true +management.tracing.enabled=true +management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans diff --git a/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/MainTest.java b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/MainTest.java new file mode 100644 index 000000000000..2b500440ebc0 --- /dev/null +++ b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/MainTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.payment.microservice; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +/** Application Context loads test */ +class MainTest { + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> Main.main(new String[] {})); + } +} diff --git a/priority-queue/src/test/java/com/iluwatar/priority/queue/QueueManagerTest.java b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/ProductControllerTest.java similarity index 61% rename from priority-queue/src/test/java/com/iluwatar/priority/queue/QueueManagerTest.java rename to microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/ProductControllerTest.java index c5f2a515b74b..7fbbf2c1a7b8 100644 --- a/priority-queue/src/test/java/com/iluwatar/priority/queue/QueueManagerTest.java +++ b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/ProductControllerTest.java @@ -22,34 +22,42 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.priority.queue; +package com.iluwatar.payment.microservice; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; -/** - * Check queue manager - */ -class QueueManagerTest { +/** Payment controller test. */ +class ProductControllerTest { + + private PaymentController paymentController; + + @BeforeEach + void setUp() { + paymentController = new PaymentController(); + } + /** Test to process the payment. */ @Test - void publishMessage() { - var queueManager = new QueueManager(2); - var testMessage = new Message("Test Message", 1); - queueManager.publishMessage(testMessage); - var recivedMessage = queueManager.receiveMessage(); - assertEquals(testMessage, recivedMessage); + void testValidateProduct() { + // Arrange + String request = "Sample payment process request"; + // Act + ResponseEntity response = paymentController.payment(request); + // Assert + assertEquals(ResponseEntity.ok(true), response); } + /** Test to process the payment with null request. */ @Test - void receiveMessage() { - var queueManager = new QueueManager(2); - var testMessage1 = new Message("Test Message 1", 1); - queueManager.publishMessage(testMessage1); - var testMessage2 = new Message("Test Message 2", 2); - queueManager.publishMessage(testMessage2); - var recivedMessage = queueManager.receiveMessage(); - assertEquals(testMessage2, recivedMessage); + void testValidateProductWithNullRequest() { + // Arrange + // Act + ResponseEntity response = paymentController.payment(null); + // Assert + assertEquals(ResponseEntity.ok(true), response); } -} \ No newline at end of file +} diff --git a/microservices-distributed-tracing/pom.xml b/microservices-distributed-tracing/pom.xml new file mode 100644 index 000000000000..1957766d694d --- /dev/null +++ b/microservices-distributed-tracing/pom.xml @@ -0,0 +1,73 @@ + + + + + java-design-patterns + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + microservices-distributed-tracing + pom + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-tracing-bridge-brave + 1.4.4 + compile + + + io.zipkin.reporter2 + zipkin-reporter-brave + 3.5.0 + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + order-microservice + payment-microservice + product-microservice + + diff --git a/microservices-distributed-tracing/product-microservice/etc/product-microservice.png b/microservices-distributed-tracing/product-microservice/etc/product-microservice.png new file mode 100644 index 000000000000..23410f3cd571 Binary files /dev/null and b/microservices-distributed-tracing/product-microservice/etc/product-microservice.png differ diff --git a/microservices-distributed-tracing/product-microservice/etc/product-microservice.puml b/microservices-distributed-tracing/product-microservice/etc/product-microservice.puml new file mode 100644 index 000000000000..25a1805ff4b4 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/etc/product-microservice.puml @@ -0,0 +1,10 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { +package "Product Microservice" { + class ProductController { + +validateProduct() + } +} +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/product-microservice/etc/product-microservice.urm.puml b/microservices-distributed-tracing/product-microservice/etc/product-microservice.urm.puml new file mode 100644 index 000000000000..25a1805ff4b4 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/etc/product-microservice.urm.puml @@ -0,0 +1,10 @@ +@startuml +!theme vibrant +package com.iluwatar.microservices-distributed-tracing { +package "Product Microservice" { + class ProductController { + +validateProduct() + } +} +} +@enduml \ No newline at end of file diff --git a/microservices-distributed-tracing/product-microservice/pom.xml b/microservices-distributed-tracing/product-microservice/pom.xml new file mode 100644 index 000000000000..987e0fca8a74 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/pom.xml @@ -0,0 +1,56 @@ + + + + + microservices-distributed-tracing + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + product-microservice + jar + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.product.com.iluwatar.product.microservice.microservice.Main + + + + + + + + + diff --git a/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/Main.java b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/Main.java new file mode 100644 index 000000000000..91e41631c190 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/Main.java @@ -0,0 +1,77 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.product.microservice.microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * With the Microservices pattern, a request often travels through multiple different microservices. + * Tracking the entire request flow across these services can be challenging, especially when trying + * to diagnose performance issues or failures. Distributed tracing addresses this challenge by + * providing end-to-end visibility into the lifecycle of a request as it passes through various + * microservices. + * + *

The intent of the Distributed Tracing pattern is to trace a request across different + * microservices, collecting detailed timing data and logs that help in understanding the flow, + * performance bottlenecks, and failure points in a distributed system. Each microservice involved + * in the request contributes to the tracing data, creating a comprehensive view of the request's + * journey. + * + *

This implementation demonstrates distributed tracing in a microservices architecture for an + * e-commerce platform. When a customer places an order, the OrderService interacts with both the + * PaymentService to process the payment and the ProductService to check the product inventory. + * Tracing logs are generated for each interaction, and these logs can be visualized using Zipkin. + * + *

To run Zipkin and view the tracing logs, you can use the following Docker command: + * + *

+ * {@code docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin }
+ * 
+ * + *

Start Zipkin with the command above. Once Zipkin is running, you can access the Zipkin UI at + * http://localhost:9411 to view the tracing logs and analyze + * the request flows across your microservices. + * + *

To place an order and generate tracing data, you can use the following curl command: + * + *

+ * {@code curl -X POST http://localhost:30300/order -H "Content-Type: application/json" -d '{"orderId": "123"}' }
+ * 
+ * + *

This command sends a POST request to create an order, which will trigger interactions with the + * payment and product microservices, generating tracing logs that can be viewed in Zipkin. + */ +@SpringBootApplication +public class Main { + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} diff --git a/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/ProductController.java b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/ProductController.java new file mode 100644 index 000000000000..cc4b21475cc1 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/ProductController.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.product.microservice.microservice; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** Controller for handling product validation requests. */ +@Slf4j +@RestController +public class ProductController { + + /** + * Validates the product based on the request. + * + * @param request the request body containing product information (can be null) + * @return ResponseEntity containing the validation result (true) + */ + @PostMapping("/product/validate") + public ResponseEntity validateProduct(@RequestBody(required = false) String request) { + LOGGER.info("Received product validation request: {}", request); + boolean result = true; + LOGGER.info("Product validation result: {}", result); + return ResponseEntity.ok(result); + } +} diff --git a/microservices-distributed-tracing/product-microservice/src/main/resources/application.properties b/microservices-distributed-tracing/product-microservice/src/main/resources/application.properties new file mode 100644 index 000000000000..72c7b3ed2772 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/src/main/resources/application.properties @@ -0,0 +1,33 @@ +# +# The MIT License +# Copyright © 2014-2021 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +spring.application.name=product-microservice +server.port=30302 + +management.tracing.sampling.probability=1 +logging.pattern.level=%5p [${spring.zipkin.service.name:${spring.application.name:}},%X{traceId:-},%X{spanId:-}] +management.tracing.propagation.type=w3c +management.tracing.baggage.enabled=true +management.tracing.enabled=true +management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans + diff --git a/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/MainTest.java b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/MainTest.java new file mode 100644 index 000000000000..1534943abd02 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/MainTest.java @@ -0,0 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.product.microservice; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.iluwatar.product.microservice.microservice.Main; +import org.junit.jupiter.api.Test; + +/** Application test */ +class MainTest { + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> Main.main(new String[] {})); + } +} diff --git a/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java new file mode 100644 index 000000000000..517a1b20b088 --- /dev/null +++ b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java @@ -0,0 +1,63 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.product.microservice; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.iluwatar.product.microservice.microservice.ProductController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +class ProductControllerTest { + + private ProductController productController; + + @BeforeEach + public void setUp() { + productController = new ProductController(); + } + + /** Test to validate the product. */ + @Test + void testValidateProduct() { + // Arrange + String request = "Sample product validation request"; + // Act + ResponseEntity response = productController.validateProduct(request); + // Assert + assertEquals(ResponseEntity.ok(true), response); + } + + /** Test to validate the product with null request. */ + @Test + void testValidateProductWithNullRequest() { + // Arrange + // Act + ResponseEntity response = productController.validateProduct(null); + // Assert + assertEquals(ResponseEntity.ok(true), response); + } +} diff --git a/microservices-idempotent-consumer/README.md b/microservices-idempotent-consumer/README.md new file mode 100644 index 000000000000..794eb6e644aa --- /dev/null +++ b/microservices-idempotent-consumer/README.md @@ -0,0 +1,161 @@ +--- +title: "Idempotent Consumer Pattern in Java: Ensuring Reliable Message Processing" +shortTitle: Idempotent Consumer +description: "Learn about the Idempotent Consumer pattern in Java. Discover how it ensures reliable and consistent message processing, even in cases of duplicate messages." +category: Messaging +language: en +tag: + - Messaging + - Fault tolerance + - Event-driven + - Reliability +--- + +## Also known as + +* Idempotency Pattern + +## Intent of Idempotent Consumer Pattern + +The Idempotent Consumer pattern is used to handle duplicate messages in distributed systems, ensuring that multiple processing of the same message does not cause undesired side effects. This pattern guarantees that the same message can be processed repeatedly with the same outcome, which is critical in ensuring reliable communication and data consistency in systems where message duplicates are possible. + +## Detailed Explanation of Idempotent Consumer Pattern with Real-World Examples + +### Real-world Example + +> In a payment processing system, ensuring that payment messages are idempotent prevents duplicate transactions. For example, if a user’s payment message is accidentally processed twice, the system should recognize the second message as a duplicate and prevent it from executing a second time. By storing unique identifiers for each processed message, such as a transaction ID, the system can skip any duplicate messages. This ensures that a user is not charged twice for the same transaction, maintaining system integrity and customer satisfaction. + +### In Plain Words + +> The Idempotent Consumer pattern prevents duplicate messages from causing unintended side effects by ensuring that processing the same message multiple times results in the same outcome. This makes message processing safe in distributed systems where duplicates may occur. + +### Wikipedia says + +> In computing, idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application. + +## When to Use the Idempotent Consumer Pattern + +The Idempotent Consumer pattern is particularly useful in scenarios: + +* When messages can be duplicated due to network retries or communication issues. +* In distributed systems where message ordering is not guaranteed, making deduplication necessary to avoid repeated processing. +* In financial or critical systems, where duplicate processing would have significant side effects. + +## Real-World Applications of Idempotent Consumer Pattern + +* Payment processing systems that avoid duplicate transactions. +* E-commerce systems to prevent multiple entries of the same order. +* Inventory management systems to prevent multiple entries when updating stock levels. + +## Programmatic example of Idempotent Consumer Pattern +In this Java example, we have an idempotent service that offers functionality to create and update (start, complete, etc.) orders. The service ensures that the **create order** operation is idempotent, meaning that performing it multiple times with the same order ID will lead to the same result without creating duplicates. For state transitions (such as starting or completing an order), the service enforces valid state changes and throws exceptions if an invalid transition is attempted. The state machine governs the valid order status transitions, ensuring that statuses progress in a defined and consistent sequence. +### RequestService - Managing Idempotent Order Operations +The `RequestService` class is responsible for handling the creation and state transitions of orders. The `create` method is designed to be idempotent, ensuring that it either returns an existing order or creates a new one without any side effects if invoked multiple times with the same order ID. +```java +public class RequestService { + // Idempotent: ensures that the same request is returned if it already exists + public Request create(UUID uuid) { + Optional optReq = requestRepository.findById(uuid); + if (!optReq.isEmpty()) { + return optReq.get(); // Return existing request + } + return requestRepository.save(new Request(uuid)); // Save and return new request + } + + public Request start(UUID uuid) { + Optional optReq = requestRepository.findById(uuid); + if (optReq.isEmpty()) { + throw new RequestNotFoundException(uuid); + } + return requestRepository.save(requestStateMachine.next(optReq.get(), Request.Status.STARTED)); + } + + public Request complete(UUID uuid) { + Optional optReq = requestRepository.findById(uuid); + if (optReq.isEmpty()) { + throw new RequestNotFoundException(uuid); + } + return requestRepository.save(requestStateMachine.next(optReq.get(), Request.Status.COMPLETED)); + } +} +``` +### RequestStateMachine - Managing Order Transitions +The `RequestStateMachine` ensures that state transitions occur in a valid order. It handles the progression of an order's status, ensuring the correct sequence (e.g., from `PENDING` to `STARTED` to `COMPLETED`). +```java +public class RequestStateMachine { + + public Request next(Request req, Request.Status nextStatus) { + String transitionStr = String.format("Transition: %s -> %s", req.getStatus(), nextStatus); + switch (nextStatus) { + case PENDING -> throw new InvalidNextStateException(transitionStr); + case STARTED -> { + if (Request.Status.PENDING.equals(req.getStatus())) { + return new Request(req.getUuid(), Request.Status.STARTED); // Valid transition + } + throw new InvalidNextStateException(transitionStr); // Invalid transition + } + case COMPLETED -> { + if (Request.Status.STARTED.equals(req.getStatus())) { + return new Request(req.getUuid(), Request.Status.COMPLETED); // Valid transition + } + throw new InvalidNextStateException(transitionStr); // Invalid transition + } + default -> throw new InvalidNextStateException(transitionStr); // Invalid status + } + } +} +``` +### Main Application - Running the Idempotent Consumer Example + +In the main application, we demonstrate how the `RequestService` can be used to perform idempotent operations. Whether the order creation or state transition is invoked once or multiple times, the result is consistent and does not produce unexpected side effects. + +```java +Request req = requestService.create(UUID.randomUUID()); +// Try creating the same Request again with the same UUID (idempotent operation) +requestService.create(req.getUuid()); +// Again, try creating the same Request (idempotent operation, no new Request should be created) +requestService.create(req.getUuid()); +LOGGER.info("Nb of requests : {}", requestRepository.count()); // 1, processRequest is idempotent +// Attempt to start the Request (the first valid transition) +req = requestService.start(req.getUuid()); +// Try to start the Request again, which should throw an exception since it's already started +try { + req = requestService.start(req.getUuid()); +} catch (InvalidNextStateException ex) { + // Log an error message when trying to start a request twice + LOGGER.error("Cannot start request twice!"); +} +// Complete the Request (valid transition from STARTED to COMPLETED) +req = requestService.complete(req.getUuid()); +// Log the final status of the Request to confirm it's been completed +LOGGER.info("Request: {}", req); +``` +Program output: +``` +19:01:54.382 INFO [main] com.iluwatar.idempotentconsumer.App : Nb of requests : 1 +19:01:54.395 ERROR [main] com.iluwatar.idempotentconsumer.App : Cannot start request twice! +19:01:54.399 INFO [main] com.iluwatar.idempotentconsumer.App : Request: Request(uuid=2d5521ef-6b6b-4003-9ade-81e381fe9a63, status=COMPLETED) +``` +## Benefits and Trade-offs of the Idempotent Consumer Pattern + +### Benefits + +* **Reliability**: Ensures that messages can be processed without unwanted side effects from duplicates. +* **Consistency**: Maintains data integrity by ensuring that duplicate messages do not cause redundant updates or actions. +* **Fault Tolerance**: Handles message retries gracefully, preventing them from causing errors. + +### Trade-offs + +* **State Management**: Requires storing processed message IDs, which can add memory overhead. +* **Complexity**: Implementing deduplication mechanisms can increase the complexity of the system. +* **Scalability**: In high-throughput systems, maintaining a large set of processed messages can impact performance and resource usage. + +## Related Patterns in Java + +* [Retry Pattern](https://java-design-patterns.com/patterns/retry/): Works well with the Idempotent Consumer pattern to handle failed messages. +* [Circuit Breaker Pattern](https://java-design-patterns.com/patterns/circuitbreaker/): Often used alongside idempotent consumers to prevent repeated failures from causing overload. + +## References and Credits + +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/4dznP2Y) +* [Designing Data-Intensive Applications](https://amzn.to/3UADv7Q) diff --git a/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml new file mode 100644 index 000000000000..43fe77181375 --- /dev/null +++ b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml @@ -0,0 +1,49 @@ +@startuml +package com.iluwatar.idempotentconsumer { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + + run(requestService : RequestService, requestRepository : RequestRepository) : CommandLineRunner + } + class Request { + - status : Status + - uuid : UUID + + Request() + + Request(uuid : UUID) + + Request(uuid : UUID, status : Status) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getStatus() : Status + + getUuid() : UUID + + hashCode() : int + + setStatus(status : Status) + + setUuid(uuid : UUID) + + toString() : String + } + ~enum Status { + + COMPLETED {static} + + PENDING {static} + + STARTED {static} + + valueOf(name : String) : Status {static} + + values() : Status[] {static} + } + interface RequestRepository { + } + class RequestService { + ~ requestRepository : RequestRepository + ~ requestStateMachine : RequestStateMachine + + RequestService(requestRepository : RequestRepository, requestStateMachine : RequestStateMachine) + + complete(uuid : UUID) : Request + + create(uuid : UUID) : Request + + start(uuid : UUID) : Request + } + class RequestStateMachine { + + RequestStateMachine() + + next(req : Request, nextStatus : Status) : Request + } +} +RequestService --> "-requestRepository" RequestRepository +Request --> "-status" Status +RequestService --> "-requestStateMachine" RequestStateMachine +@enduml \ No newline at end of file diff --git a/microservices-idempotent-consumer/pom.xml b/microservices-idempotent-consumer/pom.xml new file mode 100644 index 000000000000..665be3abef56 --- /dev/null +++ b/microservices-idempotent-consumer/pom.xml @@ -0,0 +1,116 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + microservices-idempotent-consumer + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + org.mockito + mockito-core + test + + + + + com.h2database + h2 + runtime + + + + org.hibernate + hibernate-core + 6.4.4.Final + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + jar-with-dependencies + + + + + com.iluwatar.idempotentconsumer.App + + + + + + + + + + + + diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/App.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/App.java new file mode 100644 index 000000000000..fe099eab415f --- /dev/null +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/App.java @@ -0,0 +1,75 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +/** + * The main entry point for the idempotent-consumer application. This application demonstrates the + * use of the Idempotent Consumer pattern which ensures that a message is processed exactly once in + * scenarios where the same message can be delivered multiple times. + * + * @see Idempotence (Wikipedia) + * @see Idempotent + * Consumer Pattern (Apache Camel) + */ +@SpringBootApplication +@Slf4j +public class App { + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } + + /** + * The starting point of the CommandLineRunner where the main program is run. + * + * @param requestService idempotent request service + * @param requestRepository request jpa repository + */ + @Bean + public CommandLineRunner run(RequestService requestService, RequestRepository requestRepository) { + return args -> { + Request req = requestService.create(UUID.randomUUID()); + requestService.create(req.getUuid()); + requestService.create(req.getUuid()); + LOGGER.info( + "Nb of requests : {}", requestRepository.count()); // 1, processRequest is idempotent + req = requestService.start(req.getUuid()); + try { + req = requestService.start(req.getUuid()); + } catch (InvalidNextStateException ex) { + LOGGER.error("Cannot start request twice!"); + } + req = requestService.complete(req.getUuid()); + LOGGER.info("Request: {}", req); + }; + } +} diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/InvalidNextStateException.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/InvalidNextStateException.java new file mode 100644 index 000000000000..e280d37a8a1f --- /dev/null +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/InvalidNextStateException.java @@ -0,0 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +/** + * This exception is thrown when an invalid transition is attempted in the Statemachine for the + * request status. This can occur when attempting to move to a state that is not valid from the + * current state. + */ +public class InvalidNextStateException extends RuntimeException { + public InvalidNextStateException(String s) { + super(s); + } +} diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/Request.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/Request.java new file mode 100644 index 000000000000..dfd68346bb7f --- /dev/null +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/Request.java @@ -0,0 +1,58 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import java.util.UUID; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * The {@code Request} class represents a request with a unique UUID and a status. The status of a + * request can be one of four values: PENDING, STARTED, COMPLETED, or INERROR. + */ +@Entity +@NoArgsConstructor +@Data +public class Request { + enum Status { + PENDING, + STARTED, + COMPLETED + } + + @Id private UUID uuid; + private Status status; + + public Request(UUID uuid) { + this(uuid, Status.PENDING); + } + + public Request(UUID uuid, Status status) { + this.uuid = uuid; + this.status = status; + } +} diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestNotFoundException.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestNotFoundException.java new file mode 100644 index 000000000000..aedc94ef7517 --- /dev/null +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestNotFoundException.java @@ -0,0 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import java.util.UUID; + +/** + * This class extends the RuntimeException class to handle scenarios where a Request is not found. + * It is intended to be used where you would like to have a custom exception that signals that a + * requested object or action was not found in the system, based on the UUID of the request. + */ +public class RequestNotFoundException extends RuntimeException { + RequestNotFoundException(UUID uuid) { + super(String.format("Request %s not found", uuid)); + } +} diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestRepository.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestRepository.java new file mode 100644 index 000000000000..e0b273e9fb5d --- /dev/null +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestRepository.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * This is a repository interface for the "Request" entity. It extends the JpaRepository interface + * from Spring Data JPA. JpaRepository comes with many operations out of the box, including standard + * CRUD operations. With JpaRepository, we are also able to leverage the power of Spring Data's + * query methods. The UUID parameter in JpaRepository refers to the type of the ID in the "Request" + * entity. + */ +@Repository +public interface RequestRepository extends JpaRepository {} diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java new file mode 100644 index 000000000000..7960533301e6 --- /dev/null +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java @@ -0,0 +1,90 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import java.util.Optional; +import java.util.UUID; +import org.springframework.stereotype.Service; + +/** + * This service is responsible for handling request operations including creation, start, and + * completion of requests. + */ +@Service +public class RequestService { + RequestRepository requestRepository; + RequestStateMachine requestStateMachine; + + public RequestService( + RequestRepository requestRepository, RequestStateMachine requestStateMachine) { + this.requestRepository = requestRepository; + this.requestStateMachine = requestStateMachine; + } + + /** + * Creates a new Request or returns an existing one by it's UUID. This operation is idempotent: + * performing it once or several times successively leads to an equivalent result. + * + * @param uuid The unique identifier for the Request. + * @return Return existing Request or save and return a new Request. + */ + public Request create(UUID uuid) { + Optional optReq = requestRepository.findById(uuid); + if (!optReq.isEmpty()) { + return optReq.get(); + } + return requestRepository.save(new Request(uuid)); + } + + /** + * Starts the Request assigned with the given UUID. + * + * @param uuid The unique identifier for the Request. + * @return The started Request. + * @throws RequestNotFoundException if a Request with the given UUID is not found. + */ + public Request start(UUID uuid) { + Optional optReq = requestRepository.findById(uuid); + if (optReq.isEmpty()) { + throw new RequestNotFoundException(uuid); + } + return requestRepository.save(requestStateMachine.next(optReq.get(), Request.Status.STARTED)); + } + + /** + * Complete the Request assigned with the given UUID. + * + * @param uuid The unique identifier for the Request. + * @return The completed Request. + * @throws RequestNotFoundException if a Request with the given UUID is not found. + */ + public Request complete(UUID uuid) { + Optional optReq = requestRepository.findById(uuid); + if (optReq.isEmpty()) { + throw new RequestNotFoundException(uuid); + } + return requestRepository.save(requestStateMachine.next(optReq.get(), Request.Status.COMPLETED)); + } +} diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestStateMachine.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestStateMachine.java new file mode 100644 index 000000000000..a67affdf1253 --- /dev/null +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestStateMachine.java @@ -0,0 +1,65 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import org.springframework.stereotype.Component; + +/** + * This class represents a state machine for managing request transitions. It supports transitions + * to the statuses: PENDING, STARTED, and COMPLETED. + */ +@Component +public class RequestStateMachine { + + /** + * Provides the next possible state of the request based on the current and next status. + * + * @param req The actual request object. This object MUST NOT be null and SHOULD have a valid + * status. + * @param nextStatus Represents the next status that the request could transition to. MUST NOT be + * null. + * @return A new Request object with updated status if the transition is valid. + * @throws InvalidNextStateException If an invalid state transition is attempted. + */ + public Request next(Request req, Request.Status nextStatus) { + String transitionStr = String.format("Transition: %s -> %s", req.getStatus(), nextStatus); + switch (nextStatus) { + case PENDING -> throw new InvalidNextStateException(transitionStr); + case STARTED -> { + if (Request.Status.PENDING.equals(req.getStatus())) { + return new Request(req.getUuid(), Request.Status.STARTED); + } + throw new InvalidNextStateException(transitionStr); + } + case COMPLETED -> { + if (Request.Status.STARTED.equals(req.getStatus())) { + return new Request(req.getUuid(), Request.Status.COMPLETED); + } + throw new InvalidNextStateException(transitionStr); + } + default -> throw new InvalidNextStateException(transitionStr); + } + } +} diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java new file mode 100644 index 000000000000..457febedadd7 --- /dev/null +++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java @@ -0,0 +1,67 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.CommandLineRunner; + +/** Application test */ +class AppTest { + + @Test + void main() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } + + @Test + void run() throws Exception { + RequestService requestService = Mockito.mock(RequestService.class); + RequestRepository requestRepository = Mockito.mock(RequestRepository.class); + UUID uuid = UUID.randomUUID(); + Request requestPending = new Request(uuid); + Request requestStarted = new Request(uuid, Request.Status.STARTED); + Request requestCompleted = new Request(uuid, Request.Status.COMPLETED); + when(requestService.create(any())).thenReturn(requestPending); + when(requestService.start(any())).thenReturn(requestStarted); + when(requestService.complete(any())).thenReturn(requestCompleted); + + CommandLineRunner runner = new App().run(requestService, requestRepository); + + runner.run(); + + verify(requestService, times(3)).create(any()); + verify(requestService, times(2)).start(any()); + verify(requestService, times(1)).complete(any()); + verify(requestRepository, times(1)).count(); + } +} diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java new file mode 100644 index 000000000000..f883709a92be --- /dev/null +++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java @@ -0,0 +1,137 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class RequestServiceTests { + private RequestService requestService; + @Mock private RequestRepository requestRepository; + private RequestStateMachine requestStateMachine; + + @BeforeEach + void setUp() { + requestStateMachine = new RequestStateMachine(); + requestService = new RequestService(requestRepository, requestStateMachine); + } + + @Test + void createRequest_whenNotExists() { + UUID uuid = UUID.randomUUID(); + Request request = new Request(uuid); + when(requestRepository.findById(any())).thenReturn(Optional.empty()); + when(requestRepository.save(request)).thenReturn(request); + assertEquals(request, requestService.create(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(1)).save(any()); + } + + @Test + void createRequest_whenExists() { + UUID uuid = UUID.randomUUID(); + Request request = new Request(uuid); + when(requestRepository.findById(any())).thenReturn(Optional.of(request)); + assertEquals(request, requestService.create(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(0)).save(any()); + } + + @Test + void startRequest_whenNotExists_shouldThrowError() { + UUID uuid = UUID.randomUUID(); + when(requestRepository.findById(any())).thenReturn(Optional.empty()); + assertThrows(RequestNotFoundException.class, () -> requestService.start(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(0)).save(any()); + } + + @Test + void startRequest_whenIsPending() { + UUID uuid = UUID.randomUUID(); + Request request = new Request(uuid); + Request startedEntity = new Request(uuid, Request.Status.STARTED); + when(requestRepository.findById(any())).thenReturn(Optional.of(request)); + when(requestRepository.save(any())).thenReturn(startedEntity); + assertEquals(startedEntity, requestService.start(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(1)).save(startedEntity); + } + + @Test + void startRequest_whenIsStarted_shouldThrowError() { + UUID uuid = UUID.randomUUID(); + Request requestStarted = new Request(uuid, Request.Status.STARTED); + when(requestRepository.findById(any())).thenReturn(Optional.of(requestStarted)); + assertThrows(InvalidNextStateException.class, () -> requestService.start(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(0)).save(any()); + } + + @Test + void startRequest_whenIsCompleted_shouldThrowError() { + UUID uuid = UUID.randomUUID(); + Request requestStarted = new Request(uuid, Request.Status.COMPLETED); + when(requestRepository.findById(any())).thenReturn(Optional.of(requestStarted)); + assertThrows(InvalidNextStateException.class, () -> requestService.start(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(0)).save(any()); + } + + @Test + void completeRequest_whenStarted() { + UUID uuid = UUID.randomUUID(); + Request request = new Request(uuid, Request.Status.STARTED); + Request completedEntity = new Request(uuid, Request.Status.COMPLETED); + when(requestRepository.findById(any())).thenReturn(Optional.of(request)); + when(requestRepository.save(any())).thenReturn(completedEntity); + assertEquals(completedEntity, requestService.complete(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(1)).save(completedEntity); + } + + @Test + void completeRequest_whenNotInprogress() { + UUID uuid = UUID.randomUUID(); + Request request = new Request(uuid); + when(requestRepository.findById(any())).thenReturn(Optional.of(request)); + assertThrows(InvalidNextStateException.class, () -> requestService.complete(uuid)); + verify(requestRepository, times(1)).findById(uuid); + verify(requestRepository, times(0)).save(any()); + } +} diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java new file mode 100644 index 000000000000..62837cbf5dec --- /dev/null +++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java @@ -0,0 +1,96 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.idempotentconsumer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class RequestStateMachineTests { + private RequestStateMachine requestStateMachine; + + @BeforeEach + public void setUp() { + requestStateMachine = new RequestStateMachine(); + } + + @Test + void transitionPendingToStarted() { + Request startedRequest = + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.PENDING), Request.Status.STARTED); + assertEquals(Request.Status.STARTED, startedRequest.getStatus()); + } + + @Test + void transitionAnyToPending_shouldThrowError() { + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.PENDING), Request.Status.PENDING)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.STARTED), Request.Status.PENDING)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), Request.Status.PENDING)); + } + + @Test + void transitionCompletedToAny_shouldThrowError() { + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), Request.Status.PENDING)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), Request.Status.STARTED)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), + Request.Status.COMPLETED)); + } + + @Test + void transitionStartedToCompleted() { + Request completedRequest = + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.STARTED), Request.Status.COMPLETED); + assertEquals(Request.Status.COMPLETED, completedRequest.getStatus()); + } +} diff --git a/microservices-log-aggregation/README.md b/microservices-log-aggregation/README.md new file mode 100644 index 000000000000..c23b1fe4319b --- /dev/null +++ b/microservices-log-aggregation/README.md @@ -0,0 +1,163 @@ +--- +title: "Microservices Log Aggregation Pattern in Java: Centralizing Logs for Enhanced Monitoring" +shortTitle: Microservices Log Aggregation +description: "Learn about the Microservices Log Aggregation pattern, a method for centralizing log collection and analysis to enhance monitoring, debugging, and operational intelligence in distributed systems." +category: Integration +language: en +tag: + - Data processing + - Decoupling + - Enterprise patterns + - Fault tolerance + - Messaging + - Microservices + - Performance + - Scalability +--- + +## Also known as + +* Centralized Logging +* Log Management + +## Intent of Microservices Log Aggregation Design Pattern + +Log Aggregation is a crucial microservices design pattern that centralizes the collection, storage, and analysis of logs from multiple sources, facilitating efficient monitoring, debugging, and operational intelligence. + +## Detailed Explanation of Microservices Log Aggregation Pattern with Real-World Examples + +Real-world example + +> Imagine an e-commerce platform using a microservices architecture, where each service generates logs. A log aggregation system, utilizing tools like the ELK Stack (Elasticsearch, Logstash, Kibana), centralizes these logs. This setup allows administrators to effectively monitor and analyze the entire platform's activity in real-time. By collecting logs from each microservice and centralizing them, the system provides a unified view, enabling quick troubleshooting and comprehensive analysis of user behavior and system performance. + +In plain words + +> The Log Aggregation design pattern centralizes the collection and analysis of log data from multiple applications or services to simplify monitoring and troubleshooting. + +Wikipedia says + +> You have applied the Microservice architecture pattern. The application consists of multiple services and service instances that are running on multiple machines. Requests often span multiple service instances. Each service instance generates writes information about what it is doing to a log file in a standardized format. The log file contains errors, warnings, information and debug messages. + +## Programmatic Example of Microservices Log Aggregation Pattern in Java + +Log Aggregation is a pattern that centralizes the collection, storage, and analysis of logs from multiple sources to facilitate monitoring, debugging, and operational intelligence. It is particularly useful in distributed systems where logs from various components need to be centralized for better management and analysis. + +In this example, we will demonstrate the Log Aggregation pattern using a simple Java application. The application consists of multiple services that generate logs. These logs are collected by a log aggregator and stored in a central log store. + +The `CentralLogStore` is responsible for storing the logs collected from various services. In this example, we are using an in-memory store for simplicity. + +```java +public class CentralLogStore { + + private final List logs = new ArrayList<>(); + + public void storeLog(LogEntry logEntry) { + logs.add(logEntry); + } + + public void displayLogs() { + logs.forEach(System.out::println); + } +} +``` + +The `LogAggregator` collects logs from various services and stores them in the `CentralLogStore`. It filters logs based on their log level. + +```java +public class LogAggregator { + + private final CentralLogStore centralLogStore; + private final LogLevel minimumLogLevel; + + public LogAggregator(CentralLogStore centralLogStore, LogLevel minimumLogLevel) { + this.centralLogStore = centralLogStore; + this.minimumLogLevel = minimumLogLevel; + } + + public void collectLog(LogEntry logEntry) { + if (logEntry.getLogLevel().compareTo(minimumLogLevel) >= 0) { + centralLogStore.storeLog(logEntry); + } + } +} +``` + +The `LogProducer` represents a service that generates logs. It sends the logs to the `LogAggregator`. + +```java +public class LogProducer { + + private final String serviceName; + private final LogAggregator logAggregator; + + public LogProducer(String serviceName, LogAggregator logAggregator) { + this.serviceName = serviceName; + this.logAggregator = logAggregator; + } + + public void generateLog(LogLevel logLevel, String message) { + LogEntry logEntry = new LogEntry(serviceName, logLevel, message, LocalDateTime.now()); + logAggregator.collectLog(logEntry); + } +} +``` + +The `main` application creates services, generates logs, aggregates, and finally displays the logs. + +```java +public class App { + + public static void main(String[] args) throws InterruptedException { + final CentralLogStore centralLogStore = new CentralLogStore(); + final LogAggregator aggregator = new LogAggregator(centralLogStore, LogLevel.INFO); + + final LogProducer serviceA = new LogProducer("ServiceA", aggregator); + final LogProducer serviceB = new LogProducer("ServiceB", aggregator); + + serviceA.generateLog(LogLevel.INFO, "This is an INFO log from ServiceA"); + serviceB.generateLog(LogLevel.ERROR, "This is an ERROR log from ServiceB"); + serviceA.generateLog(LogLevel.DEBUG, "This is a DEBUG log from ServiceA"); + + centralLogStore.displayLogs(); + } +} +``` + +In this example, the `LogProducer` services generate logs of different levels. The `LogAggregator` collects these logs and stores them in the `CentralLogStore` if they meet the minimum log level requirement. Finally, the logs are displayed by the `CentralLogStore`. + +## When to Use the Microservices Log Aggregation Pattern in Java + +* Microservices log aggregation is essential in distributed systems for better management and analysis of log data. +* Applicable in environments where compliance and auditing require consolidated log data. +* Beneficial in systems that require high availability and resilience, ensuring that log data is preserved and accessible despite individual component failures. + +## Real-World Applications of Microservices Log Aggregation Pattern in Java + +* ava applications using frameworks like Log4j2 or SLF4J with centralized log management tools such as the ELK stack or Splunk benefit from microservices log aggregation. +* Microservices architectures where each service outputs logs that are aggregated into a single system to provide a unified view of the system’s health and behavior. + +## Benefits and Trade-offs of Microservices Log Aggregation Pattern + +Benefits: + +* Centralizing logs in a microservices environment improves debuggability and traceability across multiple services. +* Enhances monitoring capabilities by providing a centralized platform for log analysis. +* Facilitates compliance with regulatory requirements for log retention and auditability. + +Trade-offs: + +* Introduces a potential single point of failure if the log aggregation system is not adequately resilient. +* Can lead to high data volumes requiring significant storage and processing resources. + +## Related Java Design Patterns + +* Messaging Patterns: Log Aggregation often utilizes messaging systems to transport log data, facilitating decoupling and asynchronous data processing. +* Microservices: Often employed in microservice architectures to handle logs from various services efficiently. +* Publish/Subscribe: Utilizes a pub/sub model for log data collection where components publish logs and the aggregation system subscribes to them. + +## References and Credits + +* [Cloud Native Java: Designing Resilient Systems with Spring Boot, Spring Cloud, and Cloud Foundry](https://amzn.to/44vDTat) +* [Logging in Action: With Fluentd, Kubernetes and more](https://amzn.to/3JQLzdT) +* [Release It! Design and Deploy Production-Ready Software](https://amzn.to/3Uul4kF) +* [Pattern: Log aggregation (microservices.io)](https://microservices.io/patterns/observability/application-logging.html) diff --git a/log-aggregation/etc/log-aggregation.png b/microservices-log-aggregation/etc/log-aggregation.png similarity index 100% rename from log-aggregation/etc/log-aggregation.png rename to microservices-log-aggregation/etc/log-aggregation.png diff --git a/log-aggregation/etc/log-aggregation.puml b/microservices-log-aggregation/etc/log-aggregation.puml similarity index 100% rename from log-aggregation/etc/log-aggregation.puml rename to microservices-log-aggregation/etc/log-aggregation.puml diff --git a/microservices-log-aggregation/etc/log-aggregation.urm.puml b/microservices-log-aggregation/etc/log-aggregation.urm.puml new file mode 100644 index 000000000000..1d4551ed025f --- /dev/null +++ b/microservices-log-aggregation/etc/log-aggregation.urm.puml @@ -0,0 +1,68 @@ +@startuml +package com.iluwatar.logaggregation { + class App { + + App() + + main(args : String[]) {static} + } + class CentralLogStore { + - LOGGER : Logger {static} + - logs : ConcurrentLinkedQueue + + CentralLogStore() + + displayLogs() + + storeLog(logEntry : LogEntry) + } + class LogAggregator { + - BUFFER_THRESHOLD : int {static} + - LOGGER : Logger {static} + - buffer : ConcurrentLinkedQueue + - centralLogStore : CentralLogStore + - executorService : ExecutorService + - logCount : AtomicInteger + - minLogLevel : LogLevel + + LogAggregator(centralLogStore : CentralLogStore, minLogLevel : LogLevel) + + collectLog(logEntry : LogEntry) + - flushBuffer() + - startBufferFlusher() + + stop() + } + class LogEntry { + - level : LogLevel + - message : String + - serviceName : String + - timestamp : LocalDateTime + + LogEntry(serviceName : String, level : LogLevel, message : String, timestamp : LocalDateTime) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getLevel() : LogLevel + + getMessage() : String + + getServiceName() : String + + getTimestamp() : LocalDateTime + + hashCode() : int + + setLevel(level : LogLevel) + + setMessage(message : String) + + setServiceName(serviceName : String) + + setTimestamp(timestamp : LocalDateTime) + + toString() : String + } + enum LogLevel { + + DEBUG {static} + + ERROR {static} + + INFO {static} + + valueOf(name : String) : LogLevel {static} + + values() : LogLevel[] {static} + } + class LogProducer { + - LOGGER : Logger {static} + - aggregator : LogAggregator + - serviceName : String + + LogProducer(serviceName : String, aggregator : LogAggregator) + + generateLog(level : LogLevel, message : String) + } +} +LogAggregator --> "-centralLogStore" CentralLogStore +LogEntry --> "-level" LogLevel +CentralLogStore --> "-logs" LogEntry +LogAggregator --> "-buffer" LogEntry +LogAggregator --> "-minLogLevel" LogLevel +LogProducer --> "-aggregator" LogAggregator +@enduml \ No newline at end of file diff --git a/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml b/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml new file mode 100644 index 000000000000..1d4551ed025f --- /dev/null +++ b/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml @@ -0,0 +1,68 @@ +@startuml +package com.iluwatar.logaggregation { + class App { + + App() + + main(args : String[]) {static} + } + class CentralLogStore { + - LOGGER : Logger {static} + - logs : ConcurrentLinkedQueue + + CentralLogStore() + + displayLogs() + + storeLog(logEntry : LogEntry) + } + class LogAggregator { + - BUFFER_THRESHOLD : int {static} + - LOGGER : Logger {static} + - buffer : ConcurrentLinkedQueue + - centralLogStore : CentralLogStore + - executorService : ExecutorService + - logCount : AtomicInteger + - minLogLevel : LogLevel + + LogAggregator(centralLogStore : CentralLogStore, minLogLevel : LogLevel) + + collectLog(logEntry : LogEntry) + - flushBuffer() + - startBufferFlusher() + + stop() + } + class LogEntry { + - level : LogLevel + - message : String + - serviceName : String + - timestamp : LocalDateTime + + LogEntry(serviceName : String, level : LogLevel, message : String, timestamp : LocalDateTime) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getLevel() : LogLevel + + getMessage() : String + + getServiceName() : String + + getTimestamp() : LocalDateTime + + hashCode() : int + + setLevel(level : LogLevel) + + setMessage(message : String) + + setServiceName(serviceName : String) + + setTimestamp(timestamp : LocalDateTime) + + toString() : String + } + enum LogLevel { + + DEBUG {static} + + ERROR {static} + + INFO {static} + + valueOf(name : String) : LogLevel {static} + + values() : LogLevel[] {static} + } + class LogProducer { + - LOGGER : Logger {static} + - aggregator : LogAggregator + - serviceName : String + + LogProducer(serviceName : String, aggregator : LogAggregator) + + generateLog(level : LogLevel, message : String) + } +} +LogAggregator --> "-centralLogStore" CentralLogStore +LogEntry --> "-level" LogLevel +CentralLogStore --> "-logs" LogEntry +LogAggregator --> "-buffer" LogEntry +LogAggregator --> "-minLogLevel" LogLevel +LogProducer --> "-aggregator" LogAggregator +@enduml \ No newline at end of file diff --git a/microservices-log-aggregation/pom.xml b/microservices-log-aggregation/pom.xml new file mode 100644 index 000000000000..fd5548661d3d --- /dev/null +++ b/microservices-log-aggregation/pom.xml @@ -0,0 +1,80 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + microservices-log-aggregation + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-junit-jupiter + 5.16.1 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.logaggregation.App + + + + + + + + + diff --git a/log-aggregation/src/main/java/com/iluwatar/logaggregation/App.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/App.java similarity index 98% rename from log-aggregation/src/main/java/com/iluwatar/logaggregation/App.java rename to microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/App.java index ef9ca0a7ba87..080dc6763136 100644 --- a/log-aggregation/src/main/java/com/iluwatar/logaggregation/App.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/App.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java similarity index 88% rename from log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java rename to microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java index f7226638c98d..3f525fa9e65f 100644 --- a/log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,9 +28,9 @@ import lombok.extern.slf4j.Slf4j; /** - * A centralized store for logs. It collects logs from various services and stores them. - * This class is thread-safe, ensuring that logs from different services are safely stored - * concurrently without data races. + * A centralized store for logs. It collects logs from various services and stores them. This class + * is thread-safe, ensuring that logs from different services are safely stored concurrently without + * data races. */ @Slf4j public class CentralLogStore { @@ -50,9 +50,7 @@ public void storeLog(LogEntry logEntry) { logs.offer(logEntry); } - /** - * Displays all logs currently stored in the central log store. - */ + /** Displays all logs currently stored in the central log store. */ public void displayLogs() { LOGGER.info("----- Centralized Logs -----"); for (LogEntry logEntry : logs) { diff --git a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java similarity index 83% rename from log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java rename to microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java index 406ce144a03d..0acdc9fedc62 100644 --- a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,11 +32,10 @@ import lombok.extern.slf4j.Slf4j; /** - * Responsible for collecting and buffering logs from different services. - * Once the logs reach a certain threshold or after a certain time interval, - * they are flushed to the central log store. This class ensures logs are collected - * and processed asynchronously and efficiently, providing both an immediate collection - * and periodic flushing. + * Responsible for collecting and buffering logs from different services. Once the logs reach a + * certain threshold or after a certain time interval, they are flushed to the central log store. + * This class ensures logs are collected and processed asynchronously and efficiently, providing + * both an immediate collection and periodic flushing. */ @Slf4j public class LogAggregator { @@ -84,8 +83,7 @@ public void collectLog(LogEntry logEntry) { } /** - * Stops the log aggregator service and flushes any remaining logs to - * the central log store. + * Stops the log aggregator service and flushes any remaining logs to the central log store. * * @throws InterruptedException If any thread has interrupted the current thread. */ @@ -106,15 +104,16 @@ private void flushBuffer() { } private void startBufferFlusher() { - executorService.execute(() -> { - while (!Thread.currentThread().isInterrupted()) { - try { - Thread.sleep(5000); // Flush every 5 seconds. - flushBuffer(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - }); + executorService.execute( + () -> { + while (!Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(5000); // Flush every 5 seconds. + flushBuffer(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); } } diff --git a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java similarity index 92% rename from log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java rename to microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java index bece65c64d21..93fe30d7c246 100644 --- a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,8 +29,8 @@ import lombok.Data; /** - * Represents a single log entry, capturing essential details like the service name, - * log level, message, and the timestamp when the log was generated. + * Represents a single log entry, capturing essential details like the service name, log level, + * message, and the timestamp when the log was generated. */ @Data @AllArgsConstructor diff --git a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java similarity index 83% rename from log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java rename to microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java index ed9258649189..da6c69afb179 100644 --- a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,14 +25,17 @@ package com.iluwatar.logaggregation; /** - * Enum representing different log levels. - * Defines the severity of a log message, helping in filtering and prioritization. + * Enum representing different log levels. Defines the severity of a log message, helping in + * filtering and prioritization. + * *

    - *
  • DEBUG: Detailed information, typically of interest only when diagnosing problems.
  • - *
  • INFO: Confirmation that things are working as expected.
  • - *
  • ERROR: Indicates a problem that needs attention.
  • + *
  • DEBUG: Detailed information, typically of interest only when diagnosing problems. + *
  • INFO: Confirmation that things are working as expected. + *
  • ERROR: Indicates a problem that needs attention. *
*/ public enum LogLevel { - DEBUG, INFO, ERROR + DEBUG, + INFO, + ERROR } diff --git a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java similarity index 88% rename from log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java rename to microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java index f6ca7f0ded12..a1d3f4c71ad9 100644 --- a/log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,9 +29,9 @@ import lombok.extern.slf4j.Slf4j; /** - * Represents a service that produces logs. - * The logs are generated based on certain activities or events within the service. - * Once a log is generated, it's passed on to the aggregator for further processing. + * Represents a service that produces logs. The logs are generated based on certain activities or + * events within the service. Once a log is generated, it's passed on to the aggregator for further + * processing. */ @AllArgsConstructor @Slf4j diff --git a/log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java b/microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java similarity index 96% rename from log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java rename to microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java index 182e7252769f..219bb4c48fad 100644 --- a/log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java +++ b/microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java @@ -2,7 +2,7 @@ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License - * Copyright © 2014-2023 Ilkka Seppälä + * Copyright © 2014-2022 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -38,8 +38,7 @@ @ExtendWith(MockitoExtension.class) class LogAggregatorTest { - @Mock - private CentralLogStore centralLogStore; + @Mock private CentralLogStore centralLogStore; private LogAggregator logAggregator; @BeforeEach diff --git a/model-view-controller/README.md b/model-view-controller/README.md index 0256bab91d7a..536cb2ca4a2a 100644 --- a/model-view-controller/README.md +++ b/model-view-controller/README.md @@ -1,24 +1,30 @@ --- -title: Model-View-Controller +title: "Model-View-Controller Pattern in Java: Streamlining Java Web Development" +shortTitle: Model-View-Controller (MVC) +description: "Learn about the Model-View-Controller (MVC) design pattern in Java, including its benefits, real-world examples, use cases, and how to implement it effectively in your applications." category: Architectural language: en tag: - - Decoupling + - Architecture + - Client-server + - Decoupling + - Layered architecture + - Presentation --- -## Intent -Separate the user interface into three interconnected components: -the model, the view and the controller. Let the model manage the data, the view -display the data and the controller mediate updating the data and redrawing the -display. +## Also known as -## Explanation +* MVC + +## Intent of Model-View-Controller Design Pattern + +To separate an application into three interconnected components (Model, View, Controller), enabling modular development of each part independently, enhancing maintainability and scalability. Model-View-Controller (MVC) design pattern is widely used in Java applications for web development and user interface separation. + +## Detailed Explanation of Model-View-Controller Pattern with Real-World Examples Real-world example -> Consider ICU room in hospital which displays the patients health information on device displays which -> are taking input from sensors connected to patient. Here, display's job is to display the data that -> it receives from the controller which in turn gets update from sensor model. +> Consider ICU room in a hospital displaying patient health information on devices taking input from sensors. The display shows data received from the controller, which updates from the sensor model. This exemplifies the MVC design pattern in a real-world Java application. In plain words @@ -26,54 +32,29 @@ In plain words Wikipedia says -> Model–view–controller (MVC) is commonly used for developing user interfaces that divide the -> related program logic into three interconnected elements. This is done to separate internal -> representations of information from the ways information is presented to and accepted from the user. +> Model–view–controller (MVC) is commonly used for developing user interfaces that divide the related program logic into three interconnected elements. This is done to separate internal representations of information from the ways information is presented to and accepted from the user. + +Architecture diagram + +![Model-View-Controller Architecture Diagram](./etc/mvc-architecture-diagram.png) -**Programmatic Example** + +## Programmatic Example of Model-View-Controller Pattern in Java Consider following `GiantModel` model class that provides the health, fatigue & nourishment information. ```java +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor public class GiantModel { private Health health; private Fatigue fatigue; private Nourishment nourishment; - /** - * Instantiates a new GiantModel. - */ - public GiantModel(Health health, Fatigue fatigue, Nourishment nourishment) { - this.health = health; - this.fatigue = fatigue; - this.nourishment = nourishment; - } - - public Health getHealth() { - return health; - } - - public void setHealth(Health health) { - this.health = health; - } - - public Fatigue getFatigue() { - return fatigue; - } - - public void setFatigue(Fatigue fatigue) { - this.fatigue = fatigue; - } - - public Nourishment getNourishment() { - return nourishment; - } - - public void setNourishment(Nourishment nourishment) { - this.nourishment = nourishment; - } - @Override public String toString() { return String.format("The giant looks %s, %s and %s.", health, fatigue, nourishment); @@ -92,7 +73,7 @@ public class GiantView { } ``` -And `GiantController` class that takes updates from `GiantModel` & sends to `GiantView` for display. +`GiantController` class takes updates from `GiantModel` and sends to `GiantView` for display. ```java public class GiantController { @@ -138,23 +119,48 @@ public class GiantController { } ``` -## Class diagram -![alt text](./etc/model-view-controller.png "Model-View-Controller") +This example demonstrates how the MVC pattern separates concerns in a Java application, making it easier to manage and update components independently. + +## When to Use the Model-View-Controller Pattern in Java + +* Used in web applications to separate data model, user interface, and user input processing. +* Suitable for applications requiring a clear separation of concerns, ensuring that the business logic, user interface, and user input are loosely coupled and independently managed, following the MVC pattern. + +## Model-View-Controller Pattern Java Tutorials + +* [Spring Boot Model (ZetCode)](https://zetcode.com/springboot/model/) +* [Spring MVC Tutorial (Baeldung)](https://www.baeldung.com/spring-mvc-tutorial) + +## Real-World Applications of Model-View-Controller Pattern in Java + +* Frameworks like Spring MVC in Java for web applications. +* Desktop applications in Java, such as those using Swing or JavaFX. + +## Benefits and Trade-offs of Model-View-Controller Pattern + +Benefits: + +* Promotes organized code structure by separating concerns. +* Facilitates parallel development of components. +* Enhances testability due to decoupled nature. +* Easier to manage and update individual parts without affecting others. -## Applicability -Use the Model-View-Controller pattern when +Trade-offs: -* You want to clearly separate the domain data from its user interface representation +* Increased complexity in initially setting up the architecture. +* Can lead to excessive boilerplate if not implemented correctly or for very small projects. -## Tutorials +## Related Java Design Patterns -* [Spring Boot MVC](https://zetcode.com/springboot/model/) -* [Spring MVC Tutorial](https://www.baeldung.com/spring-mvc-tutorial) +* [Observer](https://java-design-patterns.com/patterns/observer/): Often used in MVC where the view observes the model for changes; this is a fundamental relationship for updating the UI when the model state changes. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Controllers may use different strategies for handling user input, related through the ability to switch strategies for user input processing in Java MVC applications. +* [Composite](https://java-design-patterns.com/patterns/composite/): Views can be structured using the Composite Pattern to manage hierarchies of user interface components. -## Credits +## References and Credits -* [Trygve Reenskaug - Model-view-controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Pro Spring 5: An In-Depth Guide to the Spring Framework and Its Tools](https://amzn.to/3y9Rrwp) +* [Model-view-controller (Wikipedia)](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) diff --git a/model-view-controller/etc/mvc-architecture-diagram.png b/model-view-controller/etc/mvc-architecture-diagram.png new file mode 100644 index 000000000000..33371d41f22c Binary files /dev/null and b/model-view-controller/etc/mvc-architecture-diagram.png differ diff --git a/model-view-controller/pom.xml b/model-view-controller/pom.xml index 78072af4455e..807596459140 100644 --- a/model-view-controller/pom.xml +++ b/model-view-controller/pom.xml @@ -34,6 +34,14 @@ model-view-controller + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java index 00cced2407a2..a4a3c69cbb4f 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * Fatigue enumeration. - */ +/** Fatigue enumeration. */ @AllArgsConstructor public enum Fatigue { ALERT("alert"), @@ -37,7 +35,6 @@ public enum Fatigue { private final String title; - @Override public String toString() { return title; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java index 35881fbff83d..5fb8f1acab14 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java @@ -24,9 +24,7 @@ */ package com.iluwatar.model.view.controller; -/** - * GiantController can update the giant data and redraw it using the view. - */ +/** GiantController can update the giant data and redraw it using the view. */ public class GiantController { private final GiantModel giant; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java index 3de47cdcba6d..8be7e6e4e47c 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; -/** - * GiantModel contains the giant data. - */ +/** GiantModel contains the giant data. */ @Getter @Setter @Builder @@ -44,7 +42,6 @@ public class GiantModel { private Fatigue fatigue; private Nourishment nourishment; - @Override public String toString() { return String.format("The giant looks %s, %s and %s.", health, fatigue, nourishment); diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java index b73204d1dd38..a9696d898c78 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * GiantView displays the giant. - */ +/** GiantView displays the giant. */ @Slf4j public class GiantView { diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java index 0f54f5cd3825..7fba2f54f43e 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * Health enumeration. - */ +/** Health enumeration. */ @AllArgsConstructor public enum Health { HEALTHY("healthy"), @@ -37,7 +35,6 @@ public enum Health { private final String title; - @Override public String toString() { return title; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java index f08a9c5bced3..d385588bbd2d 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * Nourishment enumeration. - */ +/** Nourishment enumeration. */ @AllArgsConstructor public enum Nourishment { SATURATED("saturated"), @@ -37,7 +35,6 @@ public enum Nourishment { private final String title; - @Override public String toString() { return title; diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java index 8b44e47d2f38..b435b393061d 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.model.view.controller; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java index 1f9ea0420721..9dc9f2807e74 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java @@ -30,16 +30,10 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/20/15 - 2:19 PM - * - * @author Jeroen Meulemeester - */ +/** GiantControllerTest */ class GiantControllerTest { - /** - * Verify if the controller passes the health level through to the model and vice versa - */ + /** Verify if the controller passes the health level through to the model and vice versa */ @Test void testSetHealth() { final var model = mock(GiantModel.class); @@ -61,9 +55,7 @@ void testSetHealth() { verifyNoMoreInteractions(model, view); } - /** - * Verify if the controller passes the fatigue level through to the model and vice versa - */ + /** Verify if the controller passes the fatigue level through to the model and vice versa */ @Test void testSetFatigue() { final var model = mock(GiantModel.class); @@ -85,9 +77,7 @@ void testSetFatigue() { verifyNoMoreInteractions(model, view); } - /** - * Verify if the controller passes the nourishment level through to the model and vice versa - */ + /** Verify if the controller passes the nourishment level through to the model and vice versa */ @Test void testSetNourishment() { final var model = mock(GiantModel.class); @@ -122,5 +112,4 @@ void testUpdateView() { verifyNoMoreInteractions(model, view); } - -} \ No newline at end of file +} diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java index 74113c2770db..ed9eefc4ea62 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java @@ -28,16 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/20/15 - 2:10 PM - * - * @author Jeroen Meulemeester - */ +/** GiantModelTest */ class GiantModelTest { - /** - * Verify if the health value is set properly though the constructor and setter - */ + /** Verify if the health value is set properly though the constructor and setter */ @Test void testSetHealth() { final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); @@ -50,9 +44,7 @@ void testSetHealth() { } } - /** - * Verify if the fatigue level is set properly though the constructor and setter - */ + /** Verify if the fatigue level is set properly though the constructor and setter */ @Test void testSetFatigue() { final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); @@ -65,9 +57,7 @@ void testSetFatigue() { } } - /** - * Verify if the nourishment level is set properly though the constructor and setter - */ + /** Verify if the nourishment level is set properly though the constructor and setter */ @Test void testSetNourishment() { final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); @@ -79,5 +69,4 @@ void testSetNourishment() { assertEquals(String.format(messageFormat, nourishment), model.toString()); } } - } diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java index 58bfbe3a1ede..da2032b7fd94 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java @@ -37,11 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/20/15 - 2:04 PM - * - * @author Jeroen Meulemeester - */ +/** GiantViewTest */ class GiantViewTest { private InMemoryAppender appender; @@ -71,9 +67,7 @@ void testDisplayGiant() { assertEquals(1, appender.getLogSize()); } - /** - * Logging Appender Implementation - */ + /** Logging Appender Implementation */ public static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/model-view-intent/README.md b/model-view-intent/README.md index 00b0d29ea473..1febe76b0327 100644 --- a/model-view-intent/README.md +++ b/model-view-intent/README.md @@ -1,245 +1,230 @@ --- -title: Model-View-Intent +title: "Model-View-Intent Pattern in Java: Building Robust and Scalable Java UIs with MVI" +shortTitle: Model-View-Intent (MVI) +description: "Discover the Model-View-Intent (MVI) pattern for Java applications. Learn how MVI enhances UI predictability, maintainability, and state management through unidirectional data flow. Explore real-world examples and implementation details." category: Architectural language: en tags: - - Decoupling - - Encapsulation + - Abstraction + - Decoupling + - Presentation + - Reactive + - State tracking --- -## Intent -MVI is a derivation of the original MVC architectural pattern. Instead of working with a -proactive controller MVI works with the reactive component called intent: it's a component -which translates user input events into model updates. +## Also known as -## Explanation +* MVI -> MVI is a Reactive Architecture Pattern which is short for Model -View-Intent. -It introduces two new concepts: the intent and the state. -UI might have different states — Loading State, Fetch Data State, Error State, -and user events are submitted in the form of an Intent. +## Intent of Model-View-Intent Design Pattern -* [Stateful Android Apps With MVI (MODEL — VIEW — INTENT)](https://medium.com/huawei-developers/stateful-android-apps-with-mvi-architecture-model-view-intent-d106b09bd967) +The Model-View-Intent (MVI) pattern for Java applications creates a unidirectional and cyclical data flow between the Model, View, and Intent components, enhancing UI predictability and state management. -## Class diagram -![alt text](./etc/model-view-intent.png "Model-View-Intent") +## Detailed Explanation of Model-View-Intent Pattern with Real-World Examples -**Programmatic Example** +Real-world example + +> Consider a real-world analogy of the Model-View-Intent (MVI) pattern in Java using the scenario of ordering at a fast-food restaurant to understand its application in enhancing UI state management. +> +> In this analogy: +> - **Model:** It's like the kitchen of the restaurant, where the current state of your order is managed. As you choose items, the kitchen updates the order status and ingredients used. +> - **View:** This represents the menu and the digital display board where you see your current order summary. It reflects the current state of your order, showing what items you've added or removed. +> - **Intent:** Think of this as your decision-making process when you interact with the menu. Each choice you make (like adding a burger or removing a drink) sends a specific intention to the system (kitchen). +> +> When you decide to add an item to your order (Intent), the kitchen (Model) processes this request, updates the state of your order, and then the display (View) updates to show the latest status of your order. This cycle continues until you finalize your order, demonstrating the unidirectional and cyclical flow characteristic of MVI. This ensures that every change in the order is predictably and accurately reflected in the customer's view, similar to how UI components update in response to state changes in software using MVI. + +In plain words + +> The Model-View-Intent (MVI) pattern is a reactive architectural approach where user actions (Intent) modify the application state (Model), and the updated state is then reflected back in the user interface (View) in a unidirectional and cyclical data flow. + +Architecture diagram + +![Model-View-Intent Architecture Diagram](./etc/mvi-architecture-diagram.png) + + +## Programmatic Example of Model-View-Intent Pattern in Java + +The Model-View-Intent (MVI) pattern for Java is a modern approach to structuring your application's logic, ensuring a smooth, unidirectional flow of data and events. It's a variation of the Model-View-Presenter (MVP) and Model-View-ViewModel (MVVM) patterns, but with a more streamlined flow of data and events. + +In MVI, the View sends user events to the Intent. The Intent translates these events into a state change in the Model. The Model then pushes this new state to the View, which updates itself accordingly. This creates a unidirectional data flow, which can make your code easier to understand and debug. + +First, we have the `App` class, which serves as the entry point for the application. It creates the View and ViewModel, and then simulates a series of user interactions with the calculator. -CalculatorAction defines our Intent in MVI for user interactions. It has to be an interface -instead of enum, so that we can pass parameters to certain children. ```java -public interface CalculatorAction { - - /** - * Makes identifying action trivial. - * - * @return subclass tag. - * */ - String tag(); +public final class App { + private static final double RANDOM_VARIABLE = 10.0; + + public static void main(final String[] args) { + var view = new CalculatorView(new CalculatorViewModel()); + var variable1 = RANDOM_VARIABLE; + + view.setVariable(variable1); + view.add(); + view.displayTotal(); + + variable1 = 2.0; + view.setVariable(variable1); + view.subtract(); + view.divide(); + view.multiply(); + view.displayTotal(); + } + + private App() { + } } ``` -CalculatorModel defines the state of our view or in out case, variable and output of the calculator. +The `CalculatorView` class represents the View in MVI. It receives user events (in this case, simulated by the `App` class), and sends them to the ViewModel. It also updates its display when it receives a new state from the ViewModel. + ```java -@Data -public class CalculatorModel { +public class CalculatorView { + private CalculatorViewModel viewModel; + + public CalculatorView(CalculatorViewModel viewModel) { + this.viewModel = viewModel; + } - /** - * Current calculator variable used for operations. - **/ - final Double variable; + public void setVariable(double variable) { + viewModel.process(new SetVariableEvent(variable)); + } + + public void add() { + viewModel.process(new AddEvent()); + } + + public void subtract() { + viewModel.process(new SubtractEvent()); + } + + public void divide() { + viewModel.process(new DivideEvent()); + } + + public void multiply() { + viewModel.process(new MultiplyEvent()); + } - /** - * Current calculator output -> is affected by operations. - **/ - final Double output; + public void displayTotal() { + System.out.println("Total: " + viewModel.getState().getTotal()); + } } ``` +The `CalculatorViewModel` class represents the ViewModel in MVI. It receives events from the View, updates the Model's state accordingly, and then pushes the new state to the View. -CalculatorView will serve as a mock view which will expose potential user actions and -display calculator state -> output and current variable ```java -@Slf4j -public class CalculatorView { +public class CalculatorViewModel { + private CalculatorModel model; + + public CalculatorViewModel() { + this.model = new CalculatorModel(); + } + + public void process(UserEvent event) { + event.apply(model); + } + + public CalculatorModel getState() { + return model; + } +} +``` - /** - * View model param handling the operations. - * */ - private final CalculatorViewModel viewModel = new CalculatorViewModel(); +The `CalculatorModel` class represents the Model in MVI. It holds the current state of the calculator, and provides methods for updating that state. + +```java +public class CalculatorModel { + private double total; + private double variable; - /** - * Display current view model output with logger. - * */ - void displayTotal() { - LOGGER.info( - "Total value = {}", - viewModel.getCalculatorModel().getOutput().toString() - ); + public void setVariable(double variable) { + this.variable = variable; } - /** - * Handle addition action. - * */ - void add() { - viewModel.handleAction(new AdditionCalculatorAction()); + public void add() { + total += variable; } - /** - * Handle subtraction action. - * */ - void subtract() { - viewModel.handleAction(new SubtractionCalculatorAction()); + public void subtract() { + total -= variable; } - /** - * Handle multiplication action. - * */ - void multiply() { - viewModel.handleAction(new MultiplicationCalculatorAction()); + public void divide() { + total /= variable; } - /** - * Handle division action. - * */ - void divide() { - viewModel.handleAction(new DivisionCalculatorAction()); + public void multiply() { + total *= variable; } - /** - * Handle setting new variable action. - * - * @param value -> new calculator variable. - * */ - void setVariable(final Double value) { - viewModel.handleAction(new SetVariableCalculatorAction(value)); + public double getTotal() { + return total; } } ``` -Finally, ViewModel handles the exposed events with the handleAction(event) method, which delegates -the specific handling to private methods. Initially calculator output and variable are equal to 0. +Finally, the `UserEvent` interface and its implementations represent the different types of user events that can occur. Each event knows how to apply itself to the Model. + ```java -public final class CalculatorViewModel { - - /** - * Current calculator model (can be changed). - */ - private CalculatorModel model = - new CalculatorModel(0.0, 0.0); - - /** - * Handle calculator action. - * - * @param action -> transforms calculator model. - */ - void handleAction(final CalculatorAction action) { - switch (action.tag()) { - case AdditionCalculatorAction.TAG -> add(); - case SubtractionCalculatorAction.TAG -> subtract(); - case MultiplicationCalculatorAction.TAG -> multiply(); - case DivisionCalculatorAction.TAG -> divide(); - case SetVariableCalculatorAction.TAG -> { - SetVariableCalculatorAction setVariableAction = - (SetVariableCalculatorAction) action; - setVariable(setVariableAction.getVariable()); - } - default -> { - } - } - } - - /** - * Getter. - * - * @return current calculator model. - */ - public CalculatorModel getCalculatorModel() { - return model; +public interface UserEvent { + void apply(CalculatorModel model); +} + +public class SetVariableEvent implements UserEvent { + private double variable; + + public SetVariableEvent(double variable) { + this.variable = variable; } - /** - * Set new calculator model variable. - * - * @param variable -> value of new calculator model variable. - */ - private void setVariable(final Double variable) { - model = new CalculatorModel( - variable, - model.getOutput() - ); - } - - /** - * Add variable to model output. - */ - private void add() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() + model.getVariable() - ); - } - - /** - * Subtract variable from model output. - */ - private void subtract() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() - model.getVariable() - ); - } - - /** - * Multiply model output by variable. - */ - private void multiply() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() * model.getVariable() - ); - } - - /** - * Divide model output by variable. - */ - private void divide() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() / model.getVariable() - ); + @Override + public void apply(CalculatorModel model) { + model.setVariable(variable); } } + +// Similar classes would be created for AddEvent, SubtractEvent, DivideEvent, and MultiplyEvent ... ``` -## Applicability -Use the Model-View-Intent pattern when +This example demonstrates the key aspects of the MVI pattern: unidirectional data flow, clear separation of concerns, and the use of events to drive changes in the Model's state. + +## When to Use the Model-View-Intent Pattern in Java + +* The MVI pattern is useful in Java applications with complex user interfaces that require a clear separation of concerns, predictable state management, and enhanced maintainability. +* Often applied in reactive programming environments to ensure a smooth data flow and state consistency. + +## Model-View-Intent Pattern Java Tutorials + +* [Model View Intent: a new Android Architecture Pattern (Apium Academy)](https://apiumacademy.com/blog/model-view-intent-pattern/) +* [MVI Architecture for Android Tutorial: Getting Started (Kodeco)](https://www.kodeco.com/817602-mvi-architecture-for-android-tutorial-getting-started) + +## Real-World Applications of Model-View-Intent Pattern in Java + +* Widely adopted in reactive and event-driven Java applications, particularly those using frameworks like RxJava or Project Reactor. +* Used in Android development, especially with libraries that support reactive programming such as RxJava and LiveData. + +## Benefits and Trade-offs of Model-View-Intent Pattern -* You want to clearly separate the domain data from its user interface representation -* You want to minimise the public api of the view model +Benefits: -## Known uses -A popular architecture pattern in android. The small public api is particularly powerful -with the new Android Compose UI, as you can pass a single method (viewModel::handleEvent) -to all Composables(parts of UI) as a callback for user input event. +* Enhances the predictability of the UI by establishing a clear and cyclical data flow. +* Improves testability due to well-defined separation between components. +* Supports better state management by centralizing the state within the Model. -## Consequences -Pros: -* Encapsulation -* Separation of concerns -* Clear list of all possible user events +Trade-offs: -Cons: -* More boilerplate code compared to alternatives (especially in Java) +* Increases complexity for simple UIs due to the structured and cyclical flow. +* Requires familiarity with reactive programming paradigms. +* Can lead to boilerplate code if not managed properly. -## Related patterns -MVC: -* [Trygve Reenskaug - Model-view-controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +## Related Java Design Patterns -## Credits +[Model-View-ViewModel (MVVM)](https://java-design-patterns.com/patterns/model-view-viewmodel/): Shares a similar goal of separating the view from the model but differs as MVI introduces a cyclical data flow. +[Model-View-Controller (MVC)](https://java-design-patterns.com/patterns/model-view-controller/): MVI can be seen as an evolution of MVC, focusing more on reactive programming and unidirectional data flow. +[Observer](https://java-design-patterns.com/patterns/observer/): Essential in MVI to observe changes in the Model and update the View accordingly. -* [Model View Intent: a new Android Architecture Pattern](https://apiumacademy.com/blog/model-view-intent-pattern/) -* [MVI Architecture for Android Tutorial](https://www.kodeco.com/817602-mvi-architecture-for-android-tutorial-getting-started) +## References and Credits +* [Functional and Reactive Domain Modeling](https://amzn.to/4adghJ8) +* [Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications](https://amzn.to/4dxwawC) diff --git a/model-view-intent/etc/mvi-architecture-diagram.png b/model-view-intent/etc/mvi-architecture-diagram.png new file mode 100644 index 000000000000..a0672ddc051f Binary files /dev/null and b/model-view-intent/etc/mvi-architecture-diagram.png differ diff --git a/model-view-intent/pom.xml b/model-view-intent/pom.xml index 9ca57f8819af..048acd19952f 100644 --- a/model-view-intent/pom.xml +++ b/model-view-intent/pom.xml @@ -34,6 +34,14 @@ model-view-intent + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java index c318708ad318..4bb699cf2834 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework licensed under LGPL - * (see lgpl-3.0.txt). + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License * Copyright © 2014-2022 Ilkka Seppälä @@ -27,23 +25,17 @@ package com.iluwatar.model.view.intent; /** - * Model-View-Intent is a pattern for implementing user interfaces. - * Its main advantage over MVVM which it closely mirrors is a - * minimal public api with which user events can be exposed to the ViewModel. - * In case of the MVI every event is exposed by using a single method - * with 1 argument which implements UserEvent interface. - * Specific parameters can be expressed as its parameters. In this case, - * we'll be using MVI to implement a simple calculator - * with +, -, /, * operations and the ability to set the variable. - * It's important to note, that every user action happens through the + * Model-View-Intent is a pattern for implementing user interfaces. Its main advantage over MVVM + * which it closely mirrors is a minimal public api with which user events can be exposed to the + * ViewModel. In case of the MVI every event is exposed by using a single method with 1 argument + * which implements UserEvent interface. Specific parameters can be expressed as its parameters. In + * this case, we'll be using MVI to implement a simple calculator with +, -, /, * operations and the + * ability to set the variable. It's important to note, that every user action happens through the * view, we never interact with the ViewModel directly. */ public final class App { - - /** - * To avoid magic value lint error. - */ + /** To avoid magic value lint error. */ private static final double RANDOM_VARIABLE = 10.0; /** @@ -63,10 +55,10 @@ public static void main(final String[] args) { // add calculator variable to output -> calculator output = 10.0 view.add(); - view.displayTotal(); // display output + view.displayTotal(); // display output variable1 = 2.0; - view.setVariable(variable1); // calculator variable = 2.0 + view.setVariable(variable1); // calculator variable = 2.0 // subtract calculator variable from output -> calculator output = 8 view.subtract(); @@ -79,9 +71,6 @@ public static void main(final String[] args) { view.displayTotal(); } - /** - * Avoid default constructor lint error. - */ - private App() { - } + /** Avoid default constructor lint error. */ + private App() {} } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java index 8e11322fca8d..3ef9c9934248 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java @@ -1,23 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent; import lombok.Data; import lombok.Getter; -/** - * Current state of calculator. - */ +/** Current state of calculator. */ @Data public class CalculatorModel { - /** - * Current calculator variable used for operations. - **/ - @Getter - private final Double variable; + /** Current calculator variable used for operations. */ + @Getter private final Double variable; - /** - * Current calculator output -> is affected by operations. - **/ - @Getter - private final Double output; + /** Current calculator output -> is affected by operations. */ + @Getter private final Double output; } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java index dfaf154684f7..a5ceb587db6e 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent; import com.iluwatar.model.view.intent.actions.AdditionCalculatorAction; @@ -10,55 +34,38 @@ import lombok.extern.slf4j.Slf4j; /** - * Exposes changes to the state of calculator - * to {@link CalculatorViewModel} through - * {@link com.iluwatar.model.view.intent.actions.CalculatorAction} - * and displays its updated {@link CalculatorModel}. + * Exposes changes to the state of calculator to {@link CalculatorViewModel} through {@link + * com.iluwatar.model.view.intent.actions.CalculatorAction} and displays its updated {@link + * CalculatorModel}. */ @Slf4j @Data public class CalculatorView { - /** - * View model param handling the operations. - */ - @Getter - private final CalculatorViewModel viewModel; + /** View model param handling the operations. */ + @Getter private final CalculatorViewModel viewModel; - /** - * Display current view model output with logger. - */ + /** Display current view model output with logger. */ void displayTotal() { - LOGGER.info( - "Total value = {}", - viewModel.getCalculatorModel().getOutput().toString() - ); + LOGGER.info("Total value = {}", viewModel.getCalculatorModel().getOutput().toString()); } - /** - * Handle addition action. - */ + /** Handle addition action. */ void add() { viewModel.handleAction(new AdditionCalculatorAction()); } - /** - * Handle subtraction action. - */ + /** Handle subtraction action. */ void subtract() { viewModel.handleAction(new SubtractionCalculatorAction()); } - /** - * Handle multiplication action. - */ + /** Handle multiplication action. */ void multiply() { viewModel.handleAction(new MultiplicationCalculatorAction()); } - /** - * Handle division action. - */ + /** Handle division action. */ void divide() { viewModel.handleAction(new DivisionCalculatorAction()); } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java index 160057c012bd..9c1fee801720 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent; import com.iluwatar.model.view.intent.actions.AdditionCalculatorAction; @@ -8,16 +32,12 @@ import com.iluwatar.model.view.intent.actions.SubtractionCalculatorAction; /** - * Handle transformations to {@link CalculatorModel} - * based on intercepted {@link CalculatorAction}. + * Handle transformations to {@link CalculatorModel} based on intercepted {@link CalculatorAction}. */ public final class CalculatorViewModel { - /** - * Current calculator model (can be changed). - */ - private CalculatorModel model = - new CalculatorModel(0.0, 0.0); + /** Current calculator model (can be changed). */ + private CalculatorModel model = new CalculatorModel(0.0, 0.0); /** * Handle calculator action. @@ -26,17 +46,15 @@ public final class CalculatorViewModel { */ void handleAction(final CalculatorAction action) { switch (action.tag()) { - case AdditionCalculatorAction.TAG -> add(); - case SubtractionCalculatorAction.TAG -> subtract(); - case MultiplicationCalculatorAction.TAG -> multiply(); - case DivisionCalculatorAction.TAG -> divide(); - case SetVariableCalculatorAction.TAG -> { - SetVariableCalculatorAction setVariableAction = - (SetVariableCalculatorAction) action; + case AdditionCalculatorAction.ADDITION -> add(); + case SubtractionCalculatorAction.SUBTRACTION -> subtract(); + case MultiplicationCalculatorAction.MULTIPLICATION -> multiply(); + case DivisionCalculatorAction.DIVISION -> divide(); + case SetVariableCalculatorAction.SET_VARIABLE -> { + SetVariableCalculatorAction setVariableAction = (SetVariableCalculatorAction) action; setVariable(setVariableAction.getVariable()); } - default -> { - } + default -> throw new IllegalArgumentException("Unknown tag"); } } @@ -55,49 +73,26 @@ public CalculatorModel getCalculatorModel() { * @param variable -> value of new calculator model variable. */ private void setVariable(final Double variable) { - model = new CalculatorModel( - variable, - model.getOutput() - ); + model = new CalculatorModel(variable, model.getOutput()); } - /** - * Add variable to model output. - */ + /** Add variable to model output. */ private void add() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() + model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() + model.getVariable()); } - /** - * Subtract variable from model output. - */ + /** Subtract variable from model output. */ private void subtract() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() - model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() - model.getVariable()); } - /** - * Multiply model output by variable. - */ + /** Multiply model output by variable. */ private void multiply() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() * model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() * model.getVariable()); } - /** - * Divide model output by variable. - */ + /** Divide model output by variable. */ private void divide() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() / model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() / model.getVariable()); } } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java index 5647232e0b98..21aca2a54eed 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java @@ -1,19 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent.actions; -/** - * Addition {@link CalculatorAction}. - * */ +/** Addition {@link CalculatorAction}. */ public class AdditionCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ - public static final String TAG = "ADDITION"; + /** Subclass tag. */ + public static final String ADDITION = "ADDITION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { - return TAG; + return ADDITION; } } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java index 70477409aa7b..b9566dc53ff5 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java @@ -1,15 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent.actions; -/** - * Defines what outside interactions can be consumed by view model. - * */ +/** Defines what outside interactions can be consumed by view model. */ public interface CalculatorAction { /** * Makes identifying action trivial. * * @return subclass tag. - * */ + */ String tag(); } - diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java index 9da56c6ccc06..28e2ad362cec 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java @@ -1,19 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent.actions; -/** - * Division {@link CalculatorAction}. - * */ +/** Division {@link CalculatorAction}. */ public class DivisionCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ - public static final String TAG = "DIVISION"; + /** Subclass tag. */ + public static final String DIVISION = "DIVISION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { - return TAG; + return DIVISION; } } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java index 1bf9f4c86990..9c5bcd044049 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java @@ -1,19 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent.actions; -/** - * Multiplication {@link CalculatorAction}. - * */ +/** Multiplication {@link CalculatorAction}. */ public class MultiplicationCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ - public static final String TAG = "MULTIPLICATION"; + /** Subclass tag. */ + public static final String MULTIPLICATION = "MULTIPLICATION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { - return TAG; + return MULTIPLICATION; } } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java index 564609ccc1af..ce438e22a5d1 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java @@ -1,30 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent.actions; import lombok.Data; import lombok.Getter; -/** - * SetVariable {@link CalculatorAction}. - */ +/** SetVariable {@link CalculatorAction}. */ @Data public final class SetVariableCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - */ - public static final String TAG = "SET_VARIABLE"; + /** Subclass tag. */ + public static final String SET_VARIABLE = "SET_VARIABLE"; - /** - * Used by {@link com.iluwatar.model.view.intent.CalculatorViewModel}. - */ - @Getter - private final Double variable; + /** Used by {@link com.iluwatar.model.view.intent.CalculatorViewModel}. */ + @Getter private final Double variable; - /** - * Makes checking subclass type trivial. - */ + /** Makes checking subclass type trivial. */ @Override public String tag() { - return TAG; + return SET_VARIABLE; } } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java index c8b31e3b86a3..4a20d32a1fe4 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java @@ -1,19 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent.actions; -/** - * Subtraction {@link CalculatorAction}. - * */ +/** Subtraction {@link CalculatorAction}. */ public class SubtractionCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ - public static final String TAG = "SUBTRACTION"; + /** Subclass tag. */ + public static final String SUBTRACTION = "SUBTRACTION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { - return TAG; + return SUBTRACTION; } } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java index 3ce2a4662e8c..062fc309dda2 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java @@ -1,6 +1,29 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ /** - * Handle actions for {@link com.iluwatar.model.view.intent.CalculatorModel} - * defined by {@link com.iluwatar.model.view.intent.actions.CalculatorAction}. + * Handle actions for {@link com.iluwatar.model.view.intent.CalculatorModel} defined by {@link + * com.iluwatar.model.view.intent.actions.CalculatorAction}. */ - package com.iluwatar.model.view.intent.actions; diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java index ddfcb9827621..654eb228b704 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java @@ -1,6 +1,26 @@ -/** - * Define Model, View and ViewModel. - * Use them in {@link com.iluwatar.model.view.intent.App} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ - +/** Define Model, View and ViewModel. Use them in {@link com.iluwatar.model.view.intent.App} */ package com.iluwatar.model.view.intent; diff --git a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java index 6adc84c64c2b..b28ad3cabf31 100644 --- a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java +++ b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.model.view.intent; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java index 57cb556d69ef..492c965fe079 100644 --- a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java +++ b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java @@ -1,11 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model.view.intent; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.model.view.intent.actions.*; -import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; -public class CalculatorViewModelTest { +class CalculatorViewModelTest { private CalculatorModel modelAfterExecutingActions(List actions) { CalculatorViewModel viewModel = new CalculatorViewModel(); @@ -18,65 +44,69 @@ private CalculatorModel modelAfterExecutingActions(List action @Test void testSetup() { CalculatorModel model = modelAfterExecutingActions(new ArrayList<>()); - assert model.getVariable() == 0 && model.getOutput() == 0; + assertEquals(0, model.getVariable()); + assertEquals(0, model.getOutput()); } @Test void testSetVariable() { - List actions = List.of( - new SetVariableCalculatorAction(10.0) - ); + List actions = List.of(new SetVariableCalculatorAction(10.0)); CalculatorModel model = modelAfterExecutingActions(actions); - assert model.getVariable() == 10.0 && model.getOutput() == 0; + assertEquals(10.0, model.getVariable()); + assertEquals(0, model.getOutput()); } @Test void testAddition() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new SetVariableCalculatorAction(7.0), - new AdditionCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new SetVariableCalculatorAction(7.0), + new AdditionCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); - assert model.getVariable() == 7.0 && model.getOutput() == 11.0; + assertEquals(7.0, model.getVariable()); + assertEquals(11.0, model.getOutput()); } @Test void testSubtraction() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new SubtractionCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new SubtractionCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); - assert model.getVariable() == 2.0 && model.getOutput() == 2.0; + assertEquals(2.0, model.getVariable()); + assertEquals(2.0, model.getOutput()); } @Test void testMultiplication() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new MultiplicationCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new MultiplicationCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); - assert model.getVariable() == 2.0 && model.getOutput() == 8.0; + assertEquals(2.0, model.getVariable()); + assertEquals(8.0, model.getOutput()); } @Test void testDivision() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new SetVariableCalculatorAction(2.0), - new DivisionCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new SetVariableCalculatorAction(2.0), + new DivisionCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); - assert model.getVariable() == 2.0 && model.getOutput() == 2.0; + assertEquals(2.0, model.getVariable()); + assertEquals(2.0, model.getOutput()); } } diff --git a/model-view-presenter/README.md b/model-view-presenter/README.md index f836980f8e33..58cafed55b43 100644 --- a/model-view-presenter/README.md +++ b/model-view-presenter/README.md @@ -1,547 +1,173 @@ --- -title: Model-View-Presenter +title: "Model-View-Presenter Pattern in Java: Enhancing UI Logic Separation for Cleaner Code" +shortTitle: Model-View-Presenter (MVP) +description: "Discover the Model-View-Presenter (MVP) pattern in Java. Learn how it separates user interface, business logic, and data interaction to enhance testability and maintainability." category: Architectural language: en tag: - - Decoupling + - Architecture + - Client-server + - Decoupling + - Enterprise patterns + - Interface + - Presentation --- -## Intent -Apply a "Separation of Concerns" principle in a way that allows -developers to build and test user interfaces. +## Also known as -## Explanation +* MVP + +## Intent of Model-View-Presenter Design Pattern + +MVP aims to separate the user interface (UI) logic from the business logic and model in a software application, enabling easier testing and maintenance. + +## Detailed Explanation of Model-View-Presenter Pattern with Real-World Examples Real-world example -> Consider File selection application that allows to select a file from storage. -> File selection logic is completely separated from user interface implementation. +> Consider a real-world analogy of the Model-View-Presenter (MVP) pattern using a restaurant scenario: +> +> - **Model:** This is the kitchen in a restaurant, where all the cooking and preparation of dishes happens. It's responsible for managing the food ingredients, cooking processes, and ensuring that recipes are followed correctly. +> +> - **View:** This represents the dining area and the menu presented to the customers. It displays the available dishes, takes orders, and shows the final presentation of the food. However, it doesn't decide what's cooked or how it's prepared. +> +> - **Presenter:** Acting as the waiter, the presenter takes the customer's order (input) and communicates it to the kitchen (model). The waiter then brings the prepared food (output) back to the customer in the dining area (view). The waiter ensures that what the customer sees (the menu and food presentation) aligns with what the kitchen can provide, and also updates the view based on the kitchen's capabilities (e.g., out-of-stock items). +> +> In this analogy, the clear separation of roles allows the restaurant to operate efficiently: the kitchen focuses on food preparation, the dining area on customer interaction, and the waiter bridges the two, ensuring smooth operation and customer satisfaction. In plain words -> It separates the UI completely from service/domain layer into Presenter. +> The Model-View-Presenter (MVP) pattern separates the user interface, business logic, and data interaction in an application, with the presenter mediating between the view and the model to facilitate clear communication and updates. Java developers use MVP to improve application structure. Wikipedia says -> Model–view–presenter (MVP) is a derivation of the model–view–controller (MVC) architectural pattern, -> and is used mostly for building user interfaces. +> Model–view–presenter (MVP) is a derivation of the model–view–controller (MVC) architectural pattern, and is used mostly for building user interfaces. In MVP, the presenter assumes the functionality of the "middle-man". In MVP, all presentation logic is pushed to the presenter. -**Programmatic example** +Architecture diagram -Let's understand a simple file selection application build in AWT/Swing. -`FileLoader` reads & loads contain of given file. It represents the model component of MVP. - -```java -public class FileLoader implements Serializable { +![Model-View-Presenter Architecture Diagram](./etc/mvp-architecture-diagram.png) - /** - * Generated serial version UID. - */ - private static final long serialVersionUID = -4745803872902019069L; - private static final Logger LOGGER = LoggerFactory.getLogger(FileLoader.class); +## Programmatic Example of Model-View-Presenter Pattern in Java - /** - * Indicates if the file is loaded or not. - */ - private boolean loaded; +The Model-View-Presenter (MVP) design pattern is a derivative of the well-known Model-View-Controller (MVC) pattern. It aims to separate the application's logic (Model), GUIs (View), and the way that the user's actions update the application's logic (Presenter). This separation of concerns makes the application easier to manage, extend, and test. - /** - * The name of the file that we want to load. - */ - private String fileName; +Let's break down the MVP pattern using the provided code: - /** - * Loads the data of the file specified. - */ - public String loadData() { - var dataFileName = this.fileName; - try (var br = new BufferedReader(new FileReader(new File(dataFileName)))) { - var result = br.lines().collect(Collectors.joining("\n")); - this.loaded = true; - return result; - } catch (Exception e) { - LOGGER.error("File {} does not exist", dataFileName); - } - - return null; - } +1. **Model**: The Model represents the application's logic. In our case, the `FileLoader` class is the Model. It's responsible for handling the file loading process. - /** - * Sets the path of the file to be loaded, to the given value. - * - * @param fileName The path of the file to be loaded. - */ +```java +@Getter +public class FileLoader implements Serializable { + //... public void setFileName(String fileName) { this.fileName = fileName; } - - /** - * Gets the path of the file to be loaded. - * - * @return fileName The path of the file to be loaded. - */ - public String getFileName() { - return this.fileName; - } - - /** - * Returns true if the given file exists. - * - * @return True, if the file given exists, false otherwise. - */ + public boolean fileExists() { - return new File(this.fileName).exists(); + //... } - /** - * Returns true if the given file is loaded. - * - * @return True, if the file is loaded, false otherwise. - */ - public boolean isLoaded() { - return this.loaded; + public String loadData() { + //... } } ``` -`FileSelectorView` interface represents the View component in the MVP pattern. It can be -implemented by either the GUI components, or by the Stub. This is how it eases the UI testing. +2. **View**: The View is responsible for displaying the data provided by the Model. Here, the `FileSelectorView` interface and its implementation `FileSelectorJFrame` represent the View. They define how to display data and messages to the user. ```java -public interface FileSelectorView extends Serializable { - - /** - * Opens the view. - */ +public interface FileSelectorView { + //... + void setPresenter(FileSelectorPresenter presenter); void open(); - - /** - * Closes the view. - */ void close(); - - /** - * Returns true if view is opened. - * - * @return True, if the view is opened, false otherwise. - */ - boolean isOpened(); - - /** - * Sets the presenter component, to the one given as parameter. - * - * @param presenter The new presenter component. - */ - void setPresenter(FileSelectorPresenter presenter); - - /** - * Gets presenter component. - * - * @return The presenter Component. - */ - FileSelectorPresenter getPresenter(); - - /** - * Sets the file's name, to the value given as parameter. - * - * @param name The new name of the file. - */ - void setFileName(String name); - - /** - * Gets the name of file. - * - * @return The name of the file. - */ - String getFileName(); - - /** - * Displays a message to the users. - * - * @param message The message to be displayed. - */ void showMessage(String message); - - /** - * Displays the data to the view. - * - * @param data The data to be written. - */ void displayData(String data); + String getFileName(); } -``` - -`FileSelectorJFrame` represents the GUI implementation of the View component in the MVP pattern. - -```java -public class FileSelectorJFrame extends JFrame implements FileSelectorView, ActionListener { - - /** - * Default serial version ID. - */ - private static final long serialVersionUID = 1L; - - /** - * The "OK" button for loading the file. - */ - private final JButton ok; - - /** - * The cancel button. - */ - private final JButton cancel; - - /** - * The text field for giving the name of the file that we want to open. - */ - private final JTextField input; - - /** - * A text area that will keep the contents of the file opened. - */ - private final JTextArea area; - - /** - * The Presenter component that the frame will interact with. - */ - private FileSelectorPresenter presenter; - - /** - * The name of the file that we want to read it's contents. - */ - private String fileName; - - /** - * Constructor. - */ - public FileSelectorJFrame() { - super("File Loader"); - this.setDefaultCloseOperation(EXIT_ON_CLOSE); - this.setLayout(null); - this.setBounds(100, 100, 500, 200); - - /* - * Add the panel. - */ - var panel = new JPanel(); - panel.setLayout(null); - this.add(panel); - panel.setBounds(0, 0, 500, 200); - panel.setBackground(Color.LIGHT_GRAY); - - /* - * Add the info label. - */ - var info = new JLabel("File Name :"); - panel.add(info); - info.setBounds(30, 10, 100, 30); - - /* - * Add the contents label. - */ - var contents = new JLabel("File contents :"); - panel.add(contents); - contents.setBounds(30, 100, 120, 30); - - /* - * Add the text field. - */ - this.input = new JTextField(100); - panel.add(input); - this.input.setBounds(150, 15, 200, 20); - - /* - * Add the text area. - */ - this.area = new JTextArea(100, 100); - var pane = new JScrollPane(area); - pane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED); - pane.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED); - panel.add(pane); - this.area.setEditable(false); - pane.setBounds(150, 100, 250, 80); - - /* - * Add the OK button. - */ - this.ok = new JButton("OK"); - panel.add(ok); - this.ok.setBounds(250, 50, 100, 25); - this.ok.addActionListener(this); - - /* - * Add the cancel button. - */ - this.cancel = new JButton("Cancel"); - panel.add(this.cancel); - this.cancel.setBounds(380, 50, 100, 25); - this.cancel.addActionListener(this); - - this.presenter = null; - this.fileName = null; - } - - @Override - public void actionPerformed(ActionEvent e) { - if (this.ok.equals(e.getSource())) { - this.fileName = this.input.getText(); - presenter.fileNameChanged(); - presenter.confirmed(); - } else if (this.cancel.equals(e.getSource())) { - presenter.cancelled(); - } - } - - @Override - public void open() { - this.setVisible(true); - } - - @Override - public void close() { - this.dispose(); - } - - @Override - public boolean isOpened() { - return this.isVisible(); - } - - @Override - public void setPresenter(FileSelectorPresenter presenter) { - this.presenter = presenter; - } - - @Override - public FileSelectorPresenter getPresenter() { - return this.presenter; - } - - @Override - public void setFileName(String name) { - this.fileName = name; - } - - @Override - public String getFileName() { - return this.fileName; - } - - @Override - public void showMessage(String message) { - JOptionPane.showMessageDialog(null, message); - } +public class FileSelectorJFrame implements FileSelectorView { + //... @Override public void displayData(String data) { - this.area.setText(data); + this.dataDisplayed = true; } } ``` -`FileSelectorStub` is a stub that implements the View interface and it is useful when we want to test the reaction to -user events, such as mouse clicks etc. - -```java -public class FileSelectorStub implements FileSelectorView { - - /** - * Indicates whether or not the view is opened. - */ - private boolean opened; - - /** - * The presenter Component. - */ - private FileSelectorPresenter presenter; - - /** - * The current name of the file. - */ - private String name; - - /** - * Indicates the number of messages that were "displayed" to the user. - */ - private int numOfMessageSent; - - /** - * Indicates if the data of the file where displayed or not. - */ - private boolean dataDisplayed; - - /** - * Constructor. - */ - public FileSelectorStub() { - this.opened = false; - this.presenter = null; - this.name = ""; - this.numOfMessageSent = 0; - this.dataDisplayed = false; - } - - @Override - public void open() { - this.opened = true; - } - - @Override - public void setPresenter(FileSelectorPresenter presenter) { - this.presenter = presenter; - } - - @Override - public boolean isOpened() { - return this.opened; - } - - @Override - public FileSelectorPresenter getPresenter() { - return this.presenter; - } - - @Override - public String getFileName() { - return this.name; - } - - @Override - public void setFileName(String name) { - this.name = name; - } - - @Override - public void showMessage(String message) { - this.numOfMessageSent++; - } - - @Override - public void close() { - this.opened = false; - } - - @Override - public void displayData(String data) { - this.dataDisplayed = true; - } - - /** - * Returns the number of messages that were displayed to the user. - */ - public int getMessagesSent() { - return this.numOfMessageSent; - } - - /** - * Returns true, if the data were displayed. - * - * @return True if the data where displayed, false otherwise. - */ - public boolean dataDisplayed() { - return this.dataDisplayed; - } -} -``` - -`FileSelectorPresenter` represents the Presenter component in the MVP pattern. -It is responsible for reacting to the user's actions and update the View component. +3. **Presenter**: The Presenter acts as a bridge between the Model and the View. It reacts to the user's actions and updates the View accordingly. In our example, the `FileSelectorPresenter` class is the Presenter. ```java public class FileSelectorPresenter implements Serializable { - - /** - * Generated serial version UID. - */ - private static final long serialVersionUID = 1210314339075855074L; - - /** - * The View component that the presenter interacts with. - */ - private final FileSelectorView view; - - /** - * The Model component that the presenter interacts with. - */ - private FileLoader loader; - - /** - * Constructor. - * - * @param view The view component that the presenter will interact with. - */ - public FileSelectorPresenter(FileSelectorView view) { - this.view = view; - } - - /** - * Sets the {@link FileLoader} object, to the value given as parameter. - * - * @param loader The new {@link FileLoader} object(the Model component). - */ + //... public void setLoader(FileLoader loader) { this.loader = loader; } - /** - * Starts the presenter. - */ public void start() { view.setPresenter(this); view.open(); } - /** - * An "event" that fires when the name of the file to be loaded changes. - */ public void fileNameChanged() { loader.setFileName(view.getFileName()); } - /** - * Ok button handler. - */ public void confirmed() { - if (loader.getFileName() == null || loader.getFileName().equals("")) { - view.showMessage("Please give the name of the file first!"); - return; - } - - if (loader.fileExists()) { - var data = loader.loadData(); - view.displayData(data); - } else { - view.showMessage("The file specified does not exist."); - } + //... } - /** - * Cancels the file loading process. - */ public void cancelled() { view.close(); } } ``` -Below code reflects how we wire-up the Presenter & the View and the Presenter & the Model. +Finally, we wire up the Presenter, the View, and the Model in the `App` class: ```java +public class App { + public static void main(String[] args) { var loader = new FileLoader(); var frame = new FileSelectorJFrame(); var presenter = new FileSelectorPresenter(frame); presenter.setLoader(loader); presenter.start(); + } +} ``` -## Class diagram -![alt text](./etc/model-view-presenter_1.png "Model-View-Presenter") +In this setup, the `App` class creates instances of the Model, View, and Presenter. It then connects these instances, forming the MVP triad. The Presenter is given a reference to the View, and the Model is set on the Presenter. Finally, the Presenter is started, which in turn opens the View. + +## When to Use the Model-View-Presenter Pattern in Java + +Use MVP in applications where a clear [separation of concerns](https://java-design-patterns.com/principles/#separation-of-concerns) is needed between the presentation layer and the underlying business logic. It's particularly useful in client-server applications and enterprise-level applications. + +## Real-World Applications of Model-View-Presenter Pattern in Java + +* Desktop applications like those built using Java Swing or JavaFX. +* Web applications with complex user interfaces and business logic. + +## Benefits and Trade-offs of Model-View-Presenter Pattern + +Benefits: + +* Enhances testability of UI logic by allowing the presenter to be tested separately from the view. +* Promotes a clean separation of concerns, making the application easier to manage and extend. +* Facilitates easier UI updates without affecting the business logic. + +Trade-offs: + +* Increases complexity with more classes and interfaces. +* Requires careful design to avoid over-coupling between the presenter and the view. + +## Related Java Design Patterns -## Applicability -Use the Model-View-Presenter in any of the following -situations +* [Model-View-Controller (MVC)](https://java-design-patterns.com/patterns/model-view-controller/): MVP is often considered a variant of MVC where the presenter takes over the controller's role in managing user input and updating the model. +* [Model-View-ViewModel (MVVM)](https://java-design-patterns.com/patterns/model-view-viewmodel/): Similar to MVP but adapted for frameworks like WPF or frameworks that support data binding, making the view update automatically when the model changes. -* When you want to improve the "Separation of Concerns" principle in presentation logic -* When a user interface development and testing is necessary. +## References and Credits +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Pro JavaFX 8: A Definitive Guide to Building Desktop, Mobile, and Embedded Java Clients](https://amzn.to/4a8qcQ1) diff --git a/model-view-presenter/etc/mvp-architecture-diagram.png b/model-view-presenter/etc/mvp-architecture-diagram.png new file mode 100644 index 000000000000..df324b0ab6b0 Binary files /dev/null and b/model-view-presenter/etc/mvp-architecture-diagram.png differ diff --git a/model-view-presenter/pom.xml b/model-view-presenter/pom.xml index ab49c8f2f20b..9dcd55f610e2 100644 --- a/model-view-presenter/pom.xml +++ b/model-view-presenter/pom.xml @@ -36,6 +36,14 @@ model-view-presenter http://maven.apache.org + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java index 7bee50111411..45f7725604b4 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java @@ -34,8 +34,8 @@ * FileSelectorJframe} is the GUI and the {@link FileSelectorPresenter} is responsible to respond to * users' actions. * - *

Finally, please notice the wiring between the Presenter and the View and between the - * Presenter and the Model. + *

Finally, please notice the wiring between the Presenter and the View and between the Presenter + * and the Model. */ public class App { diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java index d7715c8964ca..b2678c811a7e 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java @@ -27,8 +27,10 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; +import java.io.Serial; import java.io.Serializable; import java.util.stream.Collectors; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,31 +40,24 @@ * *

It is responsible for reading and loading the contents of a given file. */ +@Getter public class FileLoader implements Serializable { - /** - * Generated serial version UID. - */ - private static final long serialVersionUID = -4745803872902019069L; + /** Generated serial version UID. */ + @Serial private static final long serialVersionUID = -4745803872902019069L; private static final Logger LOGGER = LoggerFactory.getLogger(FileLoader.class); - /** - * Indicates if the file is loaded or not. - */ + /** Indicates if the file is loaded or not. */ private boolean loaded; - /** - * The name of the file that we want to load. - */ + /** The name of the file that we want to load. */ private String fileName; - /** - * Loads the data of the file specified. - */ + /** Loads the data of the file specified. */ public String loadData() { var dataFileName = this.fileName; - try (var br = new BufferedReader(new FileReader(new File(dataFileName)))) { + try (var br = new BufferedReader(new FileReader(dataFileName))) { var result = br.lines().collect(Collectors.joining("\n")); this.loaded = true; return result; @@ -82,15 +77,6 @@ public void setFileName(String fileName) { this.fileName = fileName; } - /** - * Gets the path of the file to be loaded. - * - * @return fileName The path of the file to be loaded. - */ - public String getFileName() { - return this.fileName; - } - /** * Returns true if the given file exists. * @@ -99,13 +85,4 @@ public String getFileName() { public boolean fileExists() { return new File(this.fileName).exists(); } - - /** - * Returns true if the given file is loaded. - * - * @return True, if the file is loaded, false otherwise. - */ - public boolean isLoaded() { - return this.loaded; - } } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java index fa3fe2a3e8ee..1680e5455a8f 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java @@ -30,6 +30,7 @@ import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.Serial; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; @@ -44,44 +45,28 @@ */ public class FileSelectorJframe extends JFrame implements FileSelectorView, ActionListener { - /** - * Default serial version ID. - */ - private static final long serialVersionUID = 1L; + /** Default serial version ID. */ + @Serial private static final long serialVersionUID = 1L; - /** - * The "OK" button for loading the file. - */ + /** The "OK" button for loading the file. */ private final JButton ok; - /** - * The cancel button. - */ + /** The cancel button. */ private final JButton cancel; - /** - * The text field for giving the name of the file that we want to open. - */ + /** The text field for giving the name of the file that we want to open. */ private final JTextField input; - /** - * A text area that will keep the contents of the file opened. - */ + /** A text area that will keep the contents of the file opened. */ private final JTextArea area; - /** - * The Presenter component that the frame will interact with. - */ + /** The Presenter component that the frame will interact with. */ private FileSelectorPresenter presenter; - /** - * The name of the file that we want to read it's contents. - */ + /** The name of the file that we want to read it's contents. */ private String fileName; - /** - * Constructor. - */ + /** Constructor. */ public FileSelectorJframe() { super("File Loader"); this.setDefaultCloseOperation(EXIT_ON_CLOSE); diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java index e9d4af3a079e..3c15408bddcf 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java @@ -24,6 +24,7 @@ */ package com.iluwatar.model.view.presenter; +import java.io.Serial; import java.io.Serializable; /** @@ -34,19 +35,13 @@ */ public class FileSelectorPresenter implements Serializable { - /** - * Generated serial version UID. - */ - private static final long serialVersionUID = 1210314339075855074L; + /** Generated serial version UID. */ + @Serial private static final long serialVersionUID = 1210314339075855074L; - /** - * The View component that the presenter interacts with. - */ + /** The View component that the presenter interacts with. */ private final FileSelectorView view; - /** - * The Model component that the presenter interacts with. - */ + /** The Model component that the presenter interacts with. */ private FileLoader loader; /** @@ -67,26 +62,20 @@ public void setLoader(FileLoader loader) { this.loader = loader; } - /** - * Starts the presenter. - */ + /** Starts the presenter. */ public void start() { view.setPresenter(this); view.open(); } - /** - * An "event" that fires when the name of the file to be loaded changes. - */ + /** An "event" that fires when the name of the file to be loaded changes. */ public void fileNameChanged() { loader.setFileName(view.getFileName()); } - /** - * Ok button handler. - */ + /** Ok button handler. */ public void confirmed() { - if (loader.getFileName() == null || loader.getFileName().equals("")) { + if (loader.getFileName() == null || loader.getFileName().isEmpty()) { view.showMessage("Please give the name of the file first!"); return; } @@ -99,9 +88,7 @@ public void confirmed() { } } - /** - * Cancels the file loading process. - */ + /** Cancels the file loading process. */ public void cancelled() { view.close(); } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java index ed09627a21e7..3eda80eb27c4 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java @@ -36,34 +36,22 @@ */ public class FileSelectorStub implements FileSelectorView { - /** - * Indicates whether or not the view is opened. - */ + /** Indicates whether or not the view is opened. */ private boolean opened; - /** - * The presenter Component. - */ + /** The presenter Component. */ private FileSelectorPresenter presenter; - /** - * The current name of the file. - */ + /** The current name of the file. */ private String name; - /** - * Indicates the number of messages that were "displayed" to the user. - */ + /** Indicates the number of messages that were "displayed" to the user. */ private int numOfMessageSent; - /** - * Indicates if the data of the file where displayed or not. - */ + /** Indicates if the data of the file where displayed or not. */ private boolean dataDisplayed; - /** - * Constructor. - */ + /** Constructor. */ public FileSelectorStub() { this.opened = false; this.presenter = null; @@ -117,9 +105,7 @@ public void displayData(String data) { this.dataDisplayed = true; } - /** - * Returns the number of messages that were displayed to the user. - */ + /** Returns the number of messages that were displayed to the user. */ public int getMessagesSent() { return this.numOfMessageSent; } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java index b2d916ac1d0f..9a0b86a0b61a 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java @@ -32,14 +32,10 @@ */ public interface FileSelectorView extends Serializable { - /** - * Opens the view. - */ + /** Opens the view. */ void open(); - /** - * Closes the view. - */ + /** Closes the view. */ void close(); /** diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java index 78579518982c..91c7e8dbda7b 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.model.view.presenter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java index 522fb81e1465..b12877d35b63 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java @@ -28,11 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/21/15 - 12:12 PM - * - * @author Jeroen Meulemeester - */ +/** FileLoaderTest */ class FileLoaderTest { @Test @@ -41,5 +37,4 @@ void testLoadData() { fileLoader.setFileName("non-existing-file"); assertNull(fileLoader.loadData()); } - -} \ No newline at end of file +} diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java index da5788c8d6cc..47b10737db52 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java @@ -27,26 +27,19 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.awt.event.ActionEvent; - import org.junit.jupiter.api.Test; -/** - * Date: 01/29/23 - 6:00 PM - * - * @author Rahul Raj - */ +/** FileSelectorJframeTest */ class FileSelectorJframeTest { - - /** - * Tests if the jframe action event is triggered without any exception. - */ - @Test - void testActionEvent() { - assertDoesNotThrow(() ->{ - FileSelectorJframe jFrame = new FileSelectorJframe(); - ActionEvent action = new ActionEvent("dummy", 1, "dummy"); - jFrame.actionPerformed(action); - }); - } + /** Tests if the jframe action event is triggered without any exception. */ + @Test + void testActionEvent() { + assertDoesNotThrow( + () -> { + FileSelectorJframe jFrame = new FileSelectorJframe(); + ActionEvent action = new ActionEvent("dummy", 1, "dummy"); + jFrame.actionPerformed(action); + }); + } } diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java index b9a31253b622..dca646c20b87 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java @@ -38,24 +38,16 @@ */ class FileSelectorPresenterTest { - /** - * The Presenter component. - */ + /** The Presenter component. */ private FileSelectorPresenter presenter; - /** - * The View component, implemented this time as a Stub!!! - */ + /** The View component, implemented this time as a Stub!!! */ private FileSelectorStub stub; - /** - * The Model component. - */ + /** The Model component. */ private FileLoader loader; - /** - * Initializes the components of the test case. - */ + /** Initializes the components of the test case. */ @BeforeEach void setUp() { this.stub = new FileSelectorStub(); @@ -64,9 +56,7 @@ void setUp() { presenter.setLoader(loader); } - /** - * Tests if the Presenter was successfully connected with the View. - */ + /** Tests if the Presenter was successfully connected with the View. */ @Test void wiring() { presenter.start(); @@ -75,9 +65,7 @@ void wiring() { assertTrue(stub.isOpened()); } - /** - * Tests if the name of the file changes. - */ + /** Tests if the name of the file changes. */ @Test void updateFileNameToLoader() { var expectedFile = "Stamatis"; @@ -105,9 +93,7 @@ void fileConfirmationWhenNameIsNull() { assertEquals(1, stub.getMessagesSent()); } - /** - * Tests if we receive a confirmation when we attempt to open a file that it doesn't exist. - */ + /** Tests if we receive a confirmation when we attempt to open a file that it doesn't exist. */ @Test void fileConfirmationWhenFileDoesNotExist() { stub.setFileName("RandomName.txt"); @@ -120,9 +106,7 @@ void fileConfirmationWhenFileDoesNotExist() { assertEquals(1, stub.getMessagesSent()); } - /** - * Tests if we can open the file, when it exists. - */ + /** Tests if we can open the file, when it exists. */ @Test void fileConfirmationWhenFileExists() { stub.setFileName("etc/data/test.txt"); @@ -134,9 +118,7 @@ void fileConfirmationWhenFileExists() { assertTrue(stub.dataDisplayed()); } - /** - * Tests if the view closes after cancellation. - */ + /** Tests if the view closes after cancellation. */ @Test void cancellation() { presenter.start(); @@ -155,5 +137,4 @@ void testNullFile() { assertFalse(loader.isLoaded()); assertFalse(stub.dataDisplayed()); } - } diff --git a/model-view-viewmodel/README.md b/model-view-viewmodel/README.md index ab8b20628934..46597bedd9d8 100644 --- a/model-view-viewmodel/README.md +++ b/model-view-viewmodel/README.md @@ -1,30 +1,55 @@ --- -title: Model-View-ViewModel +title: "Model-View-ViewModel Pattern in Java: Separating UI and Logic for Cleaner Code" +shortTitle: Model-View-ViewModel +description: "Learn about the Model-View-ViewModel (MVVM) design pattern in Java. Discover its benefits, real-world applications, and how it improves UI and business logic separation for scalable and maintainable code." category: Architectural language: en tag: - - Decoupling + - Architecture + - Data binding + - Decoupling + - Presentation + - Scalability --- ## Also known as -Model–View–Binder +* MVVM -## Intent +## Intent of Model-View-ViewModel Design Pattern -To apply "[Separation of Concerns](https://java-design-patterns.com/principles/#separation-of-concerns)" to separate the logic from the UI components and allow developers to work on UI without affecting the logic and vice versa. +The intent of the Model-View-ViewModel (MVVM) pattern in Java is to provide a clear [separation of concerns](https://java-design-patterns.com/principles/#separation-of-concerns) between the UI logic, the presentation logic, and the business logic by dividing the application into three interconnected components: Model, View, and ViewModel. -## Explanation +## Detailed Explanation of Model-View-ViewModel Pattern with Real-World Examples + +Real-world example + +> Consider a real-world analogous example of the MVVM pattern similar to organizing a cooking show. In this scenario: +> +> - **Model:** Represents the recipe itself, which includes the ingredients and the steps needed to cook the dish. The model is purely about the data and rules for preparing the dish but does not concern itself with how this information is presented to the audience. +> +> - **View:** Is akin to the kitchen set where the cooking show is filmed, including all the visual elements like the layout of the kitchen, the placement of ingredients, and the cookware. The view is responsible for the visual presentation and how the audience sees the cooking process. +> +> - **ViewModel:** Acts like the script for the cooking show, where it interprets the recipe (model) and organizes the flow of the show. It tells the chef (view) what to display next, when to add ingredients, and how to respond to changes like substituting an ingredient. The ViewModel bridges the gap between the technical details of the recipe and the chef's presentation, ensuring the audience understands each step without delving into the complexities of the recipe itself. +> +> In this example, the ViewModel allows the chef to focus on cooking and interacting with the audience, while the underlying recipe remains unchanged, promoting a clear separation of concerns. + +In plain words + +> The MVVM design pattern separates an application into three distinct components: the Model, which holds the data and business logic; the View, which displays the user interface; and the ViewModel, which acts as an intermediary to bind data from the Model to the View. Wikipedia says > Model–view–viewmodel (MVVM) is a software architectural pattern that facilitates the separation of the development of the graphical user interface (the view) – be it via a markup language or GUI code – from the development of the business logic or back-end logic (the model) so that the view is not dependent on any specific model platform. -**Programmatic Example** +Architecture diagram + +![Model-View-ViewModel Architecture Diagram](./etc/mvvm-architecture-diagram.png) -Zkoss implementation: -> ViewModel will hold the business logic and expose the data from model to View +## Programmatic Example of Model-View-ViewModel Pattern in Java + +ViewModel will hold the business logic and expose the data from model to View. ```java public class BookViewModel { @@ -58,7 +83,7 @@ public class BookViewModel { } ``` -> View will have no logic, only UI elements +View will have no logic, only UI elements. ```xml @@ -103,40 +128,42 @@ To deploy the example, go to model-view-viewmodel folder and run: * `mvn jetty:run -Djetty.http.port=9911` * Open browser to address: http://localhost:9911/model-view-viewmodel/ -## Class diagram +## When to Use the Model-View-ViewModel Pattern in Java -![alt text](./etc/model-view-viewmodel.png "MVVM pattern class diagram") +MVVM is applicable in applications requiring a clear separation between the user interface and the underlying business logic, especially in large-scale, data-driven applications where UI and business logic change independently. This makes the Model-View-ViewModel pattern ideal for Java applications. -## Applicability +## Model-View-ViewModel Pattern Java Tutorials -* When looking for clean architecture, with better reusability, testability and maintainability. +* [Data Binding in Android (developer.android.com)](https://developer.android.com/codelabs/android-databinding#0) +* [Introduction to Model View View Model (MVVM) (GeeksforGeeks)](https://www.geeksforgeeks.org/introduction-to-model-view-view-model-mvvm/) +* [Patterns - WPF Apps With The Model-View-ViewModel Design Pattern (Microsoft)](https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern) -## Tutorials +## Real-World Applications of Model-View-ViewModel Pattern in Java -* [Zkoss Demo](https://www.zkoss.org/zkdemo/getting_started/mvvm) -* [Data Binding in Android](https://developer.android.com/codelabs/android-databinding#0) +* Widely used in JavaFX applications for desktop interfaces. +* Utilized in Android development with libraries like DataBinding and LiveData for reactive UI updates. +* ZK Framework [zkoss.org](https://www.zkoss.org/) +* KnockoutJS [knockoutjs.com](https://knockoutjs.com/) -## Typical Use Case +## Benefits and Trade-offs of Model-View-ViewModel Pattern -* Android apps -* .NET framework applications -* JavaScript applications +Benefits: -## Real world examples +* Improved testability due to decoupling of business and presentation logic. +* Easier maintenance and modification of the user interface without affecting the underlying data model. +* Enhanced reusability of the ViewModel across different views if designed generically. -* ZK Framework [zkoss.org](https://www.zkoss.org/) -* KnockoutJS [knockoutjs.com](https://knockoutjs.com/) +Trade-offs: -## Consequences +* Increased complexity in small applications where simpler patterns might suffice. +* Learning curve associated with understanding and applying the pattern correctly. -* John Gossman has criticized the MVVM pattern and its application in specific uses, stating that MVVM can be "overkill" when creating simple user interfaces. For larger applications, he believes that generalizing the viewmodel upfront can be difficult, and that large-scale data binding can lead to lower performance - Ref: [MVVM-Wiki](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) +## Related Java Design Patterns -* Can be hard to design ViewModel for larger applications. -* For complex databinding, debugging can be difficult. +* [MVC (Model-View-Controller)](https://java-design-patterns.com/patterns/model-view-controller/): MVVM can be seen as a derivative of MVC with a stronger emphasis on binding and decoupling, where the ViewModel acts as an intermediary unlike the controller in MVC. +* [MVP (Model-View-Presenter)](https://java-design-patterns.com/patterns/model-view-presenter/): Similar to MVVM but with a focus on the presenter handling the UI logic, making MVVM's ViewModel more passive in terms of direct UI manipulation. -## Credits +## References and Credits -* [ZK MVVM](https://www.zkoss.org/wiki/ZK%20Developer's%20Reference/MVVM) -* [GeeksforGeeks MVVM Intro](https://www.geeksforgeeks.org/introduction-to-model-view-view-model-mvvm/) -* [ZK MVVM Book](http://books.zkoss.org/zk-mvvm-book/9.5/) -* [Microsoft MVVM](https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern) +* [Android Programming: The Big Nerd Ranch Guide](https://amzn.to/3wBGG5o) +* [Pro JavaFX 8: A Definitive Guide to Building Desktop, Mobile, and Embedded Java Clients](https://amzn.to/4a8qcQ1) diff --git a/model-view-viewmodel/etc/mvvm-architecture-diagram.png b/model-view-viewmodel/etc/mvvm-architecture-diagram.png new file mode 100644 index 000000000000..279c734cdbff Binary files /dev/null and b/model-view-viewmodel/etc/mvvm-architecture-diagram.png differ diff --git a/model-view-viewmodel/pom.xml b/model-view-viewmodel/pom.xml index aceb5eea7604..248eb9e7d507 100644 --- a/model-view-viewmodel/pom.xml +++ b/model-view-viewmodel/pom.xml @@ -32,13 +32,12 @@ com.iluwatar 1.26.0-SNAPSHOT - com.iluwatar model-view-viewmodel 1.26.0-SNAPSHOT - 9.0.0 - 9.4.28.v20200408 - 3.3.2 + 10.0.0-jakarta + 11.0.24 + 3.4.0 yyyy-MM-dd -${project.version}-FL-${maven.build.timestamp} diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java index 27f6794025f0..e7f8f035803d 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java @@ -27,9 +27,7 @@ import lombok.AllArgsConstructor; import lombok.Data; -/** - * Book class. - */ +/** Book class. */ @AllArgsConstructor @Data public class Book { @@ -37,5 +35,4 @@ public class Book { private String name; private String author; private String description; - } diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java index 11ff6971e85e..34bb25c07b8a 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java @@ -27,12 +27,10 @@ import java.util.List; -/** - * Class representing a service to load books. - */ +/** Class representing a service to load books. */ public interface BookService { /* List all books * @return all books */ - public List load(); + List load(); } diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java index a407b1e8f492..7d7257fcc816 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java @@ -27,39 +27,44 @@ import java.util.ArrayList; import java.util.List; -/** - * Class that actually implement the books to load. - */ +/** Class that actually implement the books to load. */ public class BookServiceImpl implements BookService { private List designPatternBooks = new ArrayList<>(); - /** Initializes Book Data. - * To be used and passed along in load method - * In this case, list design pattern books are initialized to be loaded. - */ + /** + * Initializes Book Data. To be used and passed along in load method In this case, list design + * pattern books are initialized to be loaded. + */ public BookServiceImpl() { - designPatternBooks.add(new Book( - "Head First Design Patterns: A Brain-Friendly Guide", - "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", - "Head First Design Patterns Description")); - designPatternBooks.add(new Book( - "Design Patterns: Elements of Reusable Object-Oriented Software", - "Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides", - "Design Patterns Description")); - designPatternBooks.add(new Book( - "Patterns of Enterprise Application Architecture", "Martin Fowler", - "Patterns of Enterprise Application Architecture Description")); - designPatternBooks.add(new Book( - "Design Patterns Explained", "Alan Shalloway, James Trott", - "Design Patterns Explained Description")); - designPatternBooks.add(new Book( - "Applying UML and Patterns: An Introduction to " - + "Object-Oriented Analysis and Design and Iterative Development", - "Craig Larman", "Applying UML and Patterns Description")); + designPatternBooks.add( + new Book( + "Head First Design Patterns: A Brain-Friendly Guide", + "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", + "Head First Design Patterns Description")); + designPatternBooks.add( + new Book( + "Design Patterns: Elements of Reusable Object-Oriented Software", + "Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides", + "Design Patterns Description")); + designPatternBooks.add( + new Book( + "Patterns of Enterprise Application Architecture", + "Martin Fowler", + "Patterns of Enterprise Application Architecture Description")); + designPatternBooks.add( + new Book( + "Design Patterns Explained", + "Alan Shalloway, James Trott", + "Design Patterns Explained Description")); + designPatternBooks.add( + new Book( + "Applying UML and Patterns: An Introduction to " + + "Object-Oriented Analysis and Design and Iterative Development", + "Craig Larman", + "Applying UML and Patterns Description")); } public List load() { return designPatternBooks; } - } diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java index 1fb477638722..7eb81d1f0224 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java @@ -25,23 +25,17 @@ package com.iluwatar.model.view.viewmodel; import java.util.List; +import lombok.Getter; import org.zkoss.bind.annotation.Command; import org.zkoss.bind.annotation.NotifyChange; import org.zkoss.zk.ui.select.annotation.WireVariable; -/** - * BookViewModel class. - */ +/** BookViewModel class. */ public class BookViewModel { - - @WireVariable - private List bookList; - private Book selectedBook; + + @WireVariable private List bookList; + @Getter private Book selectedBook; private BookService bookService = new BookServiceImpl(); - - public Book getSelectedBook() { - return selectedBook; - } @NotifyChange("selectedBook") public void setSelectedBook(Book selectedBook) { @@ -51,11 +45,11 @@ public void setSelectedBook(Book selectedBook) { public List getBookList() { return bookService.load(); } - - /** Deleting a book. - * When event is triggered on click of Delete button, - * this method will be notified with the selected entry that will be referenced - * and used to delete the selected book from the list of books. + + /** + * Deleting a book. When event is triggered on click of Delete button, this method will be + * notified with the selected entry that will be referenced and used to delete the selected book + * from the list of books. */ @Command @NotifyChange({"selectedBook", "bookList"}) @@ -65,5 +59,4 @@ public void deleteBook() { selectedBook = null; } } - } diff --git a/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java b/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java index bec79d011171..71f3d8a5194c 100644 --- a/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java +++ b/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java @@ -30,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,27 +42,33 @@ class BookTest { List testBookList; Book testBookTwo; Book testBookThree; - + @BeforeEach void setUp() { bvm = new BookViewModel(); - testBook = new Book("Head First Design Patterns: A Brain-Friendly Guide", - "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", - "Head First Design Patterns Description"); + testBook = + new Book( + "Head First Design Patterns: A Brain-Friendly Guide", + "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", + "Head First Design Patterns Description"); testBookList = bvm.getBookList(); - testBookTwo = new Book("Head First Design Patterns: A Brain-Friendly Guide", - "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", - "Head First Design Patterns Description"); - testBookThree = new Book("Design Patterns: Elements of Reusable Object-Oriented Software", + testBookTwo = + new Book( + "Head First Design Patterns: A Brain-Friendly Guide", + "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", + "Head First Design Patterns Description"); + testBookThree = + new Book( + "Design Patterns: Elements of Reusable Object-Oriented Software", "Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides", "Design Patterns Description"); } @Test void testBookModel() { - assertNotNull(testBook); + assertNotNull(testBook); } - + @Test void testEquals() { assertEquals(testBook, testBookTwo); @@ -72,13 +79,13 @@ void testToString() { assertEquals(testBook.toString(), testBookTwo.toString()); assertNotEquals(testBook.toString(), testBookThree.toString()); } - + @Test void testHashCode() { assertTrue(testBook.equals(testBookTwo) && testBookTwo.equals(testBook)); assertEquals(testBook.hashCode(), testBookTwo.hashCode()); } - + @Test void testLoadData() { assertNotNull(testBookList); @@ -87,7 +94,7 @@ void testLoadData() { @Test void testSelectedData() { - bvm.setSelectedBook(testBook); + bvm.setSelectedBook(testBook); assertNotNull(bvm.getSelectedBook()); assertEquals(testBook.toString(), bvm.getSelectedBook().toString()); assertTrue(true, bvm.getSelectedBook().toString()); @@ -102,5 +109,4 @@ void testDeleteData() { assertNull(bvm.getSelectedBook()); assertFalse(testBookList.get(0).toString().contains("Head First Design Patterns")); } - -} \ No newline at end of file +} diff --git a/module/.gitignore b/module/.gitignore deleted file mode 100644 index ecfa5dd4617e..000000000000 --- a/module/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -error.txt -output.txt diff --git a/module/etc/module.ucls b/module/etc/module.ucls deleted file mode 100644 index d2519b856ae5..000000000000 --- a/module/etc/module.ucls +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/module/etc/module.urm.puml b/module/etc/module.urm.puml deleted file mode 100644 index b92446ca14e7..000000000000 --- a/module/etc/module.urm.puml +++ /dev/null @@ -1,43 +0,0 @@ -@startuml -package com.iluwatar.module { - class App { - + consoleLoggerModule : ConsoleLoggerModule {static} - + fileLoggerModule : FileLoggerModule {static} - + App() - + execute(args : String[]) {static} - + main(args : String[]) {static} - + prepare() {static} - + unprepare() {static} - } - class ConsoleLoggerModule { - - LOGGER : Logger {static} - + error : PrintStream - + output : PrintStream - - singleton : ConsoleLoggerModule {static} - - ConsoleLoggerModule() - + getSingleton() : ConsoleLoggerModule {static} - + prepare() : ConsoleLoggerModule - + printErrorString(value : String) - + printString(value : String) - + unprepare() - } - class FileLoggerModule { - - ERROR_FILE : String {static} - - LOGGER : Logger {static} - - OUTPUT_FILE : String {static} - + error : PrintStream - + output : PrintStream - - singleton : FileLoggerModule {static} - - FileLoggerModule() - + getSingleton() : FileLoggerModule {static} - + prepare() : FileLoggerModule - + printErrorString(value : String) - + printString(value : String) - + unprepare() - } -} -FileLoggerModule --> "-singleton" FileLoggerModule -App --> "-consoleLoggerModule" ConsoleLoggerModule -ConsoleLoggerModule --> "-singleton" ConsoleLoggerModule -App --> "-fileLoggerModule" FileLoggerModule -@enduml \ No newline at end of file diff --git a/module/src/main/java/com/iluwatar/module/App.java b/module/src/main/java/com/iluwatar/module/App.java deleted file mode 100644 index c0e03608b650..000000000000 --- a/module/src/main/java/com/iluwatar/module/App.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.module; - -import java.io.FileNotFoundException; - -/** - * The Module pattern can be considered a Creational pattern and a Structural pattern. It manages - * the creation and organization of other elements, and groups them as the structural pattern does. - * An object that applies this pattern can provide the equivalent of a namespace, providing the - * initialization and finalization process of a static class or a class with static members with - * cleaner, more concise syntax and semantics. - * - *

The below example demonstrates a use case for testing two different modules: File Logger and - * Console Logger - */ -public class App { - - private static final String ERROR = "Error"; - private static final String MESSAGE = "Message"; - public static FileLoggerModule fileLoggerModule; - public static ConsoleLoggerModule consoleLoggerModule; - - /** - * Following method performs the initialization. - * - * @throws FileNotFoundException if program is not able to find log files (output.txt and - * error.txt) - */ - public static void prepare() throws FileNotFoundException { - - /* Create new singleton objects and prepare their modules */ - fileLoggerModule = FileLoggerModule.getSingleton().prepare(); - consoleLoggerModule = ConsoleLoggerModule.getSingleton().prepare(); - } - - /** - * Following method performs the finalization. - */ - public static void unprepare() { - - /* Close all resources */ - fileLoggerModule.unprepare(); - consoleLoggerModule.unprepare(); - } - - /** - * Following method is main executor. - */ - public static void execute() { - - /* Send logs on file system */ - fileLoggerModule.printString(MESSAGE); - fileLoggerModule.printErrorString(ERROR); - - /* Send logs on console */ - consoleLoggerModule.printString(MESSAGE); - consoleLoggerModule.printErrorString(ERROR); - } - - /** - * Program entry point. - * - * @param args command line args. - * @throws FileNotFoundException if program is not able to find log files (output.txt and - * error.txt) - */ - public static void main(final String... args) throws FileNotFoundException { - prepare(); - execute(); - unprepare(); - } -} diff --git a/module/src/main/java/com/iluwatar/module/ConsoleLoggerModule.java b/module/src/main/java/com/iluwatar/module/ConsoleLoggerModule.java deleted file mode 100644 index c29e57c0eafa..000000000000 --- a/module/src/main/java/com/iluwatar/module/ConsoleLoggerModule.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.module; - -import java.io.PrintStream; -import lombok.extern.slf4j.Slf4j; - -/** - * The ConsoleLoggerModule is responsible for showing logs on System Console. - * - *

The below example demonstrates a Console logger module, which can print simple and error - * messages in two designated formats - */ -@Slf4j -public final class ConsoleLoggerModule { - - private static ConsoleLoggerModule singleton = null; - - public PrintStream output = null; - public PrintStream error = null; - - private ConsoleLoggerModule() { - } - - /** - * Static method to get single instance of class. - * - * @return singleton instance of ConsoleLoggerModule - */ - public static ConsoleLoggerModule getSingleton() { - - if (ConsoleLoggerModule.singleton == null) { - ConsoleLoggerModule.singleton = new ConsoleLoggerModule(); - } - - return ConsoleLoggerModule.singleton; - } - - /** - * Following method performs the initialization. - */ - public ConsoleLoggerModule prepare() { - - LOGGER.debug("ConsoleLoggerModule::prepare();"); - - this.output = new PrintStream(System.out); - this.error = new PrintStream(System.err); - - return this; - } - - /** - * Following method performs the finalization. - */ - public void unprepare() { - - if (this.output != null) { - - this.output.flush(); - this.output.close(); - } - - if (this.error != null) { - - this.error.flush(); - this.error.close(); - } - - LOGGER.debug("ConsoleLoggerModule::unprepare();"); - } - - /** - * Used to print a message. - * - * @param value will be printed on console - */ - public void printString(final String value) { - this.output.println(value); - } - - /** - * Used to print a error message. - * - * @param value will be printed on error console - */ - public void printErrorString(final String value) { - this.error.println(value); - } -} diff --git a/module/src/main/java/com/iluwatar/module/FileLoggerModule.java b/module/src/main/java/com/iluwatar/module/FileLoggerModule.java deleted file mode 100644 index 7a226b6436e4..000000000000 --- a/module/src/main/java/com/iluwatar/module/FileLoggerModule.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.module; - -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.PrintStream; -import lombok.extern.slf4j.Slf4j; - -/** - * The FileLoggerModule is responsible for showing logs on File System. - * - *

The below example demonstrates a File logger module, which can print simple and error - * messages in two designated files - */ -@Slf4j -public final class FileLoggerModule { - - private static FileLoggerModule singleton = null; - - private static final String OUTPUT_FILE = "output.txt"; - private static final String ERROR_FILE = "error.txt"; - - public PrintStream output = null; - public PrintStream error = null; - - private FileLoggerModule() { - } - - /** - * Static method to get single instance of class. - * - * @return singleton instance of FileLoggerModule - */ - public static FileLoggerModule getSingleton() { - - if (FileLoggerModule.singleton == null) { - FileLoggerModule.singleton = new FileLoggerModule(); - } - - return FileLoggerModule.singleton; - } - - /** - * Following method performs the initialization. - * - * @throws FileNotFoundException if program is not able to find log files (output.txt and - * error.txt) - */ - public FileLoggerModule prepare() throws FileNotFoundException { - - LOGGER.debug("FileLoggerModule::prepare();"); - - this.output = new PrintStream(new FileOutputStream(OUTPUT_FILE)); - this.error = new PrintStream(new FileOutputStream(ERROR_FILE)); - - return this; - } - - /** - * Following method performs the finalization. - */ - public void unprepare() { - - if (this.output != null) { - - this.output.flush(); - this.output.close(); - } - - if (this.error != null) { - - this.error.flush(); - this.error.close(); - } - - LOGGER.debug("FileLoggerModule::unprepare();"); - } - - /** - * Used to print a message. - * - * @param value will be printed in file - */ - public void printString(final String value) { - this.output.println(value); - } - - /** - * Used to print a error message. - * - * @param value will be printed on error file - */ - public void printErrorString(final String value) { - this.error.println(value); - } -} diff --git a/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java b/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java deleted file mode 100644 index 6d05566b45cf..000000000000 --- a/module/src/test/java/com/iluwatar/module/FileLoggerModuleTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.module; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; - -/** - * The Module pattern can be considered a Creational pattern and a Structural pattern. It manages - * the creation and organization of other elements, and groups them as the structural pattern does. - * An object that applies this pattern can provide the equivalent of a namespace, providing the - * initialization and finalization process of a static class or a class with static members with - * cleaner, more concise syntax and semantics. - *

- * The below example demonstrates a JUnit test for testing two different modules: File Logger and - * Console Logger - */ -@Slf4j -public final class FileLoggerModuleTest { - - private static final String OUTPUT_FILE = "output.txt"; - private static final String ERROR_FILE = "error.txt"; - - private static final String MESSAGE = "MESSAGE"; - private static final String ERROR = "ERROR"; - - - /** - * This test verify that 'MESSAGE' is perfectly printed in output file - * - * @throws IOException if program is not able to find log files (output.txt and error.txt) - */ - @Test - void testFileMessage() throws IOException { - - /* Get singleton instance of File Logger Module */ - final var fileLoggerModule = FileLoggerModule.getSingleton(); - - /* Prepare the essential sub modules, to perform the sequence of jobs */ - fileLoggerModule.prepare(); - - /* Print 'Message' in file */ - fileLoggerModule.printString(MESSAGE); - - /* Test if 'Message' is printed in file */ - assertEquals(readFirstLine(OUTPUT_FILE), MESSAGE); - - /* Unprepare to cleanup the modules */ - fileLoggerModule.unprepare(); - } - - /** - * This test verify that nothing is printed in output file - * - * @throws IOException if program is not able to find log files (output.txt and error.txt) - */ - @Test - void testNoFileMessage() throws IOException { - - /* Get singleton instance of File Logger Module */ - final var fileLoggerModule = FileLoggerModule.getSingleton(); - - /* Prepare the essential sub modules, to perform the sequence of jobs */ - fileLoggerModule.prepare(); - - /* Test if nothing is printed in file */ - assertNull(readFirstLine(OUTPUT_FILE)); - - /* Unprepare to cleanup the modules */ - fileLoggerModule.unprepare(); - } - - /** - * This test verify that 'ERROR' is perfectly printed in error file - * - * @throws FileNotFoundException if program is not able to find log files (output.txt and - * error.txt) - */ - @Test - void testFileErrorMessage() throws FileNotFoundException { - - /* Get singleton instance of File Logger Module */ - final var fileLoggerModule = FileLoggerModule.getSingleton(); - - /* Prepare the essential sub modules, to perform the sequence of jobs */ - fileLoggerModule.prepare(); - - /* Print 'Error' in file */ - fileLoggerModule.printErrorString(ERROR); - - /* Test if 'Message' is printed in file */ - assertEquals(ERROR, readFirstLine(ERROR_FILE)); - - /* Un-prepare to cleanup the modules */ - fileLoggerModule.unprepare(); - } - - /** - * This test verify that nothing is printed in error file - * - * @throws FileNotFoundException if program is not able to find log files (output.txt and - * error.txt) - */ - @Test - void testNoFileErrorMessage() throws FileNotFoundException { - - /* Get singleton instance of File Logger Module */ - final var fileLoggerModule = FileLoggerModule.getSingleton(); - - /* Prepare the essential sub modules, to perform the sequence of jobs */ - fileLoggerModule.prepare(); - - /* Test if nothing is printed in file */ - assertNull(readFirstLine(ERROR_FILE)); - - /* Unprepare to cleanup the modules */ - fileLoggerModule.unprepare(); - } - - /** - * Utility method to read first line of a file - * - * @param file as file name to be read - * @return a string value as first line in file - */ - private static String readFirstLine(final String file) { - - String firstLine = null; - try (var bufferedReader = new BufferedReader(new FileReader(file))) { - - while (bufferedReader.ready()) { - - /* Read the line */ - firstLine = bufferedReader.readLine(); - } - - LOGGER.info("ModuleTest::readFirstLine() : firstLine : " + firstLine); - - } catch (final IOException e) { - LOGGER.error("ModuleTest::readFirstLine()", e); - } - - return firstLine; - } -} diff --git a/monad/README.md b/monad/README.md index 9ccaaebb98ce..e63d830a7eb2 100644 --- a/monad/README.md +++ b/monad/README.md @@ -1,30 +1,39 @@ --- -title: Monad +title: "Monad Pattern in Java: Mastering Functional Programming Paradigms" +shortTitle: Monad +description: "Learn how to implement the Monad design pattern in Java for functional programming. Discover its benefits, real-world examples, and best practices to enhance code readability and error handling." category: Functional language: en tag: - - Reactive + - Abstraction + - Accumulation + - Decoupling + - Encapsulation + - Functional decomposition + - Generic + - Idiom + - Instantiation + - Interface + - Layered architecture + - Object composition --- -## Intent +## Also known as -Monad pattern based on monad from linear algebra represents the way of chaining operations -together step by step. Binding functions can be described as passing one's output to another's input -basing on the 'same type' contract. Formally, monad consists of a type constructor M and two -operations: -bind - that takes monadic object and a function from plain object to monadic value and returns monadic value -return - that takes plain type object and returns this object wrapped in a monadic value. +* Computation Wrapper +* Monadic Interface -## Explanation +## Intent of Monad Design Pattern -The Monad pattern provides a way to chain operations together and manage sequencing, -side effects, and exception handling in a consistent manner. +The Monad design pattern in Java provides a mechanism for encapsulating computations or side effects, enabling the chaining of operations while managing context and data flow in a side-effect-free manner. + +## Detailed Explanation of Monad Pattern with Real-World Examples Real-world example -> Consider a conveyor belt in a factory: items move from one station to another, -> and each station performs a specific task, with the assurance that every task will be carried out, -> even if some items are rejected at certain stations. +> Consider a real-world example of a monad in Java with a restaurant meal ordering process. This encapsulation and chaining allow for a clean, error-managed progression, similar to how monads handle data and operations in functional programming. In this scenario, each step of selecting a dish, adding sides, and choosing a drink can be thought of as a monadic operation. Each operation encapsulates the current state of the order (e.g., main dish chosen) and allows for the next choice (e.g., selecting a side) without exposing the complexity of the entire order's details to the customer. +> +> Just like in a functional monad, if any step fails (like an unavailable dish), the entire process can be halted or redirected without throwing exceptions, maintaining a smooth flow. This encapsulation and chaining allow for a clean, error-managed progression from choosing the main dish to completing the full meal order, akin to how monads handle data and operations in functional programming. This approach ensures a consistent experience, where every choice builds on the previous one in a controlled manner. In plain words @@ -32,23 +41,11 @@ In plain words Wikipedia says -> In functional programming, a monad is a structure that combines program fragments (functions) -> and wraps their return values in a type with additional computation. In addition to defining a -> wrapping monadic type, monads define two operators: one to wrap a value in the monad type, and -> another to compose together functions that output values of the monad type (these are known as -> monadic functions). General-purpose languages use monads to reduce boilerplate code needed for -> common operations (such as dealing with undefined values or fallible functions, or encapsulating -> bookkeeping code). Functional languages use monads to turn complicated sequences of functions into -> succinct pipelines that abstract away control flow, and side-effects. - -**Programmatic Example** +> In functional programming, a monad is a structure that combines program fragments (functions) and wraps their return values in a type with additional computation. In addition to defining a wrapping monadic type, monads define two operators: one to wrap a value in the monad type, and another to compose together functions that output values of the monad type (these are known as monadic functions). General-purpose languages use monads to reduce boilerplate code needed for common operations (such as dealing with undefined values or fallible functions, or encapsulating bookkeeping code). Functional languages use monads to turn complicated sequences of functions into succinct pipelines that abstract away control flow, and side effects. -Here’s the Monad implementation in Java. +## Programmatic Example of Monad Pattern in Java -The `Validator` takes an object, validates it against specified predicates, and collects any -validation errors. The `validate` method allows you to add validation steps, and the `get` method -either returns the validated object or throws an `IllegalStateException` with a list of validation -exceptions if any of the validation steps fail. +Here’s the Monad implementation in Java. The `Validator` class encapsulates an object and performs validation steps in a monadic fashion, showcasing the benefits of using the Monad pattern for error handling and state management. ```java public class Validator { @@ -107,8 +104,8 @@ And finally, a `User` object is validated for its name, email, and age using the ```java public static void main(String[] args) { - var user = new User("user", 24, Sex.FEMALE, "foobar.com"); - LOGGER.info(Validator.of(user).validate(User::name, Objects::nonNull, "name is null") + var user = new User("user", 24, Sex.FEMALE, "foobar.com"); + LOGGER.info(Validator.of(user).validate(User::name, Objects::nonNull, "name is null") .validate(User::name, name -> !name.isEmpty(), "name is empty") .validate(User::email, email -> !email.contains("@"), "email doesn't contains '@'") .validate(User::age, age -> age > 20 && age < 30, "age isn't between...").get() @@ -116,18 +113,57 @@ public static void main(String[] args) { } ``` -## Class diagram -![alt text](./etc/monad.png "Monad") +Console output: + +``` +15:06:17.679 [main] INFO com.iluwatar.monad.App -- User[name=user, age=24, sex=FEMALE, email=foobar.com] +``` + +## When to Use the Monad Pattern in Java + +The Monad design pattern is applicable when + +* Consistent and unified error handling is required without relying on exceptions, particularly in functional programming paradigms. +* Asynchronous computations need clear and maintainable chaining. +* State needs to be managed and encapsulated within functional flows. +* Dependencies and lazy evaluations are to be handled cleanly and efficiently. + +## Monad Pattern Java Tutorials + +* [Design Pattern Reloaded (Remi Forax)](https://youtu.be/-k2X7guaArU) + +## Real-World Applications of Monad Pattern in Java + +* Optional in Java's standard library for handling potential absence of values. +* Stream for constructing functional pipelines to operate on collections. +* Frameworks like Vavr enhance functional programming in Java by providing monadic constructs for better code maintainability. + +## Benefits and Trade-offs of Monad Pattern + +Benefits: + +* Increases code readability and reduces boilerplate. +* Encourages a declarative programming style. +* Promotes immutability and thread safety. +* Simplifies complex error handling and state management. + +Trade-offs: + +* Can be challenging for developers new to functional programming. +* May introduce performance overhead due to additional abstraction layers. +* Debugging can be difficult due to less transparent operational flow. -## Applicability +## Related Java Design Patterns -Use the Monad in any of the following situations +Related design patterns to monads in Java include -* When you want to chain operations easily -* When you want to apply each function regardless of the result of any of them +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Similar in that it helps instantiate monads, encapsulating object creation logic. +* [Command](https://java-design-patterns.com/patterns/command/): Also encapsulates operations, but monads add context management to the mix. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Dynamically enhances functionalities, whereas monads use static typing for consistent composability. -## Credits +## References and Credits -* [Design Pattern Reloaded by Remi Forax](https://youtu.be/-k2X7guaArU) -* [Brian Beckman: Don't fear the Monad](https://channel9.msdn.com/Shows/Going+Deep/Brian-Beckman-Dont-fear-the-Monads) -* [Monad on Wikipedia](https://en.wikipedia.org/wiki/Monad_(functional_programming)) +* [Functional Programming in Java](https://amzn.to/3JUIc5Q) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3QCmGXs) +* [Real-World Software Development: A Project-Driven Guide to Fundamentals in Java](https://amzn.to/4btoN7U) +* [Monad (functional programming) (Wikipedia)](https://en.wikipedia.org/wiki/Monad_(functional_programming)) diff --git a/monad/pom.xml b/monad/pom.xml index e48e6538b7d8..7837b60c846a 100644 --- a/monad/pom.xml +++ b/monad/pom.xml @@ -34,6 +34,14 @@ monad + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/monad/src/main/java/com/iluwatar/monad/App.java b/monad/src/main/java/com/iluwatar/monad/App.java index 7ee04f1b8c31..56e1c59dae42 100644 --- a/monad/src/main/java/com/iluwatar/monad/App.java +++ b/monad/src/main/java/com/iluwatar/monad/App.java @@ -1,63 +1,66 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.monad; - -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import lombok.extern.slf4j.Slf4j; - -/** - * The Monad pattern defines a monad structure, that enables chaining operations in pipelines and - * processing data step by step. Formally, monad consists of a type constructor M and two - * operations: - *
bind - that takes monadic object and a function from plain object to the - * monadic value and returns monadic value. - *
return - that takes plain type object and returns this object wrapped in a monadic value. - * - *

In the given example, the Monad pattern is represented as a {@link Validator} that takes an - * instance of a plain object with {@link Validator#of(Object)} and validates it {@link - * Validator#validate(Function, Predicate, String)} against given predicates. - * - *

As a validation result {@link Validator#get()} either returns valid object - * or throws {@link IllegalStateException} with list of exceptions collected during validation. - */ -@Slf4j -public class App { - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - var user = new User("user", 24, Sex.FEMALE, "foobar.com"); - LOGGER.info(Validator.of(user).validate(User::name, Objects::nonNull, "name is null") - .validate(User::name, name -> !name.isEmpty(), "name is empty") - .validate(User::email, email -> !email.contains("@"), "email doesn't contains '@'") - .validate(User::age, age -> age > 20 && age < 30, "age isn't between...").get() - .toString()); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monad; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import lombok.extern.slf4j.Slf4j; + +/** + * The Monad pattern defines a monad structure, that enables chaining operations in pipelines and + * processing data step by step. Formally, monad consists of a type constructor M and two + * operations:
+ * bind - that takes monadic object and a function from plain object to the monadic value and + * returns monadic value.
+ * return - that takes plain type object and returns this object wrapped in a monadic value. + * + *

In the given example, the Monad pattern is represented as a {@link Validator} that takes an + * instance of a plain object with {@link Validator#of(Object)} and validates it {@link + * Validator#validate(Function, Predicate, String)} against given predicates. + * + *

As a validation result {@link Validator#get()} either returns valid object or throws {@link + * IllegalStateException} with list of exceptions collected during validation. + */ +@Slf4j +public class App { + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + var user = new User("user", 24, Sex.FEMALE, "foobar.com"); + LOGGER.info( + Validator.of(user) + .validate(User::name, Objects::nonNull, "name is null") + .validate(User::name, name -> !name.isEmpty(), "name is empty") + .validate(User::email, email -> !email.contains("@"), "email doesn't contains '@'") + .validate(User::age, age -> age > 20 && age < 30, "age isn't between...") + .get() + .toString()); + } +} diff --git a/monad/src/main/java/com/iluwatar/monad/Sex.java b/monad/src/main/java/com/iluwatar/monad/Sex.java index 5979187a57ff..7a5189f53629 100644 --- a/monad/src/main/java/com/iluwatar/monad/Sex.java +++ b/monad/src/main/java/com/iluwatar/monad/Sex.java @@ -1,32 +1,31 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.monad; - -/** - * Enumeration of Types of Sex. - */ -public enum Sex { - MALE, FEMALE -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monad; + +/** Enumeration of Types of Sex. */ +public enum Sex { + MALE, + FEMALE +} diff --git a/monad/src/main/java/com/iluwatar/monad/User.java b/monad/src/main/java/com/iluwatar/monad/User.java index 089279a8db59..dd419a36dbe8 100644 --- a/monad/src/main/java/com/iluwatar/monad/User.java +++ b/monad/src/main/java/com/iluwatar/monad/User.java @@ -27,11 +27,9 @@ /** * Record class. * - * @param name - name - * @param age - age - * @param sex - sex + * @param name - name + * @param age - age + * @param sex - sex * @param email - email address */ -public record User(String name, int age, Sex sex, String email) { -} - +public record User(String name, int age, Sex sex, String email) {} diff --git a/monad/src/main/java/com/iluwatar/monad/Validator.java b/monad/src/main/java/com/iluwatar/monad/Validator.java index 757343e75608..0aef2f67c02a 100644 --- a/monad/src/main/java/com/iluwatar/monad/Validator.java +++ b/monad/src/main/java/com/iluwatar/monad/Validator.java @@ -1,121 +1,116 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.monad; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; - -/** - * Class representing Monad design pattern. Monad is a way of chaining operations on the given - * object together step by step. In Validator each step results in either success or failure - * indicator, giving a way of receiving each of them easily and finally getting validated object or - * list of exceptions. - * - * @param Placeholder for an object. - */ -public class Validator { - /** - * Object that is validated. - */ - private final T obj; - - /** - * List of exception thrown during validation. - */ - private final List exceptions = new ArrayList<>(); - - /** - * Creates a monadic value of given object. - * - * @param obj object to be validated - */ - private Validator(T obj) { - this.obj = obj; - } - - /** - * Creates validator against given object. - * - * @param t object to be validated - * @param object's type - * @return new instance of a validator - */ - public static Validator of(T t) { - return new Validator<>(Objects.requireNonNull(t)); - } - - /** - * Checks if the validation is successful. - * - * @param validation one argument boolean-valued function that represents one step of validation. - * Adds exception to main validation exception list when single step validation - * ends with failure. - * @param message error message when object is invalid - * @return this - */ - public Validator validate(Predicate validation, String message) { - if (!validation.test(obj)) { - exceptions.add(new IllegalStateException(message)); - } - return this; - } - - /** - * Extension for the {@link Validator#validate(Predicate, String)} method, dedicated for objects, - * that need to be projected before requested validation. - * - * @param projection function that gets an objects, and returns projection representing element to - * be validated. - * @param validation see {@link Validator#validate(Predicate, String)} - * @param message see {@link Validator#validate(Predicate, String)} - * @param see {@link Validator#validate(Predicate, String)} - * @return this - */ - public Validator validate( - Function projection, - Predicate validation, - String message - ) { - return validate(projection.andThen(validation::test)::apply, message); - } - - /** - * Receives validated object or throws exception when invalid. - * - * @return object that was validated - * @throws IllegalStateException when any validation step results with failure - */ - public T get() throws IllegalStateException { - if (exceptions.isEmpty()) { - return obj; - } - var e = new IllegalStateException(); - exceptions.forEach(e::addSuppressed); - throw e; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monad; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Class representing Monad design pattern. Monad is a way of chaining operations on the given + * object together step by step. In Validator each step results in either success or failure + * indicator, giving a way of receiving each of them easily and finally getting validated object or + * list of exceptions. + * + * @param Placeholder for an object. + */ +public class Validator { + /** Object that is validated. */ + private final T obj; + + /** List of exception thrown during validation. */ + private final List exceptions = new ArrayList<>(); + + /** + * Creates a monadic value of given object. + * + * @param obj object to be validated + */ + private Validator(T obj) { + this.obj = obj; + } + + /** + * Creates validator against given object. + * + * @param t object to be validated + * @param object's type + * @return new instance of a validator + */ + public static Validator of(T t) { + return new Validator<>(Objects.requireNonNull(t)); + } + + /** + * Checks if the validation is successful. + * + * @param validation one argument boolean-valued function that represents one step of validation. + * Adds exception to main validation exception list when single step validation ends with + * failure. + * @param message error message when object is invalid + * @return this + */ + public Validator validate(Predicate validation, String message) { + if (!validation.test(obj)) { + exceptions.add(new IllegalStateException(message)); + } + return this; + } + + /** + * Extension for the {@link Validator#validate(Predicate, String)} method, dedicated for objects, + * that need to be projected before requested validation. + * + * @param projection function that gets an objects, and returns projection representing element to + * be validated. + * @param validation see {@link Validator#validate(Predicate, String)} + * @param message see {@link Validator#validate(Predicate, String)} + * @param see {@link Validator#validate(Predicate, String)} + * @return this + */ + public Validator validate( + Function projection, + Predicate validation, + String message) { + return validate(projection.andThen(validation::test)::apply, message); + } + + /** + * Receives validated object or throws exception when invalid. + * + * @return object that was validated + * @throws IllegalStateException when any validation step results with failure + */ + public T get() throws IllegalStateException { + if (exceptions.isEmpty()) { + return obj; + } + var e = new IllegalStateException(); + exceptions.forEach(e::addSuppressed); + throw e; + } +} diff --git a/monad/src/test/java/com/iluwatar/monad/AppTest.java b/monad/src/test/java/com/iluwatar/monad/AppTest.java index 864ede813a54..9320d4b1c440 100644 --- a/monad/src/test/java/com/iluwatar/monad/AppTest.java +++ b/monad/src/test/java/com/iluwatar/monad/AppTest.java @@ -28,15 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application Test - */ - +/** Application Test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/monad/src/test/java/com/iluwatar/monad/MonadTest.java b/monad/src/test/java/com/iluwatar/monad/MonadTest.java index 1eed42b12bc4..58916488491f 100644 --- a/monad/src/test/java/com/iluwatar/monad/MonadTest.java +++ b/monad/src/test/java/com/iluwatar/monad/MonadTest.java @@ -30,9 +30,7 @@ import java.util.Objects; import org.junit.jupiter.api.Test; -/** - * Test for Monad Pattern - */ +/** Test for Monad Pattern */ class MonadTest { @Test @@ -40,10 +38,8 @@ void testForInvalidName() { var tom = new User(null, 21, Sex.MALE, "tom@foo.bar"); assertThrows( IllegalStateException.class, - () -> Validator.of(tom) - .validate(User::name, Objects::nonNull, "name cannot be null") - .get() - ); + () -> + Validator.of(tom).validate(User::name, Objects::nonNull, "name cannot be null").get()); } @Test @@ -51,22 +47,23 @@ void testForInvalidAge() { var john = new User("John", 17, Sex.MALE, "john@qwe.bar"); assertThrows( IllegalStateException.class, - () -> Validator.of(john) - .validate(User::name, Objects::nonNull, "name cannot be null") - .validate(User::age, age -> age > 21, "user is underage") - .get() - ); + () -> + Validator.of(john) + .validate(User::name, Objects::nonNull, "name cannot be null") + .validate(User::age, age -> age > 21, "user is underage") + .get()); } @Test void testForValid() { var sarah = new User("Sarah", 42, Sex.FEMALE, "sarah@det.org"); - var validated = Validator.of(sarah) - .validate(User::name, Objects::nonNull, "name cannot be null") - .validate(User::age, age -> age > 21, "user is underage") - .validate(User::sex, sex -> sex == Sex.FEMALE, "user is not female") - .validate(User::email, email -> email.contains("@"), "email does not contain @ sign") - .get(); + var validated = + Validator.of(sarah) + .validate(User::name, Objects::nonNull, "name cannot be null") + .validate(User::age, age -> age > 21, "user is underage") + .validate(User::sex, sex -> sex == Sex.FEMALE, "user is not female") + .validate(User::email, email -> email.contains("@"), "email does not contain @ sign") + .get(); assertSame(validated, sarah); } } diff --git a/money/README.md b/money/README.md new file mode 100644 index 000000000000..ca64d68fe882 --- /dev/null +++ b/money/README.md @@ -0,0 +1,168 @@ +--- +title: "Money Pattern in Java: Encapsulating Monetary Values with Currency Consistency" +shortTitle: Money +description: "Learn how the Money design pattern in Java ensures currency safety, precision handling, and maintainable financial operations. Explore examples, applicability, and benefits of the pattern." +category: Behavioral +language: en +tag: + - Encapsulation + - Precision handling + - Currency safety + - Value Object + - Financial operations + - Currency + - Financial + - Immutable + - Value Object +--- + +## Also known as + +* Monetary Value Object + +## Intent of Money Design Pattern + +The Money design pattern provides a robust way to encapsulate monetary values and their associated currencies. It ensures precise calculations, currency consistency, and maintainability of financial logic in Java applications. + +## Detailed Explanation of Money Pattern with Real-World Examples + +### Real-world example + +> Imagine an e-commerce platform where customers shop in their local currencies. The platform needs to calculate order totals, taxes, and discounts accurately while handling multiple currencies seamlessly. + +In this example: +- Each monetary value (like a product price or tax amount) is encapsulated in a `Money` object. +- The `Money` class ensures that only values in the same currency are combined and supports safe currency conversion for global operations. + +### In plain words + +> The Money pattern encapsulates both an amount and its currency, ensuring financial operations are precise, consistent, and maintainable. + +### Wikipedia says + +> "The Money design pattern encapsulates a monetary value and its currency, allowing for safe arithmetic operations and conversions while preserving accuracy and consistency in financial calculations." + +## Programmatic Example of Money Pattern in Java + +### Money Class + +```java + +/** + * Represents a monetary value with an associated currency. + * Provides operations for basic arithmetic (addition, subtraction, multiplication), + * as well as currency conversion while ensuring proper rounding. + */ +@Getter +public class Money { + private @Getter double amount; + private @Getter String currency; + + public Money(double amnt, String curr) { + this.amount = amnt; + this.currency = curr; + } + + private double roundToTwoDecimals(double value) { + return Math.round(value * 100.0) / 100.0; + } + + public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException { + if (!moneyToBeAdded.getCurrency().equals(this.currency)) { + throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies"); + } + this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount()); + } + + public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException { + if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) { + throw new CannotSubtractException("You are trying to subtract two different currencies"); + } else if (moneyToBeSubtracted.getAmount() > this.amount) { + throw new CannotSubtractException("The amount you are trying to subtract is larger than the amount you have"); + } + this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount()); + } + + public void multiply(int factor) { + if (factor < 0) { + throw new IllegalArgumentException("Factor must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * factor); + } + + public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) { + if (exchangeRate < 0) { + throw new IllegalArgumentException("Exchange rate must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * exchangeRate); + this.currency = currencyToChangeTo; + } +} + +## When to Use the Money Pattern + +The Money pattern should be used in scenarios where: + +1. **Currency-safe arithmetic operations** + To ensure that arithmetic operations like addition, subtraction, and multiplication are performed only between amounts in the same currency, preventing inconsistencies or errors in calculations. + +2. **Accurate rounding for financial calculations** + Precise rounding to two decimal places is critical to maintain accuracy and consistency in financial systems. + +3. **Consistent currency conversion** + When handling international transactions or displaying monetary values in different currencies, the Money pattern facilitates easy and reliable conversion using exchange rates. + +4. **Encapsulation of monetary logic** + By encapsulating all monetary operations within a dedicated class, the Money pattern improves maintainability and reduces the likelihood of errors. + +5. **Preventing errors in financial operations** + Strict validation ensures that operations like subtraction or multiplication are only performed when conditions are met, safeguarding against misuse or logical errors. + +6. **Handling diverse scenarios in financial systems** + Useful in complex systems like e-commerce, banking, and payroll applications where precise and consistent monetary value handling is crucial. + +--- +## Benefits and Trade-offs of Money Pattern + +### Benefits +1. **Precision and Accuracy** + The Money pattern ensures precise handling of monetary values, reducing the risk of rounding errors. + +2. **Encapsulation of Business Logic** + By encapsulating monetary operations, the pattern enhances maintainability and reduces redundancy in financial systems. + +3. **Currency Safety** + It ensures operations are performed only between amounts of the same currency, avoiding logical errors. + +4. **Improved Readability** + By abstracting monetary logic into a dedicated class, the code becomes easier to read and maintain. + +5. **Ease of Extension** + Adding new operations, handling different currencies, or incorporating additional business rules is straightforward. + +### Trade-offs +1. **Increased Complexity** + Introducing a dedicated `Money` class can add some overhead, especially for small or simple projects. + +2. **Potential for Misuse** + Without proper validation and handling, incorrect usage of the Money pattern may introduce subtle bugs. + +3. **Performance Overhead** + Precision and encapsulation might slightly affect performance in systems with extremely high transaction volumes. + +--- + +## Related Design Patterns + +1. **Value Object** + Money is a classic example of the Value Object pattern, where objects are immutable and define equality based on their value. + Link:https://martinfowler.com/bliki/ValueObject.html +2. **Factory Method** + Factories can be employed to handle creation logic, such as applying default exchange rates or rounding rules. + Link:https://www.geeksforgeeks.org/factory-method-for-designing-pattern/ +--- + +## References and Credits + +- [Patterns of Enterprise Application Architecture](https://martinfowler.com/eaaCatalog/money.html) by Martin Fowler +- [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) diff --git a/money/etc/money.urm.puml b/money/etc/money.urm.puml new file mode 100644 index 000000000000..3567f5b7d63f --- /dev/null +++ b/money/etc/money.urm.puml @@ -0,0 +1,21 @@ +@startuml +package com.iluwatar { + class App { + - logger : Logger {static} + + App() + + main(args : String[]) {static} + } + class Money { + - amount : double + - currency : String + + Money(amnt : double, curr : String) + + addMoney(moneyToBeAdded : Money) + + exchangeCurrency(currencyToChangeTo : String, exchangeRate : double) + + getAmount() : double + + getCurrency() : String + + multiply(factor : int) + - roundToTwoDecimals(value : double) : double + + subtractMoney(moneyToBeSubtracted : Money) + } +} +@enduml \ No newline at end of file diff --git a/money/pom.xml b/money/pom.xml new file mode 100644 index 000000000000..fbe6296465d9 --- /dev/null +++ b/money/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + money + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.App + + + + + + + + + + \ No newline at end of file diff --git a/money/src/main/java/com/iluwatar/App.java b/money/src/main/java/com/iluwatar/App.java new file mode 100644 index 000000000000..3a31b8b7dc83 --- /dev/null +++ b/money/src/main/java/com/iluwatar/App.java @@ -0,0 +1,92 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The `App` class demonstrates the functionality of the {@link Money} class, which encapsulates + * monetary values and their associated currencies. It showcases operations like addition, + * subtraction, multiplication, and currency conversion, while ensuring validation and immutability. + * + *

Through this example, the handling of invalid operations (e.g., mismatched currencies or + * invalid inputs) is demonstrated using custom exceptions. Logging is used for transparency. + * + *

This highlights the practical application of object-oriented principles such as encapsulation + * and validation in a financial context. + */ +public class App { + + // Initialize the logger + private static final Logger logger = Logger.getLogger(App.class.getName()); + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + // Create instances of Money + Money usdAmount1 = new Money(50.00, "USD"); + Money usdAmount2 = new Money(20.00, "USD"); + + // Demonstrate addition + try { + usdAmount1.addMoney(usdAmount2); + logger.log(Level.INFO, "Sum in USD: {0}", usdAmount1.getAmount()); + } catch (CannotAddTwoCurrienciesException e) { + logger.log(Level.SEVERE, "Error adding money: {0}", e.getMessage()); + } + + // Demonstrate subtraction + try { + usdAmount1.subtractMoney(usdAmount2); + logger.log(Level.INFO, "Difference in USD: {0}", usdAmount1.getAmount()); + } catch (CannotSubtractException e) { + logger.log(Level.SEVERE, "Error subtracting money: {0}", e.getMessage()); + } + + // Demonstrate multiplication + try { + usdAmount1.multiply(2); + logger.log(Level.INFO, "Multiplied Amount in USD: {0}", usdAmount1.getAmount()); + } catch (IllegalArgumentException e) { + logger.log(Level.SEVERE, "Error multiplying money: {0}", e.getMessage()); + } + + // Demonstrate currency conversion + try { + double exchangeRateUsdToEur = 0.85; // Example exchange rate + usdAmount1.exchangeCurrency("EUR", exchangeRateUsdToEur); + logger.log( + Level.INFO, + "USD converted to EUR: {0} {1}", + new Object[] {usdAmount1.getAmount(), usdAmount1.getCurrency()}); + } catch (IllegalArgumentException e) { + logger.log(Level.SEVERE, "Error converting currency: {0}", e.getMessage()); + } + } +} diff --git a/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java b/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java new file mode 100644 index 000000000000..0222055584bc --- /dev/null +++ b/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +/** An exception for when the user tries to add two diffrent currencies. */ +public class CannotAddTwoCurrienciesException extends Exception { + /** + * Constructs an exception with the specified message. + * + * @param message the message shown in the terminal (as a String). + */ + public CannotAddTwoCurrienciesException(String message) { + super(message); + } +} diff --git a/money/src/main/java/com/iluwatar/CannotSubtractException.java b/money/src/main/java/com/iluwatar/CannotSubtractException.java new file mode 100644 index 000000000000..f0403415d05e --- /dev/null +++ b/money/src/main/java/com/iluwatar/CannotSubtractException.java @@ -0,0 +1,40 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +/** + * An exception for when the user tries to subtract two different currencies or subtract an amount + * he doesn't have. + */ +public class CannotSubtractException extends Exception { + /** + * Constructs an exception with the specified message. + * + * @param message the message shown in the terminal (as a String). + */ + public CannotSubtractException(String message) { + super(message); + } +} diff --git a/money/src/main/java/com/iluwatar/Money.java b/money/src/main/java/com/iluwatar/Money.java new file mode 100644 index 000000000000..a486c4b4ca5b --- /dev/null +++ b/money/src/main/java/com/iluwatar/Money.java @@ -0,0 +1,117 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import lombok.Getter; + +/** + * Represents a monetary value with an associated currency. Provides operations for basic arithmetic + * (addition, subtraction, multiplication), as well as currency conversion while ensuring proper + * rounding. + */ +@Getter +public class Money { + private @Getter double amount; + private @Getter String currency; + + /** + * Constructs a Money object with the specified amount and currency. + * + * @param amnt the amount of money (as a double). + * @param curr the currency code (e.g., "USD", "EUR"). + */ + public Money(double amnt, String curr) { + this.amount = amnt; + this.currency = curr; + } + + /** + * Rounds the given value to two decimal places. + * + * @param value the value to round. + * @return the rounded value, up to two decimal places. + */ + private double roundToTwoDecimals(double value) { + return Math.round(value * 100.0) / 100.0; + } + + /** + * Adds another Money object to the current instance. + * + * @param moneyToBeAdded the Money object to add. + * @throws CannotAddTwoCurrienciesException if the currencies do not match. + */ + public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException { + if (!moneyToBeAdded.getCurrency().equals(this.currency)) { + throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies"); + } + this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount()); + } + + /** + * Subtracts another Money object from the current instance. + * + * @param moneyToBeSubtracted the Money object to subtract. + * @throws CannotSubtractException if the currencies do not match or if the amount to subtract is + * larger than the current amount. + */ + public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException { + if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) { + throw new CannotSubtractException("You are trying to subtract two different currencies"); + } else if (moneyToBeSubtracted.getAmount() > this.amount) { + throw new CannotSubtractException( + "The amount you are trying to subtract is larger than the amount you have"); + } + this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount()); + } + + /** + * Multiplies the current amount of money by a factor. + * + * @param factor the factor to multiply by. + * @throws IllegalArgumentException if the factor is negative. + */ + public void multiply(int factor) { + if (factor < 0) { + throw new IllegalArgumentException("Factor must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * factor); + } + + /** + * Converts the current amount of money to another currency using the provided exchange rate. + * + * @param currencyToChangeTo the new currency to convert to. + * @param exchangeRate the exchange rate to convert from the current currency to the new currency. + * @throws IllegalArgumentException if the exchange rate is negative. + */ + public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) { + if (exchangeRate < 0) { + throw new IllegalArgumentException("Exchange rate must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * exchangeRate); + this.currency = currencyToChangeTo; + } +} diff --git a/money/src/test/java/com/iluwater/money/MoneyTest.java b/money/src/test/java/com/iluwater/money/MoneyTest.java new file mode 100644 index 000000000000..491b12ee2e13 --- /dev/null +++ b/money/src/test/java/com/iluwater/money/MoneyTest.java @@ -0,0 +1,159 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwater.money; + +import static org.junit.jupiter.api.Assertions.*; + +import com.iluwatar.App; +import com.iluwatar.CannotAddTwoCurrienciesException; +import com.iluwatar.CannotSubtractException; +import com.iluwatar.Money; +import org.junit.jupiter.api.Test; + +class MoneyTest { + + @Test + void testConstructor() { + // Test the constructor + Money money = new Money(100.00, "USD"); + assertEquals(100.00, money.getAmount()); + assertEquals("USD", money.getCurrency()); + } + + @Test + void testAddMoney_SameCurrency() throws CannotAddTwoCurrienciesException { + // Test adding two Money objects with the same currency + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "USD"); + + money1.addMoney(money2); + + assertEquals(150.25, money1.getAmount(), "Amount after addition should be 150.25"); + } + + @Test + void testAddMoney_DifferentCurrency() { + // Test adding two Money objects with different currencies + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "EUR"); + + assertThrows( + CannotAddTwoCurrienciesException.class, + () -> { + money1.addMoney(money2); + }); + } + + @Test + void testSubtractMoney_SameCurrency() throws CannotSubtractException { + // Test subtracting two Money objects with the same currency + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "USD"); + + money1.subtractMoney(money2); + + assertEquals(49.75, money1.getAmount(), "Amount after subtraction should be 49.75"); + } + + @Test + void testSubtractMoney_DifferentCurrency() { + // Test subtracting two Money objects with different currencies + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "EUR"); + + assertThrows( + CannotSubtractException.class, + () -> { + money1.subtractMoney(money2); + }); + } + + @Test + void testSubtractMoney_AmountTooLarge() { + // Test subtracting an amount larger than the current amount + Money money1 = new Money(50.00, "USD"); + Money money2 = new Money(60.00, "USD"); + + assertThrows( + CannotSubtractException.class, + () -> { + money1.subtractMoney(money2); + }); + } + + @Test + void testMultiply() { + // Test multiplying the money amount by a factor + Money money = new Money(100.00, "USD"); + + money.multiply(3); + + assertEquals(300.00, money.getAmount(), "Amount after multiplication should be 300.00"); + } + + @Test + void testMultiply_NegativeFactor() { + // Test multiplying by a negative factor + Money money = new Money(100.00, "USD"); + + assertThrows( + IllegalArgumentException.class, + () -> { + money.multiply(-2); + }); + } + + @Test + void testExchangeCurrency() { + // Test converting currency using an exchange rate + Money money = new Money(100.00, "USD"); + + money.exchangeCurrency("EUR", 0.85); + + assertEquals("EUR", money.getCurrency(), "Currency after conversion should be EUR"); + assertEquals(85.00, money.getAmount(), "Amount after conversion should be 85.00"); + } + + @Test + void testExchangeCurrency_NegativeExchangeRate() { + // Test converting currency with a negative exchange rate + Money money = new Money(100.00, "USD"); + + assertThrows( + IllegalArgumentException.class, + () -> { + money.exchangeCurrency("EUR", -0.85); + }); + } + + @Test + void testAppExecution() { + assertDoesNotThrow( + () -> { + App.main(new String[] {}); + }, + "App execution should not throw any exceptions"); + } +} diff --git a/monitor/README.md b/monitor/README.md index 25aa39f2229f..46d5b686721b 100644 --- a/monitor/README.md +++ b/monitor/README.md @@ -1,22 +1,32 @@ --- -title: Monitor +title: "Monitor Pattern in Java: Implementing Robust Locking Mechanisms with Monitors" +shortTitle: Monitor +description: "Learn how the Monitor design pattern in Java ensures thread safety and synchronization. Explore examples, applicability, and benefits of using monitors in concurrent programming." category: Concurrency language: en tag: - - Performance + - Encapsulation + - Fault tolerance + - Isolation + - Synchronization + - Thread management --- ## Also known as -Monitor object pattern +* Synchronized Block -## Intent +## Intent of Monitor Design Pattern -The primary intent is to provide a structured and controlled way for multiple threads or processes to safely access and -manipulate shared resources, such as variables, data structures, or critical sections of code, without causing conflicts -or race conditions. +The Monitor design pattern in Java is essential for synchronizing concurrent operations, ensuring thread safety and preventing race conditions. -## Explanation +## Detailed Explanation of Monitor Pattern with Real-World Examples + +Real-world example + +> Imagine a shared office printer that several employees need to use. The printer can only handle one print job at a time to avoid mixing up pages from different documents. This scenario is analogous to the Monitor design pattern in programming. +> +> In this example, the printer represents the shared resource, and the employees are analogous to threads. A system is set up where each employee must request access to the printer before starting their print job. This system ensures that only one employee (or "thread") can use the printer at a time, preventing any overlap or interference between jobs. Once a print job is complete, the next employee in the queue can access the printer. This mechanism mirrors the Monitor pattern's way of controlling access to a shared resource, ensuring orderly and safe use by multiple "threads" (employees). In plain words @@ -26,53 +36,90 @@ Wikipedia says > In concurrent programming (also known as parallel programming), a monitor is a synchronization construct that allows threads to have both mutual exclusion and the ability to wait (block) for a certain condition to become false. Monitors also have a mechanism for signaling other threads that their condition has been met. -**Programmatic Examples** +## Programmatic Example of Monitor Pattern in Java -Consider there is a bank that transfers money from an account to another account with transfer method . it is `synchronized` mean just one thread can access to this method because if many threads access to it and transfer money from an account to another account in same time balance changed ! - -``` -class Bank { - - private int[] accounts; - Logger logger; - - public Bank(int accountNum, int baseAmount, Logger logger) { - this.logger = logger; - accounts = new int[accountNum]; - Arrays.fill(accounts, baseAmount); - } - - public synchronized void transfer(int accountA, int accountB, int amount) { - if (accounts[accountA] >= amount) { - accounts[accountB] += amount; - accounts[accountA] -= amount; - logger.info("Transferred from account :" + accountA + " to account :" + accountB + " , amount :" + amount + " . balance :" + getBalance()); - } - } -``` +The Monitor design pattern is a synchronization technique used in concurrent programming to ensure that only one thread can execute a particular section of code at a time. It is a method of wrapping and hiding the synchronization primitives (like semaphores or locks) within the methods of an object. This pattern is useful in situations where race conditions could occur. + +The Java Monitor design pattern can be seen in the `Bank` class example. By using synchronized methods, the `Bank` class ensures that only one thread can perform transactions at any given time, illustrating effective use of the Monitor pattern in real-world applications. + +Here is a simplified version of the `Bank` class with additional comments: + +```java +public class Bank { + + @Getter + private final int[] accounts; + + public Bank(int accountNum, int baseAmount) { + accounts = new int[accountNum]; + Arrays.fill(accounts, baseAmount); + } + + public synchronized void transfer(int accountA, int accountB, int amount) { + // Only one thread can execute this method at a time due to the 'synchronized' keyword. + if (accounts[accountA] >= amount && accountA != accountB) { + accounts[accountB] += amount; + accounts[accountA] -= amount; + } + } -getBalance always return total amount and the total amount should be same after each transfers + public synchronized int getBalance() { + // Only one thread can execute this method at a time due to the 'synchronized' keyword. + int balance = 0; + for (int account : accounts) { + balance += account; + } + return balance; + } + public synchronized int getBalance(int accountNumber) { + // Only one thread can execute this method at a time due to the 'synchronized' keyword. + return accounts[accountNumber]; + } +} ``` - private synchronized int getBalance() { - int balance = 0; - for (int account : accounts) { - balance += account; - } - return balance; - } - } + +In the `Main` class, multiple threads are created to perform transactions on the bank accounts. The `Bank` class, acting as a monitor, ensures that these transactions are performed in a thread-safe manner. + +```java +public class Main { + + private static final int NUMBER_OF_THREADS = 5; + private static final int BASE_AMOUNT = 1000; + private static final int ACCOUNT_NUM = 4; + + public static void runner(Bank bank, CountDownLatch latch) { + try { + SecureRandom random = new SecureRandom(); + Thread.sleep(random.nextInt(1000)); + for (int i = 0; i < 1000000; i++) { + bank.transfer(random.nextInt(4), random.nextInt(4), random.nextInt(0, BASE_AMOUNT)); + } + latch.countDown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public static void main(String[] args) throws InterruptedException { + var bank = new Bank(ACCOUNT_NUM, BASE_AMOUNT); + var latch = new CountDownLatch(NUMBER_OF_THREADS); + var executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS); + + for (int i = 0; i < NUMBER_OF_THREADS; i++) { + executorService.execute(() -> runner(bank, latch)); + } + + latch.await(); + } +} ``` -## Class diagram -![alt text](./etc/monitor.urm.png "Monitor class diagram") +In this example, the `Bank` class is the monitor, and the `transfer` method is the critical section that needs to be executed in a mutually exclusive manner. The `synchronized` keyword in Java is used to implement the Monitor pattern, ensuring that only one thread can execute the `transfer` method at a time. -## Applicability +## When to Use the Monitor Pattern in Java -The Monitor design pattern should be used in situations where you have shared resources that need to be accessed and -manipulated by multiple threads or processes concurrently. This pattern is particularly useful in scenarios where -synchronization is necessary to prevent race conditions, data corruption, and inconsistent states. Here are some -situations where you should consider using the Monitor pattern: +The Monitor design pattern should be used in situations where you have shared resources that need to be accessed and manipulated by multiple threads or processes concurrently. This pattern is particularly useful in scenarios where synchronization is necessary to prevent race conditions, data corruption, and inconsistent states. Here are some situations where you should consider using the Monitor pattern: 1. **Shared Data**: When your application involves shared data structures, variables, or resources that need to be accessed and updated by multiple threads. Monitors ensure that only one thread can access the shared resource at a time, preventing conflicts and ensuring data consistency. @@ -90,16 +137,33 @@ situations where you should consider using the Monitor pattern: 8. **Improved Maintainability**: When you want to encapsulate synchronization logic and shared resource management within a single object, improving code organization and making it easier to reason about concurrency-related code. -However, it's important to note that the Monitor pattern might not be the best fit for all concurrency scenarios. In -some cases, other synchronization mechanisms like locks, semaphores, or concurrent data structures might be more -suitable. Additionally, modern programming languages and frameworks often provide higher-level concurrency constructs -that abstract away the complexities of low-level synchronization. +However, it's important to note that the Monitor pattern might not be the best fit for all concurrency scenarios. In some cases, other synchronization mechanisms like locks, semaphores, or concurrent data structures might be more suitable. Additionally, modern programming languages and frameworks often provide higher-level concurrency constructs that abstract away the complexities of low-level synchronization. + +Before applying the Monitor pattern, it's recommended to thoroughly analyze your application's concurrency requirements and choose the synchronization approach that best suits your needs, taking into consideration factors like performance, complexity, and available language features. + +## Real-World Applications of Monitor Pattern in Java + +Common implementations of the Monitor design pattern in Java include synchronized methods and blocks, as well as concurrent data structures like `Vector` and `Hashtable`. + +## Benefits and Trade-offs of Monitor Pattern + +Benefits: + +* Ensures mutual exclusion, preventing race conditions. +* Simplifies the complexity of thread management by providing a clear structure for resource access. + +Trade-offs: + +* Can lead to decreased performance due to locking overhead. +* Potential for deadlocks if not carefully designed. + +## Related Java Design Patterns -Before applying the Monitor pattern, it's recommended to thoroughly analyze your application's concurrency requirements -and choose the synchronization approach that best suits your needs, taking into consideration factors like performance, -complexity, and available language features. +Semaphore: Used to control access to a common resource by multiple threads; Monitor uses a binary semaphore concept at its core. +Mutex: Another mechanism for ensuring mutual exclusion; Monitor is a higher-level construct often implemented using mutexes. -## Related patterns +## References and Credits -* Active object -* Double-checked locking +* [Concurrency: State Models & Java Programs](https://amzn.to/4dxxjUX) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) diff --git a/monitor/pom.xml b/monitor/pom.xml index 1f87196d4828..67e24c3bb4a0 100644 --- a/monitor/pom.xml +++ b/monitor/pom.xml @@ -34,6 +34,14 @@ monitor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -52,7 +60,7 @@ - com.iluwatar.abstractdocument.Main + com.iluwatar.monitor.Main diff --git a/monitor/src/main/java/com/iluwatar/monitor/Bank.java b/monitor/src/main/java/com/iluwatar/monitor/Bank.java index 76f85b303ab3..bc89c26eaad7 100644 --- a/monitor/src/main/java/com/iluwatar/monitor/Bank.java +++ b/monitor/src/main/java/com/iluwatar/monitor/Bank.java @@ -23,38 +23,39 @@ * THE SOFTWARE. */ /* -*The MIT License -*Copyright © 2014-2021 Ilkka Seppälä -* -*Permission is hereby granted, free of charge, to any person obtaining a copy -*of this software and associated documentation files (the "Software"), to deal -*in the Software without restriction, including without limitation the rights -*to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -*copies of the Software, and to permit persons to whom the Software is -*furnished to do so, subject to the following conditions: -* -*The above copyright notice and this permission notice shall be included in -*all copies or substantial portions of the Software. -* -*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -*IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -*FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -*AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -*LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -*OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -*THE SOFTWARE. -*/ + *The MIT License + *Copyright © 2014-2021 Ilkka Seppälä + * + *Permission is hereby granted, free of charge, to any person obtaining a copy + *of this software and associated documentation files (the "Software"), to deal + *in the Software without restriction, including without limitation the rights + *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + *copies of the Software, and to permit persons to whom the Software is + *furnished to do so, subject to the following conditions: + * + *The above copyright notice and this permission notice shall be included in + *all copies or substantial portions of the Software. + * + *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + *THE SOFTWARE. + */ package com.iluwatar.monitor; import java.util.Arrays; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** Bank Definition. */ @Slf4j public class Bank { - private final int[] accounts; + @Getter private final int[] accounts; /** * Constructor. @@ -72,7 +73,7 @@ public Bank(int accountNum, int baseAmount) { * * @param accountA - source account * @param accountB - destination account - * @param amount - amount to be transferred + * @param amount - amount to be transferred */ public synchronized void transfer(int accountA, int accountB, int amount) { if (accounts[accountA] >= amount && accountA != accountB) { @@ -113,13 +114,4 @@ public synchronized int getBalance() { public synchronized int getBalance(int accountNumber) { return accounts[accountNumber]; } - - /** - * Get all accounts. - * - * @return accounts - */ - public int[] getAccounts() { - return accounts; - } } diff --git a/monitor/src/main/java/com/iluwatar/monitor/Main.java b/monitor/src/main/java/com/iluwatar/monitor/Main.java index 7324e6e7ac9b..37d0af0dde41 100644 --- a/monitor/src/main/java/com/iluwatar/monitor/Main.java +++ b/monitor/src/main/java/com/iluwatar/monitor/Main.java @@ -28,6 +28,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import lombok.extern.slf4j.Slf4j; + /** * The Monitor pattern is used in concurrent algorithms to achieve mutual exclusion. * @@ -46,7 +47,7 @@ public class Main { /** * Runner to perform a bunch of transfers and handle exception. * - * @param bank bank object + * @param bank bank object * @param latch signal finished execution */ public static void runner(Bank bank, CountDownLatch latch) { diff --git a/monitor/src/test/java/com/iluwatar/monitor/BankTest.java b/monitor/src/test/java/com/iluwatar/monitor/BankTest.java index 6d0c671ebe6f..6f3b9a145df4 100644 --- a/monitor/src/test/java/com/iluwatar/monitor/BankTest.java +++ b/monitor/src/test/java/com/iluwatar/monitor/BankTest.java @@ -24,11 +24,12 @@ */ package com.iluwatar.monitor; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.*; class BankTest { diff --git a/monitor/src/test/java/com/iluwatar/monitor/MainTest.java b/monitor/src/test/java/com/iluwatar/monitor/MainTest.java index 4d69aeebd5ef..fa067cf9dd2f 100644 --- a/monitor/src/test/java/com/iluwatar/monitor/MainTest.java +++ b/monitor/src/test/java/com/iluwatar/monitor/MainTest.java @@ -24,10 +24,11 @@ */ package com.iluwatar.monitor; -import org.junit.jupiter.api.Test; -import java.util.concurrent.CountDownLatch; import static org.junit.jupiter.api.Assertions.*; +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.api.Test; + /** Test if the application starts without throwing an exception. */ class MainTest { diff --git a/monolithic-architecture/README.md b/monolithic-architecture/README.md new file mode 100644 index 000000000000..fd288dab3b99 --- /dev/null +++ b/monolithic-architecture/README.md @@ -0,0 +1,155 @@ +--- +title: "Monolithic Ecommerce App: A Cohesive Application Model" +shortTitle: Monolithic Ecommerce +description: "Explore the Monolithic Ecommerce application structure, its design intent, benefits, limitations, and real-world applications. Understand its simplicity and practical use cases." +category: Architectural +language: en +tag: + - Cohesion + - Simplicity + - Scalability + - Deployment + - Maintainability +--- + +## Monolithic-Ecommerce App +* A Monolithic Ecommerce example to showcase Monolithic Architecture + +## The Intent of Monolithic Design pattern +> the Monolithic Design Pattern structures an application as a single, cohesive unit where all components—such as business logic, user interface, and data access are tightly integrated and operate as part of a single executable. + +## Detailed Explanation of the Monolithic Architecture +Real-world Example +> A traditional E-commerce website is the most straightforward example for a monolithic application as it is comprised of a catalogue of products, orders to be made, shopping carts, and payment processes that are all inseperable of each other. + +In Plain words +>The monolithic design pattern structures an application as a single unified unit, where all components are tightly coupled and run within a single process. + +GeeksforGeeks states +> Monolithic architecture, a traditional approach in system design, which contains all application components into a single codebase. This unified structure simplifies development and deployment processes, offering ease of management and tight integration. However, because of its rigidity, it is difficult to scale and maintain, which makes it difficult to adjust to changing needs. + +Why use MVC for a Monolithic Application ? +>The Model-View-Controller (MVC) pattern is not inherently tied to microservices or distributed systems. It's a software design pattern that organizes the codebase by separating concerns into three distinct layers: +>* Model +>* View +>* Controller +> +> this also helps maintain the principles of a Monolithic Architecture which are: +> +> Simple to +>* Develop +>* Test +>* Deploy +>* Scale +> + +Architecture diagram + +![Model-View-Controller Architecture Diagram](./etc/mvc-architecture-diagram.png) + +## We can clearly see that this is a Monolithic application through the main class +This is a simplified version of the main application that shows the main interaction point with the CLI and how a user is registered +```java +@SpringBootApplication +public class EcommerceApp implements CommandLineRunner { + + private static final Logger log = LogManager.getLogger(EcommerceApp.class); + private final UserCon userService; + private final ProductCon productService; + private final OrderCon orderService; + public EcommerceApp(UserCon userService, ProductCon productService, OrderCon orderService) { + this.userService = userService; + this.productService = productService; + this.orderService = orderService; + } + public static void main(String... args) { + SpringApplication.run(EcommerceApp.class, args); + } + @Override + public void run(String... args) { + Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8); + + log.info("Welcome to the Monolithic E-commerce CLI!"); + while (true) { + log.info("\nChoose an option:"); + log.info("1. Register User"); + log.info("2. Add Product"); + log.info("3. Place Order"); + log.info("4. Exit"); + log.info("Enter your choice: "); + + int userInput = scanner.nextInt(); + scanner.nextLine(); + + switch (userInput) { + case 1 -> registerUser(scanner); + case 2 -> addProduct(scanner); + case 3 -> placeOrder(scanner); + case 4 -> { + log.info("Exiting the application. Goodbye!"); + return; + } + default -> log.info("Invalid choice! Please try again."); + } + } + } + protected void registerUser(Scanner scanner) { + log.info("Enter user details:"); + log.info("Name: "); + String name = scanner.nextLine(); + log.info("Email: "); + String email = scanner.nextLine(); + log.info("Password: "); + String password = scanner.nextLine(); + + User user = new User(null, name, email, password); + userService.registerUser(user); + log.info("User registered successfully!"); + } + +} +``` +### We can clearly reach the conclusion that all of these classes reside under the same module and are essential for each other's functionality, this is supported by the presence of all relevant classes as parts of the main application class. + +## When should you resort to a Monolithic Architecture ? +>* An enterprise Starting off with a relatively small team +>* Simplicity is the most important factor of the project +>* Maintaining less entry points to the system is cruical +>* Prototyping ideas +> +## Pros & Cons of using Monolithic Architecture +>### Pros: +>* Simple Development: Easy to develop and deploy. +>* Unified Codebase: All code in one place, simplifying debugging. +>* Better Performance: No inter-service communication overhead. +>* Lower Costs: Minimal infrastructure and tooling requirements. +>* Ease of Testing: Single application makes end-to-end testing straightforward. +> * This is also assisted by the MVC structure employed in this example. +>### Cons: +>* Scalability Issues: Cannot scale individual components. +>* Tight Coupling: Changes in one area may impact the whole system. +>* Deployment Risks: A single failure can crash the entire application. +>* Complex Maintenance: Harder to manage as the codebase grows. +>* Limited Flexibility: Difficult to adopt new technologies for specific parts. + +## Real-World Applications of Monolithic architecture Pattern in Java +>* E-Commerce Platforms +>* Content Management Systems (CMS) +>* Banking and Financial Systems +>* Enterprise Resource Planning (ERP) Systems +>* Retail Point of Sale (POS) Systems + +## References +>* [GeeksforGeeks](https://www.geeksforgeeks.org/monolithic-architecture-system-design/) +>* [Wikipedia](https://en.wikipedia.org/wiki/Monolithic_application) +>* [vFunction](https://vfunction.com/blog/what-is-monolithic-application/#:~:text=A%20traditional%20e%2Dcommerce%20platform,inseparable%20components%20of%20the%20system.) Blog post +>* [Microservices.io](https://microservices.io/patterns/monolithic.html) +>* [IBM](https://www.ibm.com/think/topics/monolithic-architecture) +>#### References used to create the code +>* [Mockito](https://site.mockito.org/) -Testing +>* [Junit](https://junit.org/junit5/docs/current/user-guide/) -Testing +>* [Springboot](https://docs.spring.io/spring-boot/index.html) -Web Application Initiation (implemented but not utilized in this example) +>* [Sprint Data Jpa](https://docs.spring.io/spring-data/jpa/reference/index.html) -Database connection +>* [Lombok](https://projectlombok.org/) -Simplifying Classes +>* [Log4j](https://logging.apache.org/log4j/2.x/index.html) -Capturing Logs +>* [H2 Databse](https://www.h2database.com/html/tutorial.html) -Efficient, Simple, Dynamic Databse \ No newline at end of file diff --git a/monolithic-architecture/etc/Monolithic-Ecommerce.urm.puml b/monolithic-architecture/etc/Monolithic-Ecommerce.urm.puml new file mode 100644 index 000000000000..0f1c6d96e735 --- /dev/null +++ b/monolithic-architecture/etc/Monolithic-Ecommerce.urm.puml @@ -0,0 +1,123 @@ +@startuml +package com.iluwatar.monolithic.model { + class Orders { + - id : Long + - product : Products + - quantity : Integer + - totalPrice : Double + - user : User + + Orders() + + Orders(id : Long, user : User, product : Products, quantity : Integer, totalPrice : Double) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : Long + + getProduct() : Products + + getQuantity() : Integer + + getTotalPrice() : Double + + getUser() : User + + hashCode() : int + + setId(id : Long) + + setProduct(product : Products) + + setQuantity(quantity : Integer) + + setTotalPrice(totalPrice : Double) + + setUser(user : User) + + toString() : String + } + class Products { + - description : String + - id : Long + - name : String + - price : Double + - stock : Integer + + Products() + + Products(id : Long, name : String, description : String, price : Double, stock : Integer) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getDescription() : String + + getId() : Long + + getName() : String + + getPrice() : Double + + getStock() : Integer + + hashCode() : int + + setDescription(description : String) + + setId(id : Long) + + setName(name : String) + + setPrice(price : Double) + + setStock(stock : Integer) + + toString() : String + } + class User { + - email : String + - id : Long + - name : String + - password : String + + User() + + User(id : Long, name : String, email : String, password : String) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getEmail() : String + + getId() : Long + + getName() : String + + getPassword() : String + + hashCode() : int + + setEmail(email : String) + + setId(id : Long) + + setName(name : String) + + setPassword(password : String) + + toString() : String + } +} +package com.iluwatar.monolithic.repository { + interface OrderRepo { + } + interface ProductRepo { + } + interface UserRepo { + + findByEmail(String) : User {abstract} + } +} +package com.iluwatar.monolithic.controller { + class OrderCon { + - orderRepository : OrderRepo + - productRepository : ProductRepo + - userRepository : UserRepo + + OrderCon(orderRepository : OrderRepo, userRepository : UserRepo, productRepository : ProductRepo) + + placeOrder(userId : Long, productId : Long, quantity : Integer) : Orders + } + class ProductCon { + - productRepository : ProductRepo + + ProductCon(productRepository : ProductRepo) + + addProduct(product : Products) : Products + + getAllProducts() : List + } + class UserCon { + - userRepository : UserRepo + + UserCon(userRepository : UserRepo) + + registerUser(user : User) : User + } +} +package com.iluwatar.monolithic { + class EcommerceApp { + - log : Logger {static} + - orderService : OrderCon + - productService : ProductCon + - userService : UserCon + + EcommerceApp(userService : UserCon, productService : ProductCon, orderService : OrderCon) + # addProduct(scanner : Scanner) + + main(args : String[]) {static} + # placeOrder(scanner : Scanner) + # registerUser(scanner : Scanner) + + run(args : String[]) + } +} +UserCon --> "-userRepository" UserRepo +Orders --> "-user" User +OrderCon --> "-productRepository" ProductRepo +OrderCon --> "-userRepository" UserRepo +OrderCon --> "-orderRepository" OrderRepo +EcommerceApp --> "-userService" UserCon +EcommerceApp --> "-productService" ProductCon +ProductCon --> "-productRepository" ProductRepo +Orders --> "-product" Products +EcommerceApp --> "-orderService" OrderCon +@enduml \ No newline at end of file diff --git a/monolithic-architecture/etc/monolithic-architecture.urm.puml b/monolithic-architecture/etc/monolithic-architecture.urm.puml new file mode 100644 index 000000000000..73e7506373b3 --- /dev/null +++ b/monolithic-architecture/etc/monolithic-architecture.urm.puml @@ -0,0 +1,123 @@ +@startuml +package com.iluwatar.monolithic.model { + class Orders { + - id : Long + - product : Products + - quantity : Integer + - totalPrice : Double + - user : User + + Orders() + + Orders(id : Long, user : User, product : Products, quantity : Integer, totalPrice : Double) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : Long + + getProduct() : Products + + getQuantity() : Integer + + getTotalPrice() : Double + + getUser() : User + + hashCode() : int + + setId(id : Long) + + setProduct(product : Products) + + setQuantity(quantity : Integer) + + setTotalPrice(totalPrice : Double) + + setUser(user : User) + + toString() : String + } + class Products { + - description : String + - id : Long + - name : String + - price : Double + - stock : Integer + + Products() + + Products(id : Long, name : String, description : String, price : Double, stock : Integer) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getDescription() : String + + getId() : Long + + getName() : String + + getPrice() : Double + + getStock() : Integer + + hashCode() : int + + setDescription(description : String) + + setId(id : Long) + + setName(name : String) + + setPrice(price : Double) + + setStock(stock : Integer) + + toString() : String + } + class User { + - email : String + - id : Long + - name : String + - password : String + + User() + + User(id : Long, name : String, email : String, password : String) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getEmail() : String + + getId() : Long + + getName() : String + + getPassword() : String + + hashCode() : int + + setEmail(email : String) + + setId(id : Long) + + setName(name : String) + + setPassword(password : String) + + toString() : String + } +} +package com.iluwatar.monolithic.repository { + interface OrderRepo { + } + interface ProductRepo { + } + interface UserRepo { + + findByEmail(String) : User {abstract} + } +} +package com.iluwatar.monolithic.controller { + class OrderCon { + - orderRepository : OrderRepo + - productRepository : ProductRepo + - userRepository : UserRepo + + OrderCon(orderRepository : OrderRepo, userRepository : UserRepo, productRepository : ProductRepo) + + placeOrder(userId : Long, productId : Long, quantity : Integer) : Orders + } + class ProductCon { + - productRepository : ProductRepo + + ProductCon(productRepository : ProductRepo) + + addProduct(product : Products) : Products + + getAllProducts() : List + } + class UserCon { + - userRepository : UserRepo + + UserCon(userRepository : UserRepo) + + registerUser(user : User) : User + } +} +package com.iluwatar.monolithic { + class EcommerceApp { + - log : Logger {static} + - orderService : OrderCon + - productService : ProductCon + - userService : UserCon + + EcommerceApp(userService : UserCon, productService : ProductCon, orderService : OrderCon) + # addProduct(scanner : Scanner) + + main(args : String[]) {static} + # placeOrder(scanner : Scanner) + # registerUser(scanner : Scanner) + + run(args : String[]) + } +} +UserCon --> "-userRepository" UserRepo +Orders --> "-user" User +OrderCon --> "-productRepository" ProductRepo +OrderCon --> "-userRepository" UserRepo +OrderCon --> "-orderRepository" OrderRepo +EcommerceApp --> "-userService" UserCon +Orders --> "-product" Products +ProductCon --> "-productRepository" ProductRepo +EcommerceApp --> "-productService" ProductCon +EcommerceApp --> "-orderService" OrderCon +@enduml \ No newline at end of file diff --git a/monolithic-architecture/etc/mvc-architecture-diagram.png b/monolithic-architecture/etc/mvc-architecture-diagram.png new file mode 100644 index 000000000000..33371d41f22c Binary files /dev/null and b/monolithic-architecture/etc/mvc-architecture-diagram.png differ diff --git a/cqrs/pom.xml b/monolithic-architecture/pom.xml similarity index 66% rename from cqrs/pom.xml rename to monolithic-architecture/pom.xml index 58ad7f29a62c..c8a15f80cdc4 100644 --- a/cqrs/pom.xml +++ b/monolithic-architecture/pom.xml @@ -25,39 +25,61 @@ THE SOFTWARE. --> - + 4.0.0 + com.iluwatar java-design-patterns 1.26.0-SNAPSHOT - cqrs + + monolithic-architecture + + - org.junit.jupiter - junit-jupiter-engine - test + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + com.h2database h2 + runtime + + - org.hibernate - hibernate-core + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + - org.glassfish.jaxb - jaxb-runtime + org.junit.jupiter + junit-jupiter-engine test + + - javax.xml.bind - jaxb-api + org.mockito + mockito-core test + @@ -68,9 +90,12 @@ - com.iluwatar.cqrs.app.App + com.iluwatar.monolithic.EcommerceApp + + jar-with-dependencies + @@ -78,3 +103,5 @@ + + diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/EcommerceApp.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/EcommerceApp.java new file mode 100644 index 000000000000..148dfa1760a2 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/EcommerceApp.java @@ -0,0 +1,152 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic; + +import com.iluwatar.monolithic.controller.OrderController; +import com.iluwatar.monolithic.controller.ProductController; +import com.iluwatar.monolithic.controller.UserController; +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.model.User; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Main entry point for the Monolithic E-commerce application. + * ------------------------------------------------------------------------ Monolithic architecture + * is a software design pattern where all components of the application (presentation, business + * logic, and data access layers) are part of a single unified codebase and deployable unit. + * ------------------------------------------------------------------------ This example implements + * a monolithic architecture by integrating user management, product management, and order placement + * within the same application, sharing common resources and a single database. + */ +@SpringBootApplication +public class EcommerceApp implements CommandLineRunner { + + private static final Logger log = LogManager.getLogger(EcommerceApp.class); + private final UserController userService; + private final ProductController productService; + private final OrderController orderService; + + /** Initilizing controllers as services. */ + public EcommerceApp( + UserController userService, ProductController productService, OrderController orderService) { + this.userService = userService; + this.productService = productService; + this.orderService = orderService; + } + + /** + * The main entry point for the Monolithic E-commerce application. Initializes the Spring Boot + * application and starts the embedded server. + */ + public static void main(String... args) { + SpringApplication.run(EcommerceApp.class, args); + } + + @Override + public void run(String... args) { + Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8); + + log.info("Welcome to the Monolithic E-commerce CLI!"); + while (true) { + log.info("\nChoose an option:"); + log.info("1. Register User"); + log.info("2. Add Product"); + log.info("3. Place Order"); + log.info("4. Exit"); + log.info("Enter your choice: "); + + int userInput = scanner.nextInt(); + scanner.nextLine(); + + switch (userInput) { + case 1 -> registerUser(scanner); + case 2 -> addProduct(scanner); + case 3 -> placeOrder(scanner); + case 4 -> { + log.info("Exiting the application. Goodbye!"); + return; + } + default -> log.info("Invalid choice! Please try again."); + } + } + } + + /** Handles User Registration through user CLI inputs. */ + protected void registerUser(Scanner scanner) { + log.info("Enter user details:"); + log.info("Name: "); + String name = scanner.nextLine(); + log.info("Email: "); + String email = scanner.nextLine(); + log.info("Password: "); + String password = scanner.nextLine(); + + User user = new User(null, name, email, password); + userService.registerUser(user); + log.info("User registered successfully!"); + } + + /** Handles the addition of products. */ + protected void addProduct(Scanner scanner) { + log.info("Enter product details:"); + log.info("Name: "); + String name = scanner.nextLine(); + log.info("Description: "); + String description = scanner.nextLine(); + log.info("Price: "); + double price = scanner.nextDouble(); + log.info("Stock: "); + int stock = scanner.nextInt(); + Product product = new Product(null, name, description, price, stock); + scanner.nextLine(); + productService.addProduct(product); + log.info("Product added successfully!"); + } + + /** Handles Order Placement through user CLI inputs. */ + protected void placeOrder(Scanner scanner) { + log.info("Enter order details:"); + log.info("User ID: "); + long userId = scanner.nextLong(); + log.info("Product ID: "); + long productId = scanner.nextLong(); + log.info("Quantity: "); + int quantity = scanner.nextInt(); + scanner.nextLine(); + + try { + orderService.placeOrder(userId, productId, quantity); + log.info("Order placed successfully!"); + } catch (Exception e) { + log.info("Error placing order: {}", e.getMessage()); + } + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/OrderController.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/OrderController.java new file mode 100644 index 000000000000..94776a0d23b7 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/OrderController.java @@ -0,0 +1,80 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.controller; + +import com.iluwatar.monolithic.exceptions.InsufficientStockException; +import com.iluwatar.monolithic.exceptions.NonExistentProductException; +import com.iluwatar.monolithic.exceptions.NonExistentUserException; +import com.iluwatar.monolithic.model.Order; +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.model.User; +import com.iluwatar.monolithic.repository.OrderRepository; +import com.iluwatar.monolithic.repository.ProductRepository; +import com.iluwatar.monolithic.repository.UserRepository; +import org.springframework.stereotype.Service; + +/** OrderController is a controller class for managing Order operations. */ +@Service +public class OrderController { + private final OrderRepository orderRepository; + private final UserRepository userRepository; + private final ProductRepository productRepository; + + /** This function handles the initializing of the controller. */ + public OrderController( + OrderRepository orderRepository, + UserRepository userRepository, + ProductRepository productRepository) { + this.orderRepository = orderRepository; + this.userRepository = userRepository; + this.productRepository = productRepository; + } + + /** This function handles placing orders with all of its cases. */ + public Order placeOrder(Long userId, Long productId, Integer quantity) { + final User user = + userRepository + .findById(userId) + .orElseThrow( + () -> new NonExistentUserException("User with ID " + userId + " not found")); + + final Product product = + productRepository + .findById(productId) + .orElseThrow( + () -> + new NonExistentProductException("Product with ID " + productId + " not found")); + + if (product.getStock() < quantity) { + throw new InsufficientStockException("Not enough stock for product " + productId); + } + + product.setStock(product.getStock() - quantity); + productRepository.save(product); + + final Order order = new Order(null, user, product, quantity, product.getPrice() * quantity); + return orderRepository.save(order); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/ProductController.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/ProductController.java new file mode 100644 index 000000000000..b409fd8e8eb1 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/ProductController.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.controller; + +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.repository.ProductRepository; +import java.util.List; +import org.springframework.stereotype.Service; + +/** ProductCon is a controller class for managing Product operations. */ +@Service +public class ProductController { + private final ProductRepository productRepository; + + /** Linking Controller to DB. */ + public ProductController(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + /** Adds a product to the DB. */ + public Product addProduct(Product product) { + return productRepository.save(product); + } + + /** Returns all relevant Product. */ + public List getAllProducts() { + return productRepository.findAll(); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/UserController.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/UserController.java new file mode 100644 index 000000000000..a4fe6dbe363e --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/UserController.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.controller; + +import com.iluwatar.monolithic.model.User; +import com.iluwatar.monolithic.repository.UserRepository; +import org.springframework.stereotype.Service; + +/** UserController is a controller class for managing user operations. */ +@Service +public class UserController { + private final UserRepository userRepository; + + /** Linking Controller to DB. */ + public UserController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + /** Adds a user to the DB. */ + public User registerUser(User user) { + return userRepository.save(user); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/InsufficientStockException.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/InsufficientStockException.java new file mode 100644 index 000000000000..d5fd9edfcf75 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/InsufficientStockException.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.exceptions; + +import java.io.Serial; + +/** Custom Exception class for enhanced readability. */ +public class InsufficientStockException extends RuntimeException { + @Serial private static final long serialVersionUID = 1005208208127745099L; + + /** + * Exception Constructor that is readable through code and provides the message inputted into it. + */ + public InsufficientStockException(String message) { + super(message); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentProductException.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentProductException.java new file mode 100644 index 000000000000..63c4821d946b --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentProductException.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.exceptions; + +import java.io.Serial; + +/** Custom Exception class for enhanced readability. */ +public class NonExistentProductException extends RuntimeException { + @Serial private static final long serialVersionUID = -593425162052345565L; + + /** + * Exception Constructor that is readable through code and provides the message inputted into it. + */ + public NonExistentProductException(String msg) { + super(msg); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentUserException.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentUserException.java new file mode 100644 index 000000000000..99625ad7b324 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentUserException.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.exceptions; + +import java.io.Serial; + +/** Custom Exception class for enhanced readability. */ +public class NonExistentUserException extends RuntimeException { + @Serial private static final long serialVersionUID = -7660909426227843633L; + + /** + * Exception Constructor that is readable through code and provides the message inputted into it. + */ + public NonExistentUserException(String msg) { + super(msg); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Order.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Order.java new file mode 100644 index 000000000000..91caaf9c7ede --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Order.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Represents a Database in which Order are stored. */ +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne private User user; + + @ManyToOne private Product product; + + private Integer quantity; + + private Double totalPrice; +} diff --git a/layers/src/main/java/entity/CakeLayer.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Product.java similarity index 69% rename from layers/src/main/java/entity/CakeLayer.java rename to monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Product.java index d3bd65ad9966..bcfe52c44572 100644 --- a/layers/src/main/java/entity/CakeLayer.java +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Product.java @@ -22,42 +22,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package entity; +package com.iluwatar.monolithic.model; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; -import jakarta.persistence.*; -import lombok.*; - -/** - * CakeLayer entity. - */ +/** Represents a database of products. */ @Entity -@Getter -@Setter +@Data @NoArgsConstructor @AllArgsConstructor -@Builder -@EqualsAndHashCode -public class CakeLayer { - - @Id - @GeneratedValue - private Long id; - - private String name; +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - private int calories; + private String name; - @ManyToOne(cascade = CascadeType.ALL) - private Cake cake; + private String description; - public CakeLayer(String name, int calories) { - this.setName(name); - this.setCalories(calories); - } + private Double price; - @Override - public String toString() { - return String.format("id=%s name=%s calories=%d", id, name, calories); - } + private Integer stock; } diff --git a/layers/src/main/java/entity/CakeTopping.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java similarity index 69% rename from layers/src/main/java/entity/CakeTopping.java rename to monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java index 697ed5a07877..0b05f447c924 100644 --- a/layers/src/main/java/entity/CakeTopping.java +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java @@ -22,43 +22,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package entity; - - -import jakarta.persistence.*; -import lombok.*; - -/** - * CakeTopping entity. - */ +package com.iluwatar.monolithic.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Represents a Product entity for the database. */ @Entity -@Getter -@Setter +@Data @NoArgsConstructor @AllArgsConstructor -@Builder -@EqualsAndHashCode -public class CakeTopping { - - @Id - @GeneratedValue - private Long id; - - private String name; - - private int calories; - - @OneToOne(cascade = CascadeType.ALL) - private Cake cake; +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - public CakeTopping(String name, int calories) { - this.setName(name); - this.setCalories(calories); - } + private String name; - @Override - public String toString() { - return String.format("id=%s name=%s calories=%d", id, name, calories); - } + @Column(unique = true) + private String email; + private String password; } diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/OrderRepository.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/OrderRepository.java new file mode 100644 index 000000000000..af38c56f1315 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/OrderRepository.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.repository; + +import com.iluwatar.monolithic.model.Order; +import org.springframework.data.jpa.repository.JpaRepository; + +/** This interface allows JpaRepository to generate queries for the required tables. */ +public interface OrderRepository extends JpaRepository {} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/ProductRepository.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/ProductRepository.java new file mode 100644 index 000000000000..660ed33bb9fe --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/ProductRepository.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.repository; + +import com.iluwatar.monolithic.model.Product; +import org.springframework.data.jpa.repository.JpaRepository; + +/** This interface allows JpaRepository to generate queries for the required tables. */ +public interface ProductRepository extends JpaRepository {} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/UserRepository.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/UserRepository.java new file mode 100644 index 000000000000..e05c688169d0 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/UserRepository.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.repository; + +import com.iluwatar.monolithic.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +/** This interface allows JpaRepository to generate queries for the required tables. */ +public interface UserRepository extends JpaRepository { + /** + * Utilizes JpaRepository functionalities to generate a function which looks up in the User table + * using emails. + */ + User findByEmail(String email); +} diff --git a/monolithic-architecture/src/main/resources/application.properties b/monolithic-architecture/src/main/resources/application.properties new file mode 100644 index 000000000000..166f3dafd7ac --- /dev/null +++ b/monolithic-architecture/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.password=password +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.username=admin +spring.h2.console.enabled=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true diff --git a/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java b/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java new file mode 100644 index 000000000000..1f31157325e0 --- /dev/null +++ b/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java @@ -0,0 +1,279 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.iluwatar.monolithic.controller.OrderController; +import com.iluwatar.monolithic.controller.ProductController; +import com.iluwatar.monolithic.controller.UserController; +import com.iluwatar.monolithic.exceptions.InsufficientStockException; +import com.iluwatar.monolithic.exceptions.NonExistentProductException; +import com.iluwatar.monolithic.exceptions.NonExistentUserException; +import com.iluwatar.monolithic.model.Order; +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.model.User; +import com.iluwatar.monolithic.repository.OrderRepository; +import com.iluwatar.monolithic.repository.ProductRepository; +import com.iluwatar.monolithic.repository.UserRepository; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.Optional; +import java.util.Scanner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class MonolithicAppTest { + + @Mock private UserController userService; + + @Mock private ProductController productService; + + @Mock private OrderController orderService; + + private EcommerceApp ecommerceApp; + + private ByteArrayOutputStream outputStream; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ecommerceApp = new EcommerceApp(userService, productService, orderService); + outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream, true, StandardCharsets.UTF_8)); + Locale.setDefault(Locale.US); + } + + @Test + void testRegisterUser() { + String simulatedInput = "John Doe\njohn@example.com\npassword123\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + ecommerceApp.registerUser(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(userService, times(1)).registerUser(any(User.class)); + assertTrue(outputStream.toString().contains("User registered successfully!")); + } + + @Test + void testPlaceOrderUserNotFound() { + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + OrderRepository mockOrderRepo = mock(OrderRepository.class); + + when(mockUserRepository.findById(1L)).thenReturn(Optional.empty()); + + OrderController orderCon = + new OrderController(mockOrderRepo, mockUserRepository, mockProductRepository); + + Exception exception = + assertThrows( + NonExistentUserException.class, + () -> { + orderCon.placeOrder(1L, 1L, 5); + }); + + assertEquals("User with ID 1 not found", exception.getMessage()); + } + + @Test + void testPlaceOrderProductNotFound() { + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + OrderRepository mockOrderRepository = mock(OrderRepository.class); + + User mockUser = new User(1L, "John Doe", "john@example.com", "password123"); + when(mockUserRepository.findById(1L)).thenReturn(Optional.of(mockUser)); + + when(mockProductRepository.findById(1L)).thenReturn(Optional.empty()); + + OrderController orderCon = + new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository); + + Exception exception = + assertThrows( + NonExistentProductException.class, + () -> { + orderCon.placeOrder(1L, 1L, 5); + }); + + assertEquals("Product with ID 1 not found", exception.getMessage()); + } + + @Test + void testOrderConstructor() { + OrderRepository mockOrderRepository = mock(OrderRepository.class); + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + + OrderController orderCon = + new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository); + + assertNotNull(orderCon); + } + + @Test + void testAddProduct() { + String simulatedInput = "Laptop\nGaming Laptop\n1200.50\n10\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + ecommerceApp.addProduct(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(productService, times(1)).addProduct(any(Product.class)); + assertTrue(outputStream.toString().contains("Product added successfully!")); + } + + @Test + void testPlaceOrderSuccess() { + String simulatedInput = "1\n2\n3\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + Order mockOrder = new Order(); + doReturn(mockOrder).when(orderService).placeOrder(anyLong(), anyLong(), anyInt()); + + ecommerceApp.placeOrder(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(orderService, times(1)).placeOrder(anyLong(), anyLong(), anyInt()); + assertTrue(outputStream.toString().contains("Order placed successfully!")); + } + + @Test + void testPlaceOrderFailure() { + String simulatedInput = "1\n2\n3\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + doThrow(new RuntimeException("Product out of stock")) + .when(orderService) + .placeOrder(anyLong(), anyLong(), anyInt()); + + ecommerceApp.placeOrder(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(orderService, times(1)).placeOrder(anyLong(), anyLong(), anyInt()); + assertTrue(outputStream.toString().contains("Error placing order: Product out of stock")); + } + + @Test + void testPlaceOrderInsufficientStock() { + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + OrderRepository mockOrderRepository = mock(OrderRepository.class); + + User mockUser = new User(1L, "John Doe", "john@example.com", "password123"); + when(mockUserRepository.findById(1L)).thenReturn(Optional.of(mockUser)); + Product mockProduct = + new Product(1L, "Laptop", "High-end gaming laptop", 1500.00, 2); // Only 2 in stock + when(mockProductRepository.findById(1L)).thenReturn(Optional.of(mockProduct)); + + OrderController orderCon = + new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository); + + Exception exception = + assertThrows( + InsufficientStockException.class, + () -> { + orderCon.placeOrder(1L, 1L, 5); + }); + assertEquals("Not enough stock for product 1", exception.getMessage()); + } + + @Test + void testProductConAddProduct() { + ProductRepository mockProductRepository = mock(ProductRepository.class); + + Product mockProduct = new Product(1L, "Smartphone", "High-end smartphone", 1000.00, 20); + + when(mockProductRepository.save(any(Product.class))).thenReturn(mockProduct); + + ProductController productController = new ProductController(mockProductRepository); + + Product savedProduct = productController.addProduct(mockProduct); + + verify(mockProductRepository, times(1)).save(any(Product.class)); + + assertNotNull(savedProduct); + assertEquals("Smartphone", savedProduct.getName()); + assertEquals("High-end smartphone", savedProduct.getDescription()); + assertEquals(1000.00, savedProduct.getPrice()); + assertEquals(20, savedProduct.getStock()); + } + + @Test + void testRun() { + String simulatedInput = + """ + 1 + John Doe + john@example.com + password123 + 2 + Laptop + Gaming Laptop + 1200.50 + 10 + 3 + 1 + 1 + 2 + 4 + """; // Exit + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + ByteArrayOutputStream outputTest = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputTest, true, StandardCharsets.UTF_8)); + + when(userService.registerUser(any(User.class))) + .thenReturn(new User(1L, "John Doe", "john@example.com", "password123")); + when(productService.addProduct(any(Product.class))) + .thenReturn(new Product(1L, "Laptop", "Gaming Laptop", 1200.50, 10)); + when(orderService.placeOrder(anyLong(), anyLong(), anyInt())) + .thenReturn( + new Order( + 1L, + new User(1L, "John Doe", "john@example.com", "password123"), + new Product(1L, "Laptop", "Gaming Laptop", 1200.50, 10), + 5, + 6002.50)); + + ecommerceApp.run(); + + verify(userService, times(1)).registerUser(any(User.class)); + verify(productService, times(1)).addProduct(any(Product.class)); + verify(orderService, times(1)).placeOrder(anyLong(), anyLong(), anyInt()); + + String output = outputTest.toString(StandardCharsets.UTF_8); + assertTrue(output.contains("Welcome to the Monolithic E-commerce CLI!")); + assertTrue(output.contains("Choose an option:")); + assertTrue(output.contains("Register User")); + assertTrue(output.contains("Add Product")); + assertTrue(output.contains("Place Order")); + assertTrue(output.contains("Exiting the application. Goodbye!")); + } +} diff --git a/monostate/README.md b/monostate/README.md index f800a4e3f138..33a603502fe4 100644 --- a/monostate/README.md +++ b/monostate/README.md @@ -1,94 +1,89 @@ --- -title: MonoState +title: "Monostate Pattern in Java: Achieving Singleton Behavior with Class-Level State" +shortTitle: Monostate +description: "Learn how the Monostate design pattern works in Java. Discover its benefits, implementation details, and use cases. Perfect for ensuring shared state across multiple class instances." category: Creational language: en tag: - - Instantiation + - Encapsulation + - Instantiation + - Object composition + - Persistence + - Polymorphism --- ## Also known as -Borg -## Intent -Monostate is an alternative approach to achieving a singleton-like behavior in object-oriented design. It enforces a unique behavior where all instances of a class share the same state. Unlike the Singleton pattern, which restricts a class to a single instance, Monostate allows for multiple instances but ensures they all have a shared state. +* Borg -## Explanation +## Intent of Monostate Design Pattern -Real-word example +The Monostate pattern is an alternative approach to achieving a singleton-like behavior in object-oriented design, ensuring a shared state in Java applications. It enforces a unique behavior where all instances of a class share the same state. Unlike the Singleton pattern, which restricts a class to a single instance, Monostate allows for multiple instances but ensures they all have a shared state. + +## Detailed Explanation of Monostate Pattern with Real-World Examples -> In a cloud-based document editing service, multiple users can collaborate on the same document simultaneously. To ensure consistency across different user sessions, the system maintains a shared state that includes the document’s content, the cursor position of each user, and any comments made on the document. Instead of coupling each user’s session directly to each other, the system utilizes the Monostate pattern to share the state among all active sessions. Each user interacts with their own instance of a DocumentSession class, unaware that they are all sharing the same underlying state. This approach decouples the individual sessions from each other, while ensuring real-time consistency and collaboration across all users working on the document. +Real-word example +> Imagine a library with multiple desks where patrons can access the library's catalog. While each desk appears to be independent, any changes made to the catalog (like adding or removing a book) are immediately reflected at all desks. This setup ensures that no matter which desk a patron uses, they see the exact same, up-to-date catalog, similar to how the Monostate pattern maintains a shared state across all instances in Java. This is analogous to the Monostate pattern, where multiple instances of a class share the same state, ensuring consistent data across all instances. In plain words > Monostate allows multiple instances of a class to share the same state by channeling their state management through a common shared structure. This ensures consistent state across all instances while maintaining the facade of independent objects. +wiki.c2.com says -**Programmatic Examples** +> A monostate is a "conceptual singleton" - all data members of a monostate are static, so all instances of the monostate use the same (static) data. Applications using a monostate can create any number of instances that they desire, as each instance uses the same data. -Suppose you are developing an online multi-person collaborative drawing application that allows multiple users to draw simultaneously on the same canvas. In order to synchronize the views of all users, you need to ensure that all users see the same canvas state, including the current background color and brush size. Therefore, you decide to use Monostate mode to synchronize these settings across all user interfaces. +## Programmatic Example of Monostate Pattern in Java -``` -public class CanvasSettings { - private static String backgroundColor = "White"; - private static int brushSize = 5; +The Monostate pattern in Java ensures that all instances of a class share the same state, making it a great Singleton alternative for maintaining consistent data. This is achieved by using static fields in the class. Any changes to these fields will be reflected across all instances of the class. This pattern is useful when you want to avoid global variables but still need a shared state across multiple instances. - public void setBackgroundColor(String color) { - backgroundColor = color; - } +Let's take a look at the `LoadBalancer` class from the `monostate` module: - public String getBackgroundColor() { - return backgroundColor; - } +```java +public class LoadBalancer { + private static List servers = new ArrayList<>(); + private static int nextServerIndex = 0; - public void setBrushSize(int size) { - if (size > 0) { - brushSize = size; - } + public LoadBalancer() { + // Initialize servers + servers.add(new Server("192.168.0.1", 8080, 1)); + servers.add(new Server("192.168.0.2", 8080, 2)); + servers.add(new Server("192.168.0.3", 8080, 3)); } - public int getBrushSize() { - return brushSize; + public void serverRequest(Request request) { + Server server = servers.get(nextServerIndex); + server.serve(request); + nextServerIndex = (nextServerIndex + 1) % servers.size(); } } ``` -* CanvasSettings class manages settings for drawing applications, such as background color and brush size. -* It uses static fields to store state, ensuring that all instances of the CanvasSettings class share the same state. -* setBackgroundColor, getBackgroundColor, setBrushSize, and getBrushSize methods are used to modify and retrieve settings. - -``` -public class MonostateExample { - public static void main(String[] args) { - CanvasSettings user1Settings = new CanvasSettings(); - CanvasSettings user2Settings = new CanvasSettings(); - - // Initially, both instances share the same state - System.out.println("User 1 Background Color: " + user1Settings.getBackgroundColor()); // output: White - System.out.println("User 2 Brush Size: " + user2Settings.getBrushSize()); // output: 5 - // User 1 changes the background color - user1Settings.setBackgroundColor("Blue"); +In this class, `servers` and `nextServerIndex` are static fields. This means that they are shared across all instances of `LoadBalancer`. The method `serverRequest` is used to serve a request and then update the `nextServerIndex` to the next server in the list. - /// The change is reflected in the settings of User 2 - System.out.println("User 2 Background Color (after change): " + user2Settings.getBackgroundColor()); // output: Blue +Now, let's see how this works in practice: - // User 2 changes the brush size - user2Settings.setBrushSize(10); +```java +public class App { + public static void main(String[] args) { + LoadBalancer loadBalancer1 = new LoadBalancer(); + LoadBalancer loadBalancer2 = new LoadBalancer(); - // The change is reflected in the settings of User 1 - System.out.println("User 1 Brush Size (after change): " + user1Settings.getBrushSize()); // output: 10 + // Both instances share the same state + loadBalancer1.serverRequest(new Request("Hello")); // Server 1 serves the request + loadBalancer2.serverRequest(new Request("Hello World")); // Server 2 serves the request } } ``` -In this MonostateExample class, we demonstrate how to create instances for two users and show that changes to settings in one instance are reflected in the other instance. -## Class diagram -![alt text](./etc/monostate.png "MonoState") +In this example, we create two instances of `LoadBalancer`: `loadBalancer1` and `loadBalancer2`. They share the same state. When we make a request via `loadBalancer1`, the request is served by the first server. When we make a request via `loadBalancer2`, the request is served by the second server, not the first one, because the `nextServerIndex` has been updated by `loadBalancer1`. This demonstrates the Monostate pattern in action. + +## When to Use the Monostate Pattern in Java -## Applicability -Use the Monostate pattern when +Use the Monostate pattern in Java design patterns when -1. **Shared State Across Instances:** All instances of a class must share the same state. Changes in one instance should be reflected across all instances. +1. **Shared State Across Instances:** All instances of a class must share the same state. Changes in one instance should be reflected across all instances. Monostate offers more flexibility compared to the traditional Singleton pattern. 2. **Transparent Usage:** Unlike Singleton, which can be less transparent in its usage, Monostate allows for a more transparent way of sharing state across instances. Clients interact with instances of the Monostate class as if they were regular instances, unaware of the shared state. @@ -100,9 +95,29 @@ Use the Monostate pattern when 6. **Consistent Configuration or State Management:** In scenarios where you need consistent configuration management or state management across different parts of an application, Monostate provides a pattern to ensure all parts of the system are in sync. -## Typical Use Case +## Real-World Applications of Monostate Pattern in Java + +* Configuration settings that need to be shared and accessible by various parts of an application. +* Resource pools where the state needs to be consistent across different consumers. + +## Benefits and Trade-offs of Monostate Pattern + +Benefits: + +* Provides a shared state without restricting the creation of multiple instances. +* Ensures consistency of data across instances. + +Trade-offs: + +* Can lead to hidden dependencies due to shared state, making the system harder to understand and debug. +* Reduces the flexibility to have instances with independent states. + +## Related Java Design Patterns + +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Both Singleton and Monostate patterns ensure a single shared state, but the Monostate pattern in Java allows for multiple instances with the same state, making it a unique object-oriented design approach. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Flyweight shares state to reduce memory usage, similar to how Monostate shares state among instances. -* The logging class -* Managing a connection to a database -* File manager +## References and Credits +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/monostate/pom.xml b/monostate/pom.xml index a9d3abfafd74..fad3226104be 100644 --- a/monostate/pom.xml +++ b/monostate/pom.xml @@ -34,6 +34,14 @@ monostate + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -45,4 +53,23 @@ test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.monostate.App + + + + + + + + diff --git a/monostate/src/main/java/com/iluwatar/monostate/App.java b/monostate/src/main/java/com/iluwatar/monostate/App.java index 289362f3023f..3c3dab2fcc4d 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/App.java +++ b/monostate/src/main/java/com/iluwatar/monostate/App.java @@ -24,7 +24,6 @@ */ package com.iluwatar.monostate; - /** * The MonoState pattern ensures that all instances of the class will have the same state. This can * be used a direct replacement of the Singleton pattern. @@ -49,5 +48,4 @@ public static void main(String[] args) { loadBalancer1.serverRequest(new Request("Hello")); loadBalancer2.serverRequest(new Request("Hello World")); } - } diff --git a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java index 285dc573f38a..b93ae2789e32 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java +++ b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java @@ -33,26 +33,22 @@ * instances of the class share the same state, all instances will delegate to the same server on * receiving a new Request. */ - public class LoadBalancer { private static final List SERVERS = new ArrayList<>(); private static int lastServedId; static { var id = 0; - for (var port : new int[]{8080, 8081, 8082, 8083, 8084}) { + for (var port : new int[] {8080, 8081, 8082, 8083, 8084}) { SERVERS.add(new Server("localhost", port, ++id)); } } - /** - * Add new server. - */ + /** Add new server. */ public final void addServer(Server server) { synchronized (SERVERS) { SERVERS.add(server); } - } public final int getNoOfServers() { @@ -63,9 +59,7 @@ public int getLastServedId() { return lastServedId; } - /** - * Handle request. - */ + /** Handle request. */ public synchronized void serverRequest(Request request) { if (lastServedId >= SERVERS.size()) { lastServedId = 0; @@ -73,5 +67,4 @@ public synchronized void serverRequest(Request request) { var server = SERVERS.get(lastServedId++); server.serve(request); } - } diff --git a/monostate/src/main/java/com/iluwatar/monostate/Request.java b/monostate/src/main/java/com/iluwatar/monostate/Request.java index 0df3af5a9962..e08f2dd472dd 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/Request.java +++ b/monostate/src/main/java/com/iluwatar/monostate/Request.java @@ -24,14 +24,5 @@ */ package com.iluwatar.monostate; -/** - * The Request class. A {@link Server} can handle an instance of a Request. - */ - -public class Request { - public final String value; - - public Request(String value) { - this.value = value; - } -} +/** The Request record. A {@link Server} can handle an instance of a Request. */ +public record Request(String value) {} diff --git a/monostate/src/main/java/com/iluwatar/monostate/Server.java b/monostate/src/main/java/com/iluwatar/monostate/Server.java index e0a83059a4fd..e0af58a82e63 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/Server.java +++ b/monostate/src/main/java/com/iluwatar/monostate/Server.java @@ -24,6 +24,7 @@ */ package com.iluwatar.monostate; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** @@ -31,31 +32,26 @@ * in a simplistic Round Robin fashion. */ @Slf4j +@Getter public class Server { public final String host; public final int port; public final int id; - /** - * Constructor. - */ + /** Constructor. */ public Server(String host, int port, int id) { this.host = host; this.port = port; this.id = id; } - public String getHost() { - return host; - } - - public int getPort() { - return port; - } - public void serve(Request request) { - LOGGER.info("Server ID {} associated to host : {} and port {}. Processed request with value {}", - id, host, port, request.value); + LOGGER.info( + "Server ID {} associated to host : {} and port {}. Processed request with value {}", + id, + host, + port, + request.value()); } } diff --git a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java index a701787c53e3..82ea4e1952cc 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java @@ -28,15 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application Test Entry - */ - +/** Application Test Entry */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java index 96f98a80d832..10a46e016a67 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java @@ -35,11 +35,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/21/15 - 12:26 PM - * - * @author Jeroen Meulemeester - */ +/** LoadBalancerTest */ class LoadBalancerTest { @Test @@ -72,7 +68,5 @@ void testServe() { verify(server, times(2)).serve(request); verifyNoMoreInteractions(server); - } - } diff --git a/multiton/README.md b/multiton/README.md index eec858a0fd75..34e37866a7a7 100644 --- a/multiton/README.md +++ b/multiton/README.md @@ -1,140 +1,166 @@ --- -title: Multiton +title: "Multiton Pattern in Java: Mastering Advanced Singleton Variants" +shortTitle: Multiton +description: "Learn how the Multiton pattern in Java ensures unique named instances and provides a global access point. Discover implementation tips and code examples." category: Creational language: en tag: - - Instantiation + - Decoupling + - Instantiation + - Object composition --- ## Also known as -Registry +* Registry of Singletons -## Intent +## Intent of Multiton Design Pattern -Ensure a class only has a limited number of instances and provide a global point of access to them. +The Multiton pattern in Java ensures a class has only unique named instances, providing a global point of access to them. Each named instance is accessed through a unique key, making it an essential part of Java design patterns. -## Explanation +## Detailed Explanation of Multiton Pattern with Real-World Examples Real-world example -> The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By -> definition, there's always nine of them. +> A real-world example of the Multiton pattern is a printer management system in a large office. In this scenario, the office has several printers, each serving a different department. Instead of creating a new printer object every time a printing request is made, the system uses the Multiton pattern to ensure that each department has exactly one printer instance. When a printing request comes from a specific department, the system checks the registry of printer instances and retrieves the existing printer for that department. If no printer exists for that department, it creates one, registers it, and then returns it. This ensures efficient management of printer resources and prevents unnecessary creation of multiple printer instances for the same department. In plain words -> Multiton pattern ensures there are a predefined amount of instances available globally. +> The Multiton pattern is an extension of the Singleton pattern, offering a way to have a map of unique named instances instead of a single instance. This makes it a valuable Java design pattern for managing named instances efficiently. Wikipedia says -> In software engineering, the multiton pattern is a design pattern which generalizes the singleton -> pattern. Whereas the singleton allows only one instance of a class to be created, the multiton -> pattern allows for the controlled creation of multiple instances, which it manages through the use -> of a map. +> In software engineering, the multiton pattern is a design pattern which generalizes the singleton pattern. Whereas the singleton allows only one instance of a class to be created, the multiton pattern allows for the controlled creation of multiple instances, which it manages through the use of a map. -**Programmatic Example** +## Programmatic Example of Multiton Pattern in Java + +In this tutorial, we’ll explore how to implement the Multiton pattern in Java, covering its structure, benefits, and providing code examples. By following these implementation tips, you’ll be able to effectively utilize this Java design pattern. + +The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By definition, there's always nine of them. `Nazgul` is the multiton class. ```java public enum NazgulName { - KHAMUL, MURAZOR, DWAR, JI_INDUR, AKHORAHIL, HOARMURATH, ADUNAPHEL, REN, UVATHA + KHAMUL, MURAZOR, DWAR, JI_INDUR, AKHORAHIL, HOARMURATH, ADUNAPHEL, REN, UVATHA } public final class Nazgul { - private static final Map nazguls; - - private final NazgulName name; - - static { - nazguls = new ConcurrentHashMap<>(); - nazguls.put(NazgulName.KHAMUL, new Nazgul(NazgulName.KHAMUL)); - nazguls.put(NazgulName.MURAZOR, new Nazgul(NazgulName.MURAZOR)); - nazguls.put(NazgulName.DWAR, new Nazgul(NazgulName.DWAR)); - nazguls.put(NazgulName.JI_INDUR, new Nazgul(NazgulName.JI_INDUR)); - nazguls.put(NazgulName.AKHORAHIL, new Nazgul(NazgulName.AKHORAHIL)); - nazguls.put(NazgulName.HOARMURATH, new Nazgul(NazgulName.HOARMURATH)); - nazguls.put(NazgulName.ADUNAPHEL, new Nazgul(NazgulName.ADUNAPHEL)); - nazguls.put(NazgulName.REN, new Nazgul(NazgulName.REN)); - nazguls.put(NazgulName.UVATHA, new Nazgul(NazgulName.UVATHA)); - } - - private Nazgul(NazgulName name) { - this.name = name; - } - - public static Nazgul getInstance(NazgulName name) { - return nazguls.get(name); - } - - public NazgulName getName() { - return name; - } + private static final Map nazguls; + + @Getter + private final NazgulName name; + + static { + nazguls = new ConcurrentHashMap<>(); + nazguls.put(NazgulName.KHAMUL, new Nazgul(NazgulName.KHAMUL)); + nazguls.put(NazgulName.MURAZOR, new Nazgul(NazgulName.MURAZOR)); + nazguls.put(NazgulName.DWAR, new Nazgul(NazgulName.DWAR)); + nazguls.put(NazgulName.JI_INDUR, new Nazgul(NazgulName.JI_INDUR)); + nazguls.put(NazgulName.AKHORAHIL, new Nazgul(NazgulName.AKHORAHIL)); + nazguls.put(NazgulName.HOARMURATH, new Nazgul(NazgulName.HOARMURATH)); + nazguls.put(NazgulName.ADUNAPHEL, new Nazgul(NazgulName.ADUNAPHEL)); + nazguls.put(NazgulName.REN, new Nazgul(NazgulName.REN)); + nazguls.put(NazgulName.UVATHA, new Nazgul(NazgulName.UVATHA)); + } + + private Nazgul(NazgulName name) { + this.name = name; + } + + public static Nazgul getInstance(NazgulName name) { + return nazguls.get(name); + } } ``` And here's how we access the `Nazgul` instances. ```java -// eagerly initialized multiton -LOGGER.info("Printing out eagerly initialized multiton contents"); -LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL)); -LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR)); -LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR)); -LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR)); -LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL)); -LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH)); -LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL)); -LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN)); -LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA)); - -// enum multiton -LOGGER.info("Printing out enum-based multiton contents"); -LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL); -LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR); -LOGGER.info("DWAR={}", NazgulEnum.DWAR); -LOGGER.info("JI_INDUR={}", NazgulEnum.JI_INDUR); -LOGGER.info("AKHORAHIL={}", NazgulEnum.AKHORAHIL); -LOGGER.info("HOARMURATH={}", NazgulEnum.HOARMURATH); -LOGGER.info("ADUNAPHEL={}", NazgulEnum.ADUNAPHEL); -LOGGER.info("REN={}", NazgulEnum.REN); -LOGGER.info("UVATHA={}", NazgulEnum.UVATHA); + public static void main(String[] args) { + // eagerly initialized multiton + LOGGER.info("Printing out eagerly initialized multiton contents"); + LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL)); + LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR)); + LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR)); + LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR)); + LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL)); + LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH)); + LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL)); + LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN)); + LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA)); + + // enum multiton + LOGGER.info("Printing out enum-based multiton contents"); + LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL); + LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR); + LOGGER.info("DWAR={}", NazgulEnum.DWAR); + LOGGER.info("JI_INDUR={}", NazgulEnum.JI_INDUR); + LOGGER.info("AKHORAHIL={}", NazgulEnum.AKHORAHIL); + LOGGER.info("HOARMURATH={}", NazgulEnum.HOARMURATH); + LOGGER.info("ADUNAPHEL={}", NazgulEnum.ADUNAPHEL); + LOGGER.info("REN={}", NazgulEnum.REN); + LOGGER.info("UVATHA={}", NazgulEnum.UVATHA); +} ``` Program output: ``` -20:35:07.413 [main] INFO com.iluwatar.multiton.App - Printing out eagerly initialized multiton contents -20:35:07.417 [main] INFO com.iluwatar.multiton.App - KHAMUL=com.iluwatar.multiton.Nazgul@48cf768c -20:35:07.419 [main] INFO com.iluwatar.multiton.App - MURAZOR=com.iluwatar.multiton.Nazgul@7960847b -20:35:07.419 [main] INFO com.iluwatar.multiton.App - DWAR=com.iluwatar.multiton.Nazgul@6a6824be -20:35:07.419 [main] INFO com.iluwatar.multiton.App - JI_INDUR=com.iluwatar.multiton.Nazgul@5c8da962 -20:35:07.419 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=com.iluwatar.multiton.Nazgul@512ddf17 -20:35:07.419 [main] INFO com.iluwatar.multiton.App - HOARMURATH=com.iluwatar.multiton.Nazgul@2c13da15 -20:35:07.419 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=com.iluwatar.multiton.Nazgul@77556fd -20:35:07.419 [main] INFO com.iluwatar.multiton.App - REN=com.iluwatar.multiton.Nazgul@368239c8 -20:35:07.420 [main] INFO com.iluwatar.multiton.App - UVATHA=com.iluwatar.multiton.Nazgul@9e89d68 -20:35:07.420 [main] INFO com.iluwatar.multiton.App - Printing out enum-based multiton contents -20:35:07.420 [main] INFO com.iluwatar.multiton.App - KHAMUL=KHAMUL -20:35:07.420 [main] INFO com.iluwatar.multiton.App - MURAZOR=MURAZOR -20:35:07.420 [main] INFO com.iluwatar.multiton.App - DWAR=DWAR -20:35:07.420 [main] INFO com.iluwatar.multiton.App - JI_INDUR=JI_INDUR -20:35:07.421 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=AKHORAHIL -20:35:07.421 [main] INFO com.iluwatar.multiton.App - HOARMURATH=HOARMURATH -20:35:07.421 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=ADUNAPHEL -20:35:07.421 [main] INFO com.iluwatar.multiton.App - REN=REN -20:35:07.421 [main] INFO com.iluwatar.multiton.App - UVATHA=UVATHA +15:16:10.597 [main] INFO com.iluwatar.multiton.App -- Printing out eagerly initialized multiton contents +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- KHAMUL=com.iluwatar.multiton.Nazgul@4141d797 +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- MURAZOR=com.iluwatar.multiton.Nazgul@38cccef +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- DWAR=com.iluwatar.multiton.Nazgul@5679c6c6 +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- JI_INDUR=com.iluwatar.multiton.Nazgul@27ddd392 +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- AKHORAHIL=com.iluwatar.multiton.Nazgul@19e1023e +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- HOARMURATH=com.iluwatar.multiton.Nazgul@7cef4e59 +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- ADUNAPHEL=com.iluwatar.multiton.Nazgul@64b8f8f4 +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- REN=com.iluwatar.multiton.Nazgul@2db0f6b2 +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- UVATHA=com.iluwatar.multiton.Nazgul@3cd1f1c8 +15:16:10.600 [main] INFO com.iluwatar.multiton.App -- Printing out enum-based multiton contents +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- KHAMUL=KHAMUL +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- MURAZOR=MURAZOR +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- DWAR=DWAR +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- JI_INDUR=JI_INDUR +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- AKHORAHIL=AKHORAHIL +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- HOARMURATH=HOARMURATH +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- ADUNAPHEL=ADUNAPHEL +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- REN=REN +15:16:10.601 [main] INFO com.iluwatar.multiton.App -- UVATHA=UVATHA ``` -## Class diagram +## When to Use the Multiton Pattern in Java + +Use cases for the Multiton pattern in Java + +* A class must have named instances, but only one instance for each unique key. +* Global access to these instances is necessary without requiring global variables. +* You want to manage shared resources categorized by key. + +## Real-World Applications of Multiton Pattern in Java + +* Managing database connections in different contexts. +* Configuration settings for different environments in an application. + +## Benefits and Trade-offs of Multiton Pattern + +Benefits: + +* Ensures controlled access to instances based on key. +* Reduces global state usage by encapsulating instance management within the pattern. + +Trade-offs: + +* Increased memory usage if not managed properly due to multiple instances. +* Potential issues with concurrency if not implemented with thread safety in mind. -![alt text](./etc/multiton.png "Multiton") +## Related Java Design Patterns -## Applicability +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Multiton can be seen as an extension of the Singleton pattern where Singleton allows only one instance of a class, Multiton allows one instance per key. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Multiton uses a method to create or retrieve instances, similar to how a Factory Method controls object creation. -Use the Multiton pattern when +## References and Credits -* There must be a specific number of instances of a class, and they must be accessible to clients from -a well-known access point. +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) diff --git a/multiton/pom.xml b/multiton/pom.xml index 6d2430560a5c..f86151ca40f6 100644 --- a/multiton/pom.xml +++ b/multiton/pom.xml @@ -34,6 +34,14 @@ multiton + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/multiton/src/main/java/com/iluwatar/multiton/App.java b/multiton/src/main/java/com/iluwatar/multiton/App.java index 80aa9a4c8bee..5e932a86b093 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/App.java +++ b/multiton/src/main/java/com/iluwatar/multiton/App.java @@ -32,7 +32,7 @@ * the Multiton by passing an enumeration as a parameter. * *

There is more than one way to implement the multiton design pattern. In the first example - * {@link Nazgul} is the Multiton and we can ask single {@link Nazgul} from it using {@link + * {@link Nazgul} is the Multiton, and we can ask single {@link Nazgul} from it using {@link * NazgulName}. The {@link Nazgul}s are statically initialized and stored in a concurrent hash map. * *

In the enum implementation {@link NazgulEnum} is the multiton. It is static and mutable diff --git a/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java b/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java index 2010f0565b5e..d1c46bfa9e88 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java +++ b/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; /** * Nazgul is a Multiton class. Nazgul instances can be queried using {@link #getInstance} method. @@ -34,7 +35,7 @@ public final class Nazgul { private static final Map nazguls; - private final NazgulName name; + @Getter private final NazgulName name; static { nazguls = new ConcurrentHashMap<>(); @@ -56,8 +57,4 @@ private Nazgul(NazgulName name) { public static Nazgul getInstance(NazgulName name) { return nazguls.get(name); } - - public NazgulName getName() { - return name; - } } diff --git a/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java b/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java index e9fef580337e..bde5c0d4a291 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java +++ b/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java @@ -24,9 +24,7 @@ */ package com.iluwatar.multiton; -/** - * enum based multiton implementation. - */ +/** enum based multiton implementation. */ public enum NazgulEnum { KHAMUL, MURAZOR, diff --git a/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java b/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java index 44474efb8ce6..0d9c93cac265 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java +++ b/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java @@ -24,9 +24,7 @@ */ package com.iluwatar.multiton; -/** - * Each Nazgul has different {@link NazgulName}. - */ +/** Each Nazgul has different {@link NazgulName}. */ public enum NazgulName { KHAMUL, MURAZOR, diff --git a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java index 16fbf361338e..95478068b92a 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Test if the application starts without throwing an exception. - */ - +/** Test if the application starts without throwing an exception. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java index ef7a96b901bc..916409119ed0 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java @@ -29,15 +29,12 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -/** - * @author anthony - * - */ +/** NazgulEnumTest */ class NazgulEnumTest { /** - * Check that multiple calls to any one of the instances in the multiton returns - * only that one particular instance, and do that for all instances in multiton + * Check that multiple calls to any one of the instances in the multiton returns only that one + * particular instance, and do that for all instances in multiton */ @ParameterizedTest @EnumSource diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java index 624f4198ba6c..8d7c3bfb844b 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java @@ -30,11 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/22/15 - 22:28 AM - * - * @author Jeroen Meulemeester - */ +/** NazgulTest */ class NazgulTest { /** diff --git a/mute-idiom/README.md b/mute-idiom/README.md index 1b25e95fcaf5..3999d7ab9991 100644 --- a/mute-idiom/README.md +++ b/mute-idiom/README.md @@ -1,95 +1,127 @@ --- -title: Mute Idiom -category: Idiom +title: "Mute Idiom Pattern in Java: Implementing Unobtrusive Exception Suppression" +shortTitle: Mute Idiom +description: "Discover the Mute Idiom design pattern in Java, which simplifies error handling by muting non-critical exceptions. Learn how to apply it effectively in multithreaded environments." +category: Behavioral language: en tag: -- Decoupling + - Context + - Decoupling + - Idiom + - Error handling + - Synchronization + - Thread management --- +## Also known as -## Intent -Provide a template to suppress any exceptions that either are declared but cannot occur or should only be logged; -while executing some business logic. The template removes the need to write repeated `try-catch` blocks. +* Exception Suppression +* Quiet Exception +## Intent of Mute Idiom Design Pattern -## Explanation -Real World Example -> The vending machine contained in your office displays a warning when making a transaction. The issue occurs when the -> customer decides to pay with physical money that is not recognized by the system. However, you and everyone in the office -> only pays with the company credit card and will never encounter this issue. +The Mute Idiom in Java is a design pattern that simplifies error handling by suppressing non-critical exceptions. This approach is especially useful in multithreaded applications and complex control flow scenarios. + +## Detailed Explanation of Mute Idiom Pattern with Real-World Examples + +Real-world example + +> A real-world analogy of the Mute Idiom is found in car door locking mechanisms, where non-critical exceptions (like an already locked door) are ignored, simplifying the system's logic. Similarly, in Java, the Mute Idiom focuses on essential operations, avoiding unnecessary exception handling. +> +> Imagine a car equipped with an automatic locking system that attempts to lock all doors when the car reaches a certain speed. In this system, if one of the doors is already locked, the system doesn't need to alert the driver or perform any special handling; it simply skips locking that door and continues with the others. The locking system "mutes" the handling of already locked doors, focusing only on those that need to be locked. This approach simplifies the logic and avoids unnecessary checks, similar to how the Mute Idiom in software development suppresses handling trivial exceptions. In plain words -> The Mute Idiom design pattern is used to reduce the requirement of catching exceptions when they cannot be thrown or -> should be ignored when thrown. This applies in cases such as API functions, where the underlying code cannot be changed -> to include individual use cases. -Programmatic Example +> The Mute Idiom design pattern suppresses the handling of trivial or non-critical exceptions to simplify code. -Converting the real-world example into a programmatic representation, we represent an API function as the -office Vending machine +## Programmatic Example of Mute Idiom Pattern in Java -```java +In the following Java code example, we demonstrate the Mute Idiom by muting non-critical exceptions during the resource management process. This approach ensures error handling does not interrupt the main logic. + +The Mute Idiom is a design pattern that is used to simplify error handling by muting exceptions that are deemed non-critical or expected in specific contexts. This pattern is particularly useful in multithreaded or complex control flow environments. -public class VendingMachine { - public void purchaseItem(int itemID, PaymentMethod paymentMethod) throws Exception { - if (paymentMethod == PaymentMethod.Cash) { - throw new Exception(); - } - else { - System.out.println("Here is your item"); - } - } +We have a `Resource` interface that has a `close` method which throws an `IOException`. + +```java +public interface Resource extends AutoCloseable { + @Override + void close() throws IOException; } ``` +We also have an `App` class that uses this `Resource`. In the `App` class, we have a `useOfLoggedMute` method that demonstrates the use of the Mute Idiom. Here, we acquire a `Resource`, utilize it, and then attempt to close it. The closing of the resource is done in a `finally` block to ensure that it is executed regardless of whether an exception is thrown or not. + ```java -public enum PaymentMethod { - Card,Cash +public class App { + // ... + + private static void useOfLoggedMute() { + Optional resource = Optional.empty(); + try { + resource = Optional.of(acquireResource()); + utilizeResource(resource.get()); + } finally { + resource.ifPresent(App::closeResource); + } + } + + // ... } ``` -We then run our office's daily routine, which involves purchasing items -from the vending machine with the company card, using the mute pattern to ignore the exceptions that can't be thrown -```java -package com.iluwatar.mute; +The `closeResource` method is where the Mute Idiom is applied. We use the `Mute.loggedMute` method to suppress any `IOException` that might be thrown when closing the resource. This is done because the failure to close a resource is considered a non-critical issue that does not affect the overall logic or outcome of the program. -public class Office { - private PaymentMethod companyCard = PaymentMethod.Card; - private VendingMachine officeVendingMachine = new VendingMachine(); +```java +public class App { + // ... - public static void main(String[] args) { - Office office = new Office(); - office.dailyRoutine(); + private static void closeResource(Resource resource) { + Mute.loggedMute(resource::close); + } - } - public void dailyRoutine() { - Mute.mute(() -> { - officeVendingMachine.purchaseItem(1,companyCard); - officeVendingMachine.purchaseItem(1,companyCard); - officeVendingMachine.purchaseItem(1,companyCard); - }); - } + // ... } - ``` +In this way, the Mute Idiom allows us to simplify error handling by reducing boilerplate code for expected exceptions, enhancing code readability and maintainability, and allowing uninterrupted execution for non-critical exceptions. + +## When to Use the Mute Idiom Pattern in Java + +The Mute Idiom is applicable in + +* Scenarios where certain exceptions are predictable and do not affect the overall logic or outcome. +* Commonly used in logging, cleanup operations, or interacting with third-party APIs in Java. + +## Mute Idiom Pattern Java Tutorials + +* [The Mute Design Pattern (JOOQ)](http://blog.jooq.org/2016/02/18/the-mute-design-pattern/) -## Class diagram -![alt text](./etc/mute-idiom.png "Mute Idiom") +## Real-World Applications of Mute Idiom Pattern in Java +* Muting exceptions in background tasks or threads where interruption is expected. +* Handling known issues in third-party libraries where exceptions can be safely ignored. -## Applicability -Use this idiom when -* an API declares some exception but can never throw that exception eg. ByteArrayOutputStream bulk write method. -* you need to suppress some exception just by logging it, such as closing a resource. +## Benefits and Trade-offs of Mute Idiom Pattern +Benefits: +Using the Mute Idiom +* Simplifies error handling by reducing boilerplate code for expected exceptions. +* Enhances code readability and maintainability. +* Allows uninterrupted execution for non-critical exceptions. -## Credits +Trade-offs: +* Can lead to missed critical issues if overused or misapplied. +* Makes debugging harder if exceptions are muted indiscriminately. -* [JOOQ: Mute Design Pattern](http://blog.jooq.org/2016/02/18/the-mute-design-pattern/) +## Related Java Design Patterns +* [Null Object](https://java-design-patterns.com/patterns/null-object/): Both aim to simplify error handling; Null Object avoids null checks while Mute Idiom avoids exception handling complexities. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used to wrap functionality with additional error handling or muting behaviors. +## References and Credits +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) diff --git a/mute-idiom/pom.xml b/mute-idiom/pom.xml index 4f521c638c09..40654d7de546 100644 --- a/mute-idiom/pom.xml +++ b/mute-idiom/pom.xml @@ -34,6 +34,14 @@ mute-idiom + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/App.java b/mute-idiom/src/main/java/com/iluwatar/mute/App.java index 341a9bb16d5f..75525731a8d8 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/App.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/App.java @@ -34,6 +34,7 @@ * when all we can do to handle the exception is to log it. This pattern should not be used * everywhere. It is very important to logically handle the exceptions in a system, but some * situations like the ones described above require this pattern, so that we don't need to repeat + * *

  * 
  *   try {
@@ -42,7 +43,9 @@
  *     // ignore by logging or throw error if unexpected exception occurs
  *   }
  * 
- * 
every time we need to ignore an exception. + * + * + * every time we need to ignore an exception. */ @Slf4j public class App { diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java b/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java index 5c8d017b951f..9698f6d125a0 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java @@ -24,9 +24,7 @@ */ package com.iluwatar.mute; -/** - * A runnable which may throw exception on execution. - */ +/** A runnable which may throw exception on execution. */ @FunctionalInterface public interface CheckedRunnable { /** diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java b/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java index f1904a666468..8eab13a6d5f8 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java @@ -26,22 +26,21 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import lombok.extern.slf4j.Slf4j; -/** - * A utility class that allows you to utilize mute idiom. - */ +/** A utility class that allows you to utilize mute idiom. */ +@Slf4j public final class Mute { // The constructor is never meant to be called. - private Mute() { - } + private Mute() {} /** * Executes the runnable and throws the exception occurred within a {@link * AssertionError}. This method should be utilized to mute the operations that are guaranteed not - * to throw an exception. For instance {@link ByteArrayOutputStream#write(byte[])} declares in - * it's signature that it can throw an {@link IOException}, but in reality it cannot. This is - * because the bulk write method is not overridden in {@link ByteArrayOutputStream}. + * to throw an exception. For instance {@link ByteArrayOutputStream#write(byte[])} declares in its + * signature that it can throw an {@link IOException}, but in reality it cannot. This is because + * the bulk write method is not overridden in {@link ByteArrayOutputStream}. * * @param runnable a runnable that should never throw an exception on execution. */ diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java b/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java index cb32b27955e7..c4c4c65463eb 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java @@ -30,6 +30,4 @@ * Represents any resource that the application might acquire and that must be closed after it is * utilized. Example of such resources can be a database connection, open files, sockets. */ -public interface Resource extends Closeable { - -} +public interface Resource extends Closeable {} diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java index ecfb1dabba77..ef8dff8b0318 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.mute; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Mute idiom example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Mute idiom example runs without errors. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java index 1fb5cf5b5a72..06579d454fef 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.mute; +import static org.junit.jupiter.api.Assertions.*; + import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Test for the mute-idiom pattern - */ +/** Test for the mute-idiom pattern */ class MuteTest { private static final Logger LOGGER = LoggerFactory.getLogger(MuteTest.class); @@ -42,7 +40,8 @@ class MuteTest { private static final String MESSAGE = "should not occur"; @Test - void muteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { + void + muteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { assertDoesNotThrow(() -> Mute.mute(this::methodNotThrowingAnyException)); } @@ -52,7 +51,8 @@ void muteShouldRethrowUnexpectedExceptionAsAssertionError() { } @Test - void loggedMuteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { + void + loggedMuteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { assertDoesNotThrow(() -> Mute.mute(this::methodNotThrowingAnyException)); } @@ -63,10 +63,9 @@ void loggedMuteShouldLogExceptionTraceBeforeSwallowingIt() { Mute.loggedMute(this::methodThrowingException); - assertTrue(new String(stream.toByteArray()).contains(MESSAGE)); + assertTrue(stream.toString().contains(MESSAGE)); } - private void methodNotThrowingAnyException() { LOGGER.info("Executed successfully"); } diff --git a/naked-objects/README.md b/naked-objects/README.md index 8ed8210de4d2..0b9b81fbd972 100644 --- a/naked-objects/README.md +++ b/naked-objects/README.md @@ -1,44 +1,41 @@ --- -title: Naked Objects +title: "Naked Objects Pattern in Java: Leveraging Domain Objects for Dynamic UI Generation" +shortTitle: Naked Objects +description: “Explore the Naked Objects design pattern in Java. Learn how to dynamically create user interfaces from domain objects with examples and best practices.” category: Architectural language: en tag: + - Architecture + - Business - Decoupling - - Extensibility - - Presentation + - Domain + - Enterprise patterns + - Instantiation + - Object composition + - Persistence --- -## Intent +## Also known as -> The naked object design pattern is a way to build user interfaces (UIs) for software applications that is based on the idea of direct manipulation. This means that users interact directly with the underlying domain objects of the application, without any intermediary UI elements. +* Transparent Objects -> The naked object pattern is implemented by exposing the domain objects to the user in a way that is both meaningful and accessible. This is typically done by generating a UI automatically from the domain object definitions. The UI presents the domain objects to the user in a simple and straightforward way, allowing them to create, retrieve, update, and delete objects, as well as invoke methods on them. +## Intent of Naked Objects Design Pattern -> The naked object pattern has a number of advantages, including: +To enable the rapid development of maintainable systems by representing all business objects directly and automatically creating the user interface from these definitions. Naked Objects design pattern is essential for developers aiming to align user interfaces with domain models seamlessly. -> 1. Reduced development time and cost: The naked object pattern can significantly reduce the time and cost required to develop and maintain a software application. This is because the UI is generated automatically, and the domain objects are designed to be both user-visible and manipulatable. +## Detailed Explanation of Naked Objects Pattern with Real-World Examples -> 2. Empowered users: The naked object pattern gives users direct access to the underlying domain objects of the application. This allows them to interact with the application in a way that is natural and intuitive. +Real-world example -> 3. Increased flexibility and adaptability: The naked object pattern is highly flexible and adaptable. This is because the UI is generated from the domain object definitions, which means that the UI can be easily changed as the domain model evolves. - -## Explanation - -**In plain words** - -> Imagine you are building a software application to manage a customer database. You could use the naked object pattern to build the UI for this application as follows: - -> 1. Define the domain objects for your application. This includes objects such as Customer, Order, and Product. - -> 2. Implement the business logic for your application on these domain objects. For example, you could implement methods to create a new customer, add a product to an order, or calculate the total price of an order. - -> 3. Use a naked object framework to generate a UI for your application from the domain object definitions. +> In a Naked Objects design pattern, a company might develop a customer relationship management (CRM) system where each business object, such as "Customer," "Order," and "Product," is represented directly. This allows for a dynamic and adaptable UI reflecting the underlying domain model with minimal developer intervention. The user interface is automatically generated based on these domain objects, allowing sales representatives to view and edit customer information, track orders, and manage inventory without needing a separately designed UI. +> +> This approach ensures that any changes in the business logic or domain model are immediately reflected in the user interface, significantly reducing the development and maintenance time. For instance, if a new field, "Loyalty Points," is added to the "Customer" object to track rewards, this field automatically appears in the CRM's user interface without additional UI development. This keeps the system flexible and closely aligned with the evolving business needs. -> The generated UI will present the domain objects to the user in a simple and straightforward way. The user will be able to create, retrieve, update, and delete customers, orders, and products, as well as invoke methods on them. +In plain words -For example, the user could create a new customer by entering the customer's name, address, and phone number into the UI. The user could also retrieve a list of all customers by clicking a button. To update a customer's information, the user could simply change the corresponding values in the UI and click a save button. +> The Naked Objects design pattern automatically generates a user interface from domain objects, allowing for rapid development and easy maintenance by ensuring the UI directly reflects the underlying business model. -**Wikipedia says** +Wikipedia says > Naked objects is an architectural pattern used in software engineering. It is defined by three principles: > @@ -47,13 +44,14 @@ For example, the user could create a new customer by entering the customer's nam > > The naked object pattern's innovative feature arises by combining the 1st and 2nd principles into a 3rd principle: 3. The user interface shall be entirely automatically created from the definitions of the domain objects. This may be done using reflection or source code generation. -## Programmatic example +Architecture diagram -> Certainly, here's a programmatic example section for the Naked Objects pattern: +![Naked Objects Architecture Diagram](./etc/naked-objects-architecture-diagram.png) -## Programmatic Example -In the context of the Naked Objects pattern, let's consider a simplified example with domain objects representing books and authors. The example demonstrates how the Naked Objects pattern can be applied to create a user interface for managing a library catalog. +## Programmatic Example of Naked Objects Pattern in Java + +Consider a simplified example with domain objects representing books and authors. In a Java-based application using the Naked Objects pattern, we define domain objects such as `Book` and `Author`. This example illustrates how Naked Objects can streamline user interface generation and domain object manipulation. Suppose we have the following domain objects in a Java-based application: @@ -89,11 +87,11 @@ public class Author { } ```` +In this example, we define two domain objects: `Book` and `Author`. The `Book` class has properties for the `title` and `author`, as well as an action to `borrow` the book. The `Author` class has a property for the author's `name`, a collection of books they have written (`getBooks`), and an action to create a new book (`createBook`). -> In this example, we define two domain objects: Book and Author. The Book class has properties for the title and author, as well as an action to borrow the book. The Author class has a property for the author's name, a collection of books they have written, and an action to create a new book. +With the Naked Objects framework or tool, the user interface for managing books and authors can be automatically generated based on these domain object definitions. Users can interact with the generated UI to create, retrieve, update, and delete books and authors directly through a user-friendly interface. -> With the Naked Objects framework or tool, the user interface for managing books and authors can be automatically generated based on these domain object definitions. Users can interact with the generated UI to create, retrieve, update, and delete books and authors directly through a user-friendly interface. -> Here's how you can use these domain objects to create and interact with books and authors programmatically: +Here's how you can use these domain objects to create and interact with books and authors programmatically: ```java var author = new Author(); @@ -101,53 +99,44 @@ author.setName("J.K. Rowling"); var book = author.createBook("Harry Potter and the Philosopher's Stone"); book.setAuthor(author); book.borrow(); - var booksByAuthor = author.getBooks(); - ``` -> This example demonstrates how the Naked Objects pattern can be implemented in a Java-based application with domain objects for books and authors. Users can directly manipulate these domain objects through the generated user interface. - -## Applicability +This example demonstrates how the Naked Objects pattern can be implemented in a Java-based application with domain objects for books and authors. Users can directly manipulate these domain objects through the generated user interface. -> The naked objects pattern is applicable to a wide range of software applications, but it is particularly well-suited for applications where users need to have direct access to the underlying data model. This is often the case in business applications, such as: +## When to Use the Naked Objects Pattern in Java -* Customer relationship management (CRM) systems -* Enterprise resource planning (ERP) systems -* Human resources (HR) systems -* Order management systems -* Inventory management systems -* Asset management systems -* Project management systems -* Knowledge management systems +* When aiming to create a system where the domain objects can be easily understood and manipulated without an explicit user interface design. +* For applications requiring a dynamic, adaptable UI that reflects the underlying domain model with minimal developer intervention. -> The naked objects pattern can also be used to build UIs for scientific and engineering applications, such as: +## Real-World Applications of Naked Objects Pattern in Java -* Data analysis applications -* Simulation applications -* Modeling applications -* Visualization applications ->In general, the naked objects pattern is a good choice for any application where users need to be able to create, retrieve, update, and delete data, as well as run complex reports and analyses on that data. +* Enterprise applications where business rules and domain logic are primary. +* Systems that benefit from a dynamic and adaptive user interface. -## Known uses -> 1. Here are some specific examples of applications that have been built using the naked objects pattern: +## Benefits and Trade-offs of Naked Objects Pattern -> 2. The Department of Social and Family Affairs in Ireland uses the naked objects pattern for its Child Benefit Administration system. +Benefits: -> 3. The UK National Health Service uses the naked objects pattern for its Electronic Patient Record system. +* Rapid development +* Improved maintainability +* Easy domain understanding +* Alignment between the UI and business model -> 4. The Australian Taxation Office uses the naked objects pattern for its tax return processing system. +Trade-offs: -> 5. The US Department of Defense uses the naked objects pattern for its logistics management system. +* Reduced UI flexibility +* Potential over-exposure of the domain model +* Reliance on framework capabilities -## Quick start +## Related Java Design Patterns -Apache Isis is a Java framework that implements the naked objects pattern. Check out their [starter app](https://isis.apache.org/docs/2.0.0-M9/starters/simpleapp.html) to get started on building an application. +Active Record: Similar in exposing domain models directly, but Active Record typically involves persistence aspects as well. +Domain-Driven Design: Shares the focus on domain modeling but without the automatic UI generation of Naked Objects. -## Credits +## References and Credits -* [Richard Pawson - Naked Objects](http://downloads.nakedobjects.net/resources/Pawson%20thesis.pdf) -* [Apache Isis - Introducing Apache Isis](https://isis.apache.org/versions/1.16.0/pages/downloadable-presentations/resources/downloadable-presentations/IntroducingApacheIsis-notes.pdf) - - -```` +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Naked Objects](https://amzn.to/3yhrfQr) +* [Naked Objects (Richard Pawson)](http://downloads.nakedobjects.net/resources/Pawson%20thesis.pdf) +* [Introducing Apache Isis (Apache Isis)](https://isis.apache.org/versions/1.16.0/pages/downloadable-presentations/resources/downloadable-presentations/IntroducingApacheIsis-notes.pdf) diff --git a/naked-objects/etc/naked-objects-architecture-diagram.png b/naked-objects/etc/naked-objects-architecture-diagram.png new file mode 100644 index 000000000000..d11cd92ed784 Binary files /dev/null and b/naked-objects/etc/naked-objects-architecture-diagram.png differ diff --git a/notification/README.md b/notification/README.md index 619a4ad7ad71..e94421ff504e 100644 --- a/notification/README.md +++ b/notification/README.md @@ -1,251 +1,131 @@ -title: Notification -category: Behavioural +--- +title: "Notification Pattern in Java: Enhancing System Communication with Event Alerts" +shortTitle: Notification +description: "Learn how to implement the Notification design pattern in Java with detailed explanations, code examples, and use cases. Improve your design patterns knowledge and code quality." +category: Behavioral language: en tags: -- Decoupling -- Presentation -- Domain + - Asynchronous + - Decoupling + - Event-driven + - Messaging + - Publish/subscribe --- -## Intent +## Also known as + +* Event Listener -To capture information about errors and other information that occurs in the domain layer -which acts as the internal logic and validation tool. The notification then communicates this information -back to the presentation for handling and displaying to the user what errors have occurred and why. +## Intent of Notification Design Pattern -## Explanation +The Notification design pattern in Java aims to facilitate asynchronous communication between different parts of a system by allowing objects to subscribe to specific events and receive updates asynchronously when those events occur. -Real world example +## Detailed Explanation of Notification Pattern with Real-World Examples -> You need to register a worker for your company. The information submitted needs to be valid -> before the worker can be added to the database, and if there are any errors you need to know about them. +Real-world example + +> Consider a weather alert system as a real-world analogous example of the Notification design pattern. In this system, a weather station collects data on weather conditions like temperature, humidity, and storm alerts. Multiple subscribers, such as news agencies, smartphone weather apps, and emergency services, are interested in receiving updates about specific weather events, like severe storms or extreme temperatures. +> +> When the weather station detects a significant event, it publishes this information. All subscribed entities receive these updates automatically without the weather station needing to know the details of these subscribers. For instance, a news agency might use this information to update its weather report, while emergency services might use it to prepare for potential disasters. This system exemplifies the Notification pattern's ability to decouple the publisher (the weather station) from its subscribers and deliver timely updates efficiently. In plain words -> A notification is simply a way of collecting information about errors and communicating it to the user -> so that they are aware of them +> The Notification design pattern enables an object to automatically notify a list of interested observers about changes or events without knowing the specifics of the subscribers. -**Programatic example** -Building off the example of registering a worker for a company, we can now explore a coded example for a full picture -of how this pattern looks when coded up. For full code please visit the github repository. +## Programmatic Example of Notification Pattern in Java -To begin with, the user submits information to a form through the presentation layer of our software. The information -given to register a worker is the worker's name, occupation, and date of birth. The program will then make sure none of -these fields are blank (validation) and that the worker is over 18 years old. If there are any errors, -the program will inform the user. +The Java Notification pattern is used to capture information passed between layers, validate the information, and return any errors to the presentation layer if needed. It reduces coupling between the producer and consumer of events, enhances flexibility and reusability of components, and allows for dynamic event subscription and unsubscription. -The code for the form is given below. This form acts as our presentation layer, taking input from the user and printing -output using a LOGGER (in this case). The form then gives this information to the service layer RegisterWorkerService -through a data transfer object (DTO). +In this example, we'll use a form submission scenario to demonstrate the Notification pattern. The form is used to register a worker with their name, occupation, and date of birth. The form data is passed to the domain layer for validation, and any errors are returned to the presentation layer. -The service handles information validation and the presentation can then check the notification stored within the DTO for -any errors and display them to the user if necessary. Otherwise, it will inform the user the submission was processed -successfully. +Here's the `RegisterWorkerForm` class, which acts as our presentation layer. It takes the worker's details as input and submits the form. -form: ```java -/** - * The form submitted by the user, part of the presentation layer, - * linked to the domain layer through a data transfer object and - * linked to the service layer directly. - */ -@Slf4j -public class RegisterWorkerForm { - String name; - String occupation; - LocalDate dateOfBirth; - RegisterWorkerDto worker; - /** - * Service super type which the form uses as part of its service layer. - */ - RegisterWorkerService service = new RegisterWorkerService(); - - /** - * Creates the form. - * - * @param name name of worker - * @param occupation occupation of the worker - * @param dateOfBirth date of birth of the worker - */ - public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) { - this.name = name; - this.occupation = occupation; - this.dateOfBirth = dateOfBirth; - } +class RegisterWorkerForm { - /** - * Attempts to submit the form for registering a worker. - */ - public void submit() { - //Save worker information (like name, occupation, dob) to our transfer object to be communicated between layers - saveToWorker(); - //call the service layer to register our worker - service.registerWorker(worker); - - //check for any errors - if (worker.getNotification().hasErrors()) { - indicateErrors(); //displays errors to users - LOGGER.info("Not registered, see errors"); - } else { - LOGGER.info("Registration Succeeded"); + private RegisterWorkerForm registerWorkerForm; + + RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) { + // Initialize the form with the worker's details + } + + void submit() { + // Submit the form + // If there are any errors, they will be captured in the worker's notification } - } - - ... } ``` -The data transfer object (DTO) created stores the information submitted (name, occupation, date of birth), as well as -information on the notification after this data has been validated (stored in the DTO class it extends). -This acts as the link between the service layer and our domain layer which runs the internal logic. -It also holds information on the error types created. +The `RegisterWorker` class acts as our domain layer. It validates the worker's details and returns any errors through the `RegisterWorkerDto`. -DTO: ```java -/** - * Data transfer object which stores information about the worker. This is carried between - * objects and layers to reduce the number of method calls made. - */ -@Getter -@Setter -public class RegisterWorkerDto extends DataTransferObject { - private String name; - private String occupation; - private LocalDate dateOfBirth; - - /** - * Error for when name field is blank or missing. - */ - public static final NotificationError MISSING_NAME = - new NotificationError(1, "Name is missing"); - - /** - * Error for when occupation field is blank or missing. - */ - public static final NotificationError MISSING_OCCUPATION = - new NotificationError(2, "Occupation is missing"); - - /** - * Error for when date of birth field is blank or missing. - */ - public static final NotificationError MISSING_DOB = - new NotificationError(3, "Date of birth is missing"); - - /** - * Error for when date of birth is less than 18 years ago. - */ - public static final NotificationError DOB_TOO_SOON = - new NotificationError(4, "Worker registered must be over 18"); - - - protected RegisterWorkerDto() { - super(); - } - - ... +class RegisterWorker { + + RegisterWorker(String name, String occupation, LocalDate dateOfBirth) { + // Validate the worker's details + // If there are any errors, add them to the notification + } } ``` -These errors are stored within a simple wrapper class called NotificationError. - -Our service layer (RegisterWorkerService) represents the framework of our service layer. Currently, it will -run the commands necessary to validate our Java object without handling any of the internal logic itself, -passing on the work, along with our DTO, to the domain layer. - -This validation itself is done in RegisterWorker which works within our domain layer. ServerCommand acts as -a SuperType here for the domain and holds any DTOs needed. If it passes validation, our worker is then added into -the database as submission was successful! +Finally, the `App` class is where the form is created and submitted. -validation: ```java -/** - * Handles internal logic and validation for worker registration. - * Part of the domain layer which collects information and sends it back to the presentation. - */ -@Slf4j -public class RegisterWorker extends ServerCommand { - protected RegisterWorker(RegisterWorkerDto worker) { - super(worker); - } +public class App { - /** - * Validates the data provided and adds it to the database in the backend. - */ - public void run() { - //make sure the information submitted is valid - validate(); - if (!super.getNotification().hasErrors()) { - //Add worker to system in backend (not implemented here) - LOGGER.info("Register worker in backend system"); - } + public static void main(String[] args) { + var form = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1)); + form.submit(); } - - /** - * Validates our data. Checks for any errors and if found, stores them in our notification. - */ - private void validate() { - var ourData = ((RegisterWorkerDto) this.data); - //check if any of submitted data is not given - failIfNullOrBlank(ourData.getName(), RegisterWorkerDto.MISSING_NAME); - failIfNullOrBlank(ourData.getOccupation(), RegisterWorkerDto.MISSING_OCCUPATION); - failIfNullOrBlank(ourData.getDateOfBirth().toString(), RegisterWorkerDto.MISSING_DOB); - //only if DOB is not blank, then check if worker is over 18 to register. - if (!super.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)) { - var dateOfBirth = ourData.getDateOfBirth(); - var current = now().minusYears(18); - fail(dateOfBirth.compareTo(current) > 0, RegisterWorkerDto.DOB_TOO_SOON); - } - } - - ... } ``` -After all of this explanation, we can then simulate the following inputs into the form and submit them: - -input: -```java - /** - * Variables to be submitted in the form. - */ - private static final String NAME = ""; - private static final String OCCUPATION = ""; - private static final LocalDate DATE_OF_BIRTH = LocalDate.of(2016, 7, 13); - - RegisterWorkerForm form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH); - form.submit(); -``` +In this example, if the worker's details are invalid (e.g. the name is empty), the `RegisterWorker` class will add an error to the notification. The `RegisterWorkerForm` class can then check the notification for any errors after submission. This demonstrates the Notification pattern, where information is passed between layers and any errors are returned to the presentation layer. The form then processes the submission and returns these error messages to the user, showing our notification worked. -output: +Example output: + ```java -18:10:00.075 [main] INFO com.iluwater.RegisterWorkerForm - Error 1: Name is missing: "" -18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 2: Occupation is missing: "" -18:10:00.079 [main] INFO com.iluwater.RegisterWorkerForm - Error 4: Worker registered must be over 18: "2016-07-13" -18:10:00.080 [main] INFO com.iluwater.RegisterWorkerForm - Not registered, see errors +18:10:00.075 [main] INFO com.iluwatar.RegisterWorkerForm - Error 1: Name is missing: "" +18:10:00.079 [main] INFO com.iluwatar.RegisterWorkerForm - Error 2: Occupation is missing: "" +18:10:00.079 [main] INFO com.iluwatar.RegisterWorkerForm - Error 4: Worker registered must be over 18: "2016-07-13" +18:10:00.080 [main] INFO com.iluwatar.RegisterWorkerForm - Not registered, see errors ``` -## Class diagram +## When to Use the Notification Pattern in Java + +* When a change to one object requires changing others, and you don’t know how many objects need to be changed. +* When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently. +* When a system component must be notified of events without making assumptions about the system’s other components. + +## Real-World Applications of Notification Pattern in Java + +* GUI frameworks where user actions trigger responses in the application. +* Notification systems in large-scale distributed systems. +* Event management in microservices architecture. + +## Benefits and Trade-offs of Notification Pattern -![alt text](./etc/notification.urm.png "Notification") +Benefits: -## Applicability +* Reduces coupling between the producer and consumer of events. +* Enhances flexibility and reusability of components. +* Allows for dynamic subscription and unsubscription to events. -Use the notification pattern when: +Trade-offs: -* You wish to communicate information about errors between the domain layer and the presentation layer. This is most applicable when a seperated presentation pattern is being used as this does not allow for direct communication between the domain and presentation. +* Can lead to a complex system if not managed well, due to the dynamic nature of subscriptions. +* Debugging can be challenging due to the asynchronous and decoupled nature of events. -## Related patterns +## Related Java Design Patterns -* [Service Layer](https://java-design-patterns.com/patterns/service-layer/) -* [Data Transfer Object](https://java-design-patterns.com/patterns/data-transfer-object/) -* [Domain Model](https://java-design-patterns.com/patterns/domain-model/) -* [Remote Facade](https://martinfowler.com/eaaCatalog/remoteFacade.html) -* [Autonomous View](https://martinfowler.com/eaaDev/AutonomousView.html) -* [Layer Supertype](https://martinfowler.com/eaaCatalog/layerSupertype.html) -* [Separated Presentation](https://java-design-patterns.com/patterns/data-transfer-object/) +* [Command](https://java-design-patterns.com/patterns/command/): Can be used to encapsulate a request as an object, often used in conjunction with notifications to decouple the sender and receiver. +* [Mediator](https://java-design-patterns.com/patterns/mediator/): Facilitates centralized communication between objects, whereas the Notification pattern is more decentralized. +* [Observer](https://java-design-patterns.com/patterns/observer/): A foundational pattern for the Notification pattern, focusing on one-to-many dependency relationships. -## Credits +## References and Credits -* [Martin Fowler - Notification Pattern](https://martinfowler.com/eaaDev/Notification.html) \ No newline at end of file +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui) +* [Notification Pattern (Martin Fowler)](https://martinfowler.com/eaaDev/Notification.html) diff --git a/notification/pom.xml b/notification/pom.xml index 9755f96f369b..829c9014919a 100644 --- a/notification/pom.xml +++ b/notification/pom.xml @@ -1,4 +1,30 @@ + @@ -9,6 +35,14 @@ 1.26.0-SNAPSHOT + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-params diff --git a/notification/src/main/java/com/iluwatar/App.java b/notification/src/main/java/com/iluwatar/App.java index 70e51236c115..fdf041929230 100644 --- a/notification/src/main/java/com/iluwatar/App.java +++ b/notification/src/main/java/com/iluwatar/App.java @@ -1,16 +1,40 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import java.time.LocalDate; /** - * The notification pattern captures information passed between layers, validates the information, and returns - * any errors to the presentation layer if needed. + * The notification pattern captures information passed between layers, validates the information, + * and returns any errors to the presentation layer if needed. * - *

In this code, this pattern is implemented through the example of a form being submitted to register - * a worker. The worker inputs their name, occupation, and date of birth to the RegisterWorkerForm (which acts - * as our presentation layer), and passes it to the RegisterWorker class (our domain layer) which validates it. - * Any errors caught by the domain layer are then passed back to the presentation layer through the - * RegisterWorkerDto.

+ *

In this code, this pattern is implemented through the example of a form being submitted to + * register a worker. The worker inputs their name, occupation, and date of birth to the + * RegisterWorkerForm (which acts as our presentation layer), and passes it to the RegisterWorker + * class (our domain layer) which validates it. Any errors caught by the domain layer are then + * passed back to the presentation layer through the RegisterWorkerDto. */ public class App { @@ -22,5 +46,4 @@ public static void main(String[] args) { var form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH); form.submit(); } - } diff --git a/notification/src/main/java/com/iluwatar/DataTransferObject.java b/notification/src/main/java/com/iluwatar/DataTransferObject.java index 23430e5707d3..e061a9976838 100644 --- a/notification/src/main/java/com/iluwatar/DataTransferObject.java +++ b/notification/src/main/java/com/iluwatar/DataTransferObject.java @@ -1,16 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import lombok.Getter; import lombok.NoArgsConstructor; /** - * Layer super type for all Data Transfer Objects. - * Also contains code for accessing our notification. + * Layer super type for all Data Transfer Objects. Also contains code for accessing our + * notification. */ @Getter @NoArgsConstructor public class DataTransferObject { private final Notification notification = new Notification(); - } diff --git a/notification/src/main/java/com/iluwatar/Notification.java b/notification/src/main/java/com/iluwatar/Notification.java index 93a40ecc7ff2..dc8a40ea7622 100644 --- a/notification/src/main/java/com/iluwatar/Notification.java +++ b/notification/src/main/java/com/iluwatar/Notification.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import java.util.ArrayList; @@ -6,9 +30,8 @@ import lombok.NoArgsConstructor; /** - * The notification. Used for storing errors and any other methods - * that may be necessary for when we send information back to the - * presentation layer. + * The notification. Used for storing errors and any other methods that may be necessary for when we + * send information back to the presentation layer. */ @Getter @NoArgsConstructor diff --git a/notification/src/main/java/com/iluwatar/NotificationError.java b/notification/src/main/java/com/iluwatar/NotificationError.java index d53d900e52c6..29aa0fd13398 100644 --- a/notification/src/main/java/com/iluwatar/NotificationError.java +++ b/notification/src/main/java/com/iluwatar/NotificationError.java @@ -1,11 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import lombok.AllArgsConstructor; import lombok.Getter; /** - * Error class for storing information on the error. - * Error ID is not necessary, but may be useful for serialisation. + * Error class for storing information on the error. Error ID is not necessary, but may be useful + * for serialisation. */ @Getter @AllArgsConstructor diff --git a/notification/src/main/java/com/iluwatar/RegisterWorker.java b/notification/src/main/java/com/iluwatar/RegisterWorker.java index 35b101a95e3c..5952010e3c88 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorker.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorker.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import java.time.LocalDate; @@ -5,19 +29,18 @@ import lombok.extern.slf4j.Slf4j; /** - * Class which handles actual internal logic and validation for worker registration. - * Part of the domain layer which collects information and sends it back to the presentation. + * Class which handles actual internal logic and validation for worker registration. Part of the + * domain layer which collects information and sends it back to the presentation. */ @Slf4j public class RegisterWorker extends ServerCommand { static final int LEGAL_AGE = 18; + protected RegisterWorker(RegisterWorkerDto worker) { super(worker); } - /** - * Validates the data provided and adds it to the database in the backend. - */ + /** Validates the data provided and adds it to the database in the backend. */ public void run() { validate(); @@ -26,12 +49,10 @@ public void run() { } } - /** - * Validates our data. Checks for any errors and if found, adds to notification. - */ + /** Validates our data. Checks for any errors and if found, adds to notification. */ private void validate() { var ourData = ((RegisterWorkerDto) this.data); - //check if any of submitted data is not given + // check if any of submitted data is not given // passing for empty value validation fail(isNullOrBlank(ourData.getName()), RegisterWorkerDto.MISSING_NAME); fail(isNullOrBlank(ourData.getOccupation()), RegisterWorkerDto.MISSING_OCCUPATION); @@ -69,7 +90,7 @@ protected boolean isNullOrBlank(Object obj) { * If a condition is met, adds the error to our notification. * * @param condition condition to check for. - * @param error error to add if condition met. + * @param error error to add if condition met. */ protected void fail(boolean condition, NotificationError error) { if (condition) { diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java b/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java index 1bf705e62c3a..c1fbadf9d107 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import java.time.LocalDate; @@ -5,8 +29,8 @@ import lombok.Setter; /** - * Data transfer object which stores information about the worker. This is carried between - * objects and layers to reduce the number of method calls made. + * Data transfer object which stores information about the worker. This is carried between objects + * and layers to reduce the number of method calls made. */ @Getter @Setter @@ -15,30 +39,20 @@ public class RegisterWorkerDto extends DataTransferObject { private String occupation; private LocalDate dateOfBirth; - /** - * Error for when name field is blank or missing. - */ - public static final NotificationError MISSING_NAME = - new NotificationError(1, "Name is missing"); + /** Error for when name field is blank or missing. */ + public static final NotificationError MISSING_NAME = new NotificationError(1, "Name is missing"); - /** - * Error for when occupation field is blank or missing. - */ + /** Error for when occupation field is blank or missing. */ public static final NotificationError MISSING_OCCUPATION = - new NotificationError(2, "Occupation is missing"); + new NotificationError(2, "Occupation is missing"); - /** - * Error for when date of birth field is blank or missing. - */ + /** Error for when date of birth field is blank or missing. */ public static final NotificationError MISSING_DOB = - new NotificationError(3, "Date of birth is missing"); + new NotificationError(3, "Date of birth is missing"); - /** - * Error for when date of birth is less than 18 years ago. - */ + /** Error for when date of birth is less than 18 years ago. */ public static final NotificationError DOB_TOO_SOON = - new NotificationError(4, "Worker registered must be over 18"); - + new NotificationError(4, "Worker registered must be over 18"); protected RegisterWorkerDto() { super(); @@ -47,8 +61,8 @@ protected RegisterWorkerDto() { /** * Simple set up function for capturing our worker information. * - * @param name Name of the worker - * @param occupation occupation of the worker + * @param name Name of the worker + * @param occupation occupation of the worker * @param dateOfBirth Date of Birth of the worker */ public void setupWorkerDto(String name, String occupation, LocalDate dateOfBirth) { diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java b/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java index 3d03c967c300..c1a5092f928b 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java @@ -1,12 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import java.time.LocalDate; import lombok.extern.slf4j.Slf4j; /** - * The form submitted by the user, part of the presentation layer, - * linked to the domain layer through a data transfer object and - * linked to the service layer directly. + * The form submitted by the user, part of the presentation layer, linked to the domain layer + * through a data transfer object and linked to the service layer directly. */ @Slf4j public class RegisterWorkerForm { @@ -17,10 +40,10 @@ public class RegisterWorkerForm { RegisterWorkerService service = new RegisterWorkerService(); /** - * Constructor. + * Constructor. * - * @param name Name of the worker - * @param occupation occupation of the worker + * @param name Name of the worker + * @param occupation occupation of the worker * @param dateOfBirth Date of Birth of the worker */ public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) { @@ -29,16 +52,14 @@ public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) this.dateOfBirth = dateOfBirth; } - /** - * Attempts to submit the form for registering a worker. - */ + /** Attempts to submit the form for registering a worker. */ public void submit() { - //Transmit information to our transfer object to communicate between layers + // Transmit information to our transfer object to communicate between layers saveToWorker(); - //call the service layer to register our worker + // call the service layer to register our worker service.registerWorker(worker); - //check for any errors + // check for any errors if (worker.getNotification().hasErrors()) { indicateErrors(); LOGGER.info("Not registered, see errors"); @@ -47,9 +68,7 @@ public void submit() { } } - /** - * Saves worker information to the data transfer object. - */ + /** Saves worker information to the data transfer object. */ private void saveToWorker() { worker = new RegisterWorkerDto(); worker.setName(name); @@ -57,9 +76,7 @@ private void saveToWorker() { worker.setDateOfBirth(dateOfBirth); } - /** - * Check for any errors with form submission and show them to the user. - */ + /** Check for any errors with form submission and show them to the user. */ public void indicateErrors() { worker.getNotification().getErrors().forEach(error -> LOGGER.error(error.toString())); } diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerService.java b/notification/src/main/java/com/iluwatar/RegisterWorkerService.java index bcdd4e08b8f8..a7742739510b 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorkerService.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerService.java @@ -1,13 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; /** - * Service used to register a worker. - * This represents the basic framework of a service layer which can be built upon. + * Service used to register a worker. This represents the basic framework of a service layer which + * can be built upon. */ public class RegisterWorkerService { /** - * Creates and runs a command object to do the work needed, - * in this case, register a worker in the system. + * Creates and runs a command object to do the work needed, in this case, register a worker in the + * system. * * @param registration worker to be registered if possible */ diff --git a/notification/src/main/java/com/iluwatar/ServerCommand.java b/notification/src/main/java/com/iluwatar/ServerCommand.java index 8c820d3de040..6dd1357cc888 100644 --- a/notification/src/main/java/com/iluwatar/ServerCommand.java +++ b/notification/src/main/java/com/iluwatar/ServerCommand.java @@ -1,10 +1,34 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import lombok.AllArgsConstructor; /** - * Stores the dto and access the notification within it. - * Acting as a layer supertype in this instance for the domain layer. + * Stores the dto and access the notification within it. Acting as a layer supertype in this + * instance for the domain layer. */ @AllArgsConstructor public class ServerCommand { diff --git a/notification/src/test/java/com/iluwatar/AppTest.java b/notification/src/test/java/com/iluwatar/AppTest.java index 8c20e97dabf5..0eaaf78632be 100644 --- a/notification/src/test/java/com/iluwatar/AppTest.java +++ b/notification/src/test/java/com/iluwatar/AppTest.java @@ -1,14 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } - diff --git a/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java b/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java index ffeccce82d3e..fdc5b77cffef 100644 --- a/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java +++ b/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java @@ -1,53 +1,73 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.read.ListAppender; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.time.LocalDate; -import java.util.logging.Logger; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; class RegisterWorkerFormTest { - private RegisterWorkerForm registerWorkerForm; - - @Test - void submitSuccessfully() { - // Ensure the worker is null initially - registerWorkerForm = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1)); + private RegisterWorkerForm registerWorkerForm; - assertNull(registerWorkerForm.worker); + @Test + void submitSuccessfully() { + // Ensure the worker is null initially + registerWorkerForm = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1)); - // Submit the form - registerWorkerForm.submit(); + assertNull(registerWorkerForm.worker); - // Verify that the worker is not null after submission - assertNotNull(registerWorkerForm.worker); + // Submit the form + registerWorkerForm.submit(); - // Verify that the worker's properties are set correctly - assertEquals("John Doe", registerWorkerForm.worker.getName()); - assertEquals("Engineer", registerWorkerForm.worker.getOccupation()); - assertEquals(LocalDate.of(1990, 1, 1), registerWorkerForm.worker.getDateOfBirth()); - } + // Verify that the worker is not null after submission + assertNotNull(registerWorkerForm.worker); - @Test - void submitWithErrors() { - // Set up the worker with a notification containing errors - registerWorkerForm = new RegisterWorkerForm(null, null, null); + // Verify that the worker's properties are set correctly + assertEquals("John Doe", registerWorkerForm.worker.getName()); + assertEquals("Engineer", registerWorkerForm.worker.getOccupation()); + assertEquals(LocalDate.of(1990, 1, 1), registerWorkerForm.worker.getDateOfBirth()); + } - // Submit the form - registerWorkerForm.submit(); + @Test + void submitWithErrors() { + // Set up the worker with a notification containing errors + registerWorkerForm = new RegisterWorkerForm(null, null, null); - // Verify that the worker's properties remain unchanged - assertNull(registerWorkerForm.worker.getName()); - assertNull(registerWorkerForm.worker.getOccupation()); - assertNull(registerWorkerForm.worker.getDateOfBirth()); + // Submit the form + registerWorkerForm.submit(); - // Verify the presence of errors - assertEquals(registerWorkerForm.worker.getNotification().getErrors().size(), 4); - } + // Verify that the worker's properties remain unchanged + assertNull(registerWorkerForm.worker.getName()); + assertNull(registerWorkerForm.worker.getOccupation()); + assertNull(registerWorkerForm.worker.getDateOfBirth()); + // Verify the presence of errors + assertEquals(registerWorkerForm.worker.getNotification().getErrors().size(), 4); + } } diff --git a/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java b/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java index 7b258afb6642..5f455b4cc6a5 100644 --- a/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java +++ b/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java @@ -1,90 +1,120 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.time.LocalDate; - -import static org.junit.jupiter.api.Assertions.*; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; @Slf4j class RegisterWorkerTest { - @Test - void runSuccessfully() { - RegisterWorkerDto validWorkerDto = createValidWorkerDto(); - validWorkerDto.setupWorkerDto("name", "occupation", LocalDate.of(2000, 12, 1)); - RegisterWorker registerWorker = new RegisterWorker(validWorkerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that there are no errors in the notification - assertFalse(registerWorker.getNotification().hasErrors()); - } - - @Test - void runWithMissingName() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setupWorkerDto(null, "occupation", LocalDate.of(2000, 12, 1)); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the missing name error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_NAME)); - assertEquals(registerWorker.getNotification().getErrors().size(), 1); - } - - @Test - void runWithMissingOccupation() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setupWorkerDto("name", null, LocalDate.of(2000, 12, 1)); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the missing occupation error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_OCCUPATION)); - assertEquals(registerWorker.getNotification().getErrors().size(), 1); - } - - @Test - void runWithMissingDOB() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setupWorkerDto("name", "occupation", null); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the missing DOB error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)); - assertEquals(registerWorker.getNotification().getErrors().size(), 2); - } - - @Test - void runWithUnderageDOB() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setDateOfBirth(LocalDate.now().minusYears(17)); // Under 18 - workerDto.setupWorkerDto("name", "occupation", LocalDate.now().minusYears(17)); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the underage DOB error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.DOB_TOO_SOON)); - assertEquals(registerWorker.getNotification().getErrors().size(), 1); - } - - private RegisterWorkerDto createValidWorkerDto() { - return new RegisterWorkerDto(); - } + @Test + void runSuccessfully() { + RegisterWorkerDto validWorkerDto = createValidWorkerDto(); + validWorkerDto.setupWorkerDto("name", "occupation", LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(validWorkerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that there are no errors in the notification + assertFalse(registerWorker.getNotification().hasErrors()); + } + + @Test + void runWithMissingName() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto(null, "occupation", LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing name error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_NAME)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + @Test + void runWithMissingOccupation() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto("name", null, LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing occupation error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker + .getNotification() + .getErrors() + .contains(RegisterWorkerDto.MISSING_OCCUPATION)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + @Test + void runWithMissingDOB() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto("name", "occupation", null); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing DOB error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)); + assertEquals(registerWorker.getNotification().getErrors().size(), 2); + } + + @Test + void runWithUnderageDOB() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setDateOfBirth(LocalDate.now().minusYears(17)); // Under 18 + workerDto.setupWorkerDto("name", "occupation", LocalDate.now().minusYears(17)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the underage DOB error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.DOB_TOO_SOON)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + private RegisterWorkerDto createValidWorkerDto() { + return new RegisterWorkerDto(); + } } diff --git a/null-object/README.md b/null-object/README.md index 7d1595b91fe9..75da7452a01a 100644 --- a/null-object/README.md +++ b/null-object/README.md @@ -1,26 +1,31 @@ --- -title: Null Object +title: "Null Object Pattern in Java: Streamlining Error Handling with Graceful Defaults" +shortTitle: Null Object +description: "Learn how the Null Object Pattern simplifies your Java code by handling null references effectively. Discover its implementation, advantages, and practical use cases." category: Behavioral language: en tag: - - Extensibility + - Code simplification + - Decoupling + - Polymorphism --- -## Intent +## Also known as -In most object-oriented languages, such as Java or C#, references may be null. These references need -to be checked to ensure they are not null before invoking any methods, because methods typically -cannot be invoked on null references. Instead of using a null reference to convey absence of an -object (for instance, a non-existent customer), one uses an object which implements the expected -interface, but whose method body is empty. The advantage of this approach over a working default -implementation is that a Null Object is very predictable and has no side effects: it does nothing. +* Active Nothing +* Stub -## Explanation +## Intent of Null Object Design Pattern -Real world example +The Null Object Pattern is an essential Java design pattern that provides a seamless way to handle absent objects without performing null checks, streamlining your Java applications. -> We are building a binary tree from nodes. There are ordinary nodes and "empty" nodes. Traversing -> the tree normally should not cause errors, so we use null object pattern where necessary. +## Detailed Explanation of Null Object Pattern with Real-World Examples + +Real-world example + +> A real-world analogy for the Null Object pattern can be found in the context of customer service. Imagine a customer service system where there are different types of support representatives: human agents and automated bots. When a customer request is received, the system can assign it to a human agent or, if no agents are available, to an automated bot. If neither human agents nor automated bots are available, the system assigns the request to a "Null Representative." +> +> The Null Representative is a placeholder that does nothing but ensures that the system doesn't crash or raise errors due to the absence of a support representative. It provides default responses like "Your request is being processed" without any actual processing, thereby maintaining system stability and avoiding the need for null checks throughout the codebase. In plain words @@ -28,11 +33,13 @@ In plain words Wikipedia says -> In object-oriented computer programming, a null object is an object with no referenced value or -> with defined neutral ("null") behavior. The null object design pattern describes the uses of such -> objects and their behavior (or lack thereof). +> In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral ("null") behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof). + +## Programmatic Example of Null Object in Java -**Programmatic Example** +By implementing the Null Object Pattern, Java developers can ensure that their applications handle 'empty' objects more gracefully, enhancing code stability and readability. + +We are building a binary tree from nodes. There are ordinary nodes and "empty" nodes. Traversing the tree normally should not cause errors, so we use null object pattern where necessary. Here's the definition of `Node` interface. @@ -51,8 +58,7 @@ public interface Node { } ``` -We have two implementations of `Node`. The normal implementation `NodeImpl` and `NullNode` for -empty nodes. +We have two implementations of `Node`. The normal implementation `NodeImpl` and `NullNode` for empty nodes. ```java @Slf4j @@ -62,9 +68,6 @@ public class NodeImpl implements Node { private final Node left; private final Node right; - /** - * Constructor. - */ public NodeImpl(String name, Node left, Node right) { this.name = name; this.left = left; @@ -144,17 +147,9 @@ public final class NullNode implements Node { Then we can construct and traverse the binary tree without errors as follows. ```java - var root = new NodeImpl("1", - new NodeImpl("11", - new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), - NullNode.getInstance() - ), - new NodeImpl("12", - NullNode.getInstance(), - new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()) - ) - ); - root.walk(); +var root = new NodeImpl("1", new NodeImpl("11", new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), NullNode.getInstance()), + new NodeImpl("12", NullNode.getInstance(), new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()))); +root.walk(); ``` Program output: @@ -167,17 +162,41 @@ Program output: 122 ``` -## Class diagram +## When to Use the Null Object Pattern in Java + +* When you need to provide a default behavior in place of a null object. +* To simplify the client code by eliminating null checks. +* When a default action is preferable to handling a null reference. + +## Real-World Applications of Null Object Pattern in Java + +* Commonly used in logging systems, the Null object helps prevent NullPointerExceptions, making it a critical pattern for reliable Java software development. +* Collections that use a NullIterator to handle empty collections gracefully. +* GUI systems where a NullComponent can be used to represent a component that does nothing. + +## Benefits and Trade-offs of Null Object Pattern + +Benefits: + +* Eliminates the need for null checks, reducing the risk of NullPointerException. +* Simplifies the client code and enhances readability. +* Promotes the use of polymorphism by handling default behavior through a common interface. -![alt text](./etc/null-object.png "Null Object") +Trade-offs: -## Applicability +* May introduce additional classes, potentially increasing the overall complexity of the system. +* The default behavior might mask potential issues that would otherwise be caught by explicit null handling. -Use the Null Object pattern when +## Related Java Design Patterns -* You want to avoid explicit null checks and keep the algorithm elegant and easy to read. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Null Object can be seen as a special case of the Strategy Pattern where the strategy is to do nothing. +* [State](https://java-design-patterns.com/patterns/state/): Similar in that both patterns can handle different states or behaviors; Null Object is like a state that does nothing. +* [Factory](https://java-design-patterns.com/patterns/factory/): Often used to provide Null Objects in place of actual objects. -## Credits +## References and Credits -* [Pattern Languages of Program Design 3](https://www.amazon.com/gp/product/0201310112/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201310112&linkCode=as2&tag=javadesignpat-20&linkId=7372ffb8a4e39a3bb10f199b89aef921) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Pattern Languages of Program Design 3](https://amzn.to/3UZkRF6) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) +* [Refactoring: Improving the Design of Existing Code](https://amzn.to/3UJ7etA) diff --git a/null-object/pom.xml b/null-object/pom.xml index 1a68c1f10e2d..9bc13a79e2cd 100644 --- a/null-object/pom.xml +++ b/null-object/pom.xml @@ -34,6 +34,14 @@ null-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/null-object/src/main/java/com/iluwatar/nullobject/App.java b/null-object/src/main/java/com/iluwatar/nullobject/App.java index efeaac8581d9..d8908ef32772 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/App.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/App.java @@ -38,16 +38,17 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var root = new NodeImpl("1", - new NodeImpl("11", - new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), - NullNode.getInstance() - ), - new NodeImpl("12", - NullNode.getInstance(), - new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()) - ) - ); + var root = + new NodeImpl( + "1", + new NodeImpl( + "11", + new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), + NullNode.getInstance()), + new NodeImpl( + "12", + NullNode.getInstance(), + new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()))); root.walk(); } diff --git a/null-object/src/main/java/com/iluwatar/nullobject/Node.java b/null-object/src/main/java/com/iluwatar/nullobject/Node.java index 26830dc0aa55..a306c91232c3 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/Node.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/Node.java @@ -1,41 +1,39 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.nullobject; - -/** - * Interface for binary tree node. - */ -public interface Node { - - String getName(); - - int getTreeSize(); - - Node getLeft(); - - Node getRight(); - - void walk(); -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.nullobject; + +/** Interface for binary tree node. */ +public interface Node { + + String getName(); + + int getTreeSize(); + + Node getLeft(); + + Node getRight(); + + void walk(); +} diff --git a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java index 9dd4ccf2c9db..55fcf0eaff87 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java @@ -1,63 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.nullobject; - -import lombok.extern.slf4j.Slf4j; - -/** - * Implementation for binary tree's normal nodes. - */ - -@Slf4j -public record NodeImpl(String name, Node left, Node right) implements Node { - @Override - public Node getLeft() { - return left; - } - - @Override - public Node getRight() { - return right; - } - - @Override - public String getName() { - return name; - } - @Override - public int getTreeSize() { - return 1 + left.getTreeSize() + right.getTreeSize(); - } - @Override - public void walk() { - LOGGER.info(name); - if (left.getTreeSize() > 0) { - left.walk(); - } - if (right.getTreeSize() > 0) { - right.walk(); - } - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.nullobject; + +import lombok.extern.slf4j.Slf4j; + +/** Implementation for binary tree's normal nodes. */ +@Slf4j +public record NodeImpl(String name, Node left, Node right) implements Node { + @Override + public Node getLeft() { + return left; + } + + @Override + public Node getRight() { + return right; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getTreeSize() { + return 1 + left.getTreeSize() + right.getTreeSize(); + } + + @Override + public void walk() { + LOGGER.info(name); + if (left.getTreeSize() > 0) { + left.walk(); + } + if (right.getTreeSize() > 0) { + right.walk(); + } + } +} diff --git a/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java b/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java index ce74ff0560ed..dfa1454fac5c 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java @@ -33,8 +33,7 @@ public final class NullNode implements Node { private static final NullNode instance = new NullNode(); - private NullNode() { - } + private NullNode() {} public static NullNode getInstance() { return instance; diff --git a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java index 01867936d019..df790d10851f 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.nullobject; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java index 0adc61c72815..1cc0a9d041bb 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java @@ -24,23 +24,17 @@ */ package com.iluwatar.nullobject; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -/** - * Date: 12/26/15 - 11:47 PM - * - * @author Jeroen Meulemeester - */ +import org.junit.jupiter.api.Test; + +/** NullNodeTest */ class NullNodeTest { - /** - * Verify if {@link NullNode#getInstance()} actually returns the same object instance - */ + /** Verify if {@link NullNode#getInstance()} actually returns the same object instance */ @Test void testGetInstance() { final var instance = NullNode.getInstance(); @@ -58,7 +52,7 @@ void testFields() { } /** - * Removed unnecessary test method for {@link NullNode#walk()} as the method doesn't have an implementation. + * Removed unnecessary test method for {@link NullNode#walk()} as the method doesn't have an + * implementation. */ - } diff --git a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java index 80ff86759db7..9c39e6f164bf 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java @@ -39,11 +39,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/26/15 - 11:44 PM - * - * @author Jeroen Meulemeester - */ +/** TreeTest */ class TreeTest { private InMemoryAppender appender; @@ -93,9 +89,7 @@ void testTreeSize() { assertEquals(7, TREE_ROOT.getTreeSize()); } - /** - * Walk through the tree and verify if every item is handled - */ + /** Walk through the tree and verify if every item is handled */ @Test void testWalk() { TREE_ROOT.walk(); @@ -161,5 +155,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/object-mother/README.md b/object-mother/README.md index 4f2a6664cd48..c80ad2eaa8fc 100644 --- a/object-mother/README.md +++ b/object-mother/README.md @@ -1,89 +1,167 @@ --- -title: Object Mother -category: Creational +title: "Object Mother Pattern in Java: Simplifying Object Creation for Testing" +shortTitle: Object Mother +description: "Explore the Object Mother pattern in Java for simplifying test object creation. Learn how to efficiently generate test data, reduce boilerplate, and enhance test maintainability with our in-depth guide and examples." +category: Testing language: en tag: - - Instantiation + - Code simplification + - Instantiation + - Isolation + - Testing --- -## Object Mother -It is used to define a factory of immutable content with separated builder and factory interfaces. +## Also known as -## Class diagram -![alt text](./etc/object-mother.png "Object Mother") +* Object Builder +* Test Data Builder -## Applicability -Use the Object Mother pattern when +## Intent of Object Mother Design Pattern -* You want consistent objects over several tests -* You want to reduce code for creation of objects in tests -* Every test should run with fresh data +The Object Mother pattern simplifies the creation of objects for testing purposes in Java, ensuring that test cases are clear and maintainable by centralizing the logic needed to instantiate objects in a consistent state. +## Detailed Explanation of Object Mother Pattern with Real-World Examples -## Understanding the Object Mother Pattern +Real-world example -### Real-World Scenario -Imagine you're developing a Java application for a travel agency. In your system, there are different types of travelers, such as tourists, business travelers, and travel agents, each with specific attributes and behaviors. To perform thorough testing, you need to create and manipulate these traveler objects in various contexts. The Object Mother Pattern can help you generate consistent and predefined traveler objects for testing purposes, ensuring that your tests are based on known, reliable data. +> Imagine you're developing a Java application for a travel agency. In your system, there are different types of travelers, such as tourists, business travelers, and travel agents, each with specific attributes and behaviors. To perform thorough testing, you need to create and manipulate these traveler objects in various contexts. The Object Mother Pattern can help you generate consistent and predefined traveler objects for testing purposes, ensuring that your tests are based on known, reliable data. -### In Plain Terms -The Object Mother Pattern is a design pattern used in Java to simplify the creation of objects with specific configurations, especially for testing. Instead of manually constructing objects with varying properties for each test case, you create a dedicated "Object Mother" class or method that produces these objects with predefined settings. This ensures that you have consistent and predictable test data, making your tests more reliable and easier to manage. +In plain words -### Overview from a Testing Perspective -The Object Mother Pattern is a testing-related design pattern that assists in maintaining a consistent and reliable testing environment. It allows you to define and create objects with specific attributes, helping you ensure that your tests produce consistent and predictable results, making it easier to spot issues and maintain your test suite. +> The Object Mother Pattern is a design pattern used in Java to simplify the creation of objects with specific configurations, especially for testing. Instead of manually constructing objects with varying properties for each test case, you create a dedicated "Object Mother" class or method that produces these objects with predefined settings. This ensures that you have consistent and predictable test data, making your tests more reliable and easier to manage. -### Practical Usage in Testing -In software testing, especially unit testing, the Object Mother Pattern is invaluable. It helps ensure that your tests are not influenced by unpredictable data, thus making your tests more robust and repeatable. By centralizing the creation of test objects in an Object Mother, you can easily adapt your test data to different scenarios. +wiki.c2.com says -### Example in Java -Here's an illustrative Java example of the Object Mother Pattern within the context of a travel agency application: +> Object Mother starts with the factory pattern, by delivering prefabricated test-ready objects via a simple method call. It moves beyond the realm of the factory by +> 1. facilitating the customization of created objects, +> 2. providing methods to update the objects during the tests, and +> 3. if necessary, deleting the object from the database at the completion of the test. -```java -class Traveler { - private String name; - private int age; - private boolean isBusinessTraveler; +## Programmatic Example of Object Mother Pattern in Java - // Constructor and methods for the traveler - // ... +The Object Mother is a design pattern that aims to provide an easy way to create objects for testing purposes. It encapsulates the logic for building instances of complex objects in one place, making it easier to maintain and reuse across multiple tests. - // Getter and setter methods - // ... -} - -class TravelerMother { - public static Traveler createTourist(String name, int age) { - Traveler traveler = new Traveler(); - traveler.setName(name); - traveler.setAge(age); - traveler.setBusinessTraveler(false); - return traveler; - } +First, we have the `King` class. This class represents a king with certain behaviors and states. The king can be drunk or sober, happy or unhappy. The king can also flirt with a queen, which may affect his happiness. - public static Traveler createBusinessTraveler(String name, int age) { - Traveler traveler = new Traveler(); - traveler.setName(name); - traveler.setAge(age); - traveler.setBusinessTraveler(true); - return traveler; +```java +public class King implements Royalty { + boolean isDrunk = false; + boolean isHappy = false; + + @Override + public void makeDrunk() { + isDrunk = true; + } + + @Override + public void makeSober() { + isDrunk = false; + } + + @Override + public void makeHappy() { + isHappy = true; + } + + @Override + public void makeUnhappy() { + isHappy = false; + } + + public boolean isHappy() { + return isHappy; + } + + public void flirt(Queen queen) { + var flirtStatus = queen.getFlirted(this); + if (!flirtStatus) { + this.makeUnhappy(); + } else { + this.makeHappy(); } + } } +``` -public class TravelAgency { - public static void main(String[] args) { - // Using the Object Mother to create traveler objects for testing - Traveler tourist = TravelerMother.createTourist("Alice", 28); - Traveler businessTraveler = TravelerMother.createBusinessTraveler("Bob", 35); +The `RoyaltyObjectMother` class is where the Object Mother pattern is implemented. This class provides static methods to create different types of `King` and `Queen` objects. These methods encapsulate the logic for creating these objects, making it easier to create them in a consistent way across multiple tests. - // Now you have consistent traveler objects for testing. - } +```java +class RoyaltyObjectMother { + + static King createDrunkKing() { + var king = new King(); + king.makeDrunk(); + return king; + } + + static King createHappyKing() { + var king = new King(); + king.makeHappy(); + return king; + } + + // Other methods to create different types of King and Queen objects... } +``` + +In the `RoyaltyObjectMotherTest` class, we can see how the Object Mother pattern is used to create objects for testing. The `RoyaltyObjectMother` class is used to create `King` and `Queen` objects in different states, which are then used in the tests. + +```java +class RoyaltyObjectMotherTest { + + @Test + void unsuccessfulKingFlirt() { + var soberUnhappyKing = RoyaltyObjectMother.createSoberUnhappyKing(); + var flirtyQueen = RoyaltyObjectMother.createFlirtyQueen(); + soberUnhappyKing.flirt(flirtyQueen); + assertFalse(soberUnhappyKing.isHappy()); + } + // Other tests... +} ``` -In this example, TravelerMother is the Object Mother class responsible for generating predefined Traveler objects with specific configurations. This approach ensures that you have consistent test data for various scenarios in a travel agency application, enhancing the reliability and effectiveness of your testing efforts. +In this way, the Object Mother pattern simplifies the creation of objects for testing, making the tests easier to read and maintain. + +## When to Use the Object Mother Pattern in Java + +Use the Object Mother pattern when + +* Creating complex objects with numerous fields for unit tests. +* You need to reuse a standard set of objects across multiple tests. +* Test setup is becoming cumbersome and repetitive. + +## Object Mother Pattern Java Tutorials + +* [What is an ObjectMother? (Stack Overflow)](http://stackoverflow.com/questions/923319/what-is-an-objectmother) +* [Object Mother (c2wiki)](http://c2.com/cgi/wiki?ObjectMother) +* [Test Data Builders: an alternative to the Object Mother pattern (Nat Pryce)](http://www.natpryce.com/articles/000714.html) + +## Real-World Applications of Object Mother Pattern in Java + +* In unit testing frameworks to create test fixtures. +* In enterprise applications to generate standard domain objects required across multiple test cases. +* In open-source projects like Apache Commons and Spring Framework for test object creation. + +## Benefits and Trade-offs of Object Mother Pattern + +Benefits: + +* Code Simplification: Reduces boilerplate code in tests, making tests more readable and easier to maintain. +* Isolation: Ensures test data setup is isolated from test logic, enhancing clarity. +* Consistency: Provides a consistent way to create objects, reducing the likelihood of errors in test setup. + +Trade-offs: + +* Maintenance: Requires maintaining the Object Mother class itself, which can grow complex over time. +* Overhead: May introduce additional layers of abstraction that could complicate understanding for new developers. + +## Related Java Design Patterns + +* [Builder](https://java-design-patterns.com/patterns/builder/): Both patterns deal with object creation. The Object Mother is often simpler and used specifically in a testing context, whereas the Builder Pattern is more general-purpose. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Similar in the sense of centralizing object creation logic. The Object Mother is specifically aimed at tests, while Factory Method is used more broadly in application code. -## Credits +## References and Credits -* [Answer by David Brown](http://stackoverflow.com/questions/923319/what-is-an-objectmother) to the stackoverflow question: [What is an ObjectMother?](http://stackoverflow.com/questions/923319/what-is-an-objectmother) -* [c2wiki - Object Mother](http://c2.com/cgi/wiki?ObjectMother) -* [Nat Pryce - Test Data Builders: an alternative to the Object Mother pattern](http://www.natpryce.com/articles/000714.html) +* [Growing Object-Oriented Software, Guided by Tests](https://amzn.to/4dGfIuk) +* [xUnit Test Patterns: Refactoring Test Code](https://amzn.to/4dHGDpm) diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/King.java b/object-mother/src/main/java/com/iluwatar/objectmother/King.java index 0cde71918a65..9aae081c5c47 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/King.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/King.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Defines all attributes and behaviour related to the King. - */ +/** Defines all attributes and behaviour related to the King. */ public class King implements Royalty { boolean isDrunk = false; boolean isHappy = false; @@ -67,6 +65,5 @@ public void flirt(Queen queen) { } else { this.makeHappy(); } - } } diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java index 22f944958492..e210161b307e 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Defines all attributes and behaviour related to the Queen. - */ +/** Defines all attributes and behaviour related to the Queen. */ public class Queen implements Royalty { private boolean isDrunk = false; private boolean isHappy = false; diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java b/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java index 795de80e65c2..f97e4d8d7f43 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Interface contracting Royalty Behaviour. - */ +/** Interface contracting Royalty Behaviour. */ public interface Royalty { void makeDrunk(); diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java b/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java index 1c30c3d944ef..509b32d70b6f 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Object Mother Pattern generating Royalty Types. - */ +/** Object Mother Pattern generating Royalty Types. */ public final class RoyaltyObjectMother { /** diff --git a/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java b/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java index 3b565571432a..04f91372fece 100644 --- a/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java +++ b/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java @@ -33,9 +33,7 @@ import com.iluwatar.objectmother.RoyaltyObjectMother; import org.junit.jupiter.api.Test; -/** - * Test Generation of Royalty Types using the object-mother - */ +/** Test Generation of Royalty Types using the object-mother */ class RoyaltyObjectMotherTest { @Test diff --git a/object-pool/README.md b/object-pool/README.md index 3e0641a98e9d..56888b017a05 100644 --- a/object-pool/README.md +++ b/object-pool/README.md @@ -1,29 +1,31 @@ --- -title: Object Pool +title: "Object Pool Pattern in Java: Enhancing Performance with Reusable Object Management" +shortTitle: Object Pool +description: "Learn how the Object Pool design pattern improves performance by reusing expensive objects efficiently. Explore examples, benefits, and best practices in Java." category: Creational language: en tag: - - Game programming - - Performance + - Game programming + - Instantiation + - Memory management + - Performance + - Resource management + - Scalability --- ## Also known as -Resource Pool +* Resource Pool -## Intent +## Intent of Object Pool Design Pattern -When objects are expensive to create and they are needed only for short periods of time it is -advantageous to utilize the Object Pool pattern. The Object Pool provides a cache for instantiated -objects tracking which ones are in use and which are available. +The Object Pool design pattern in Java manages a pool of reusable objects, optimizing memory management and application performance by recycling objects rather than creating and destroying them repeatedly. -## Explanation +## Detailed Explanation of Object Pool Pattern with Real-World Examples -Real world example +Real-world example -> In our war game we need to use oliphaunts, massive and mythic beasts, but the problem is that they -> are extremely expensive to create. The solution is to create a pool of them, track which ones are -> in-use, and instead of disposing them re-use the instances. +> Imagine a library with a limited number of study rooms that are frequently in demand. Instead of each student building their own study room whenever they need one, the library manages a pool of available study rooms. When a student needs a study room, they check one out from the pool. After they are done, they return the room back to the pool for others to use. This ensures that the study rooms are efficiently utilized without the need to build new rooms each time, thus saving time and resources, similar to how the Object Pool pattern manages the reuse of expensive objects in software. In plain words @@ -31,41 +33,39 @@ In plain words Wikipedia says -> The object pool pattern is a software creational design pattern that uses a set of initialized -> objects kept ready to use – a "pool" – rather than allocating and destroying them on demand. +> The object pool pattern is a software creational design pattern that uses a set of initialized objects kept ready to use – a "pool" – rather than allocating and destroying them on demand. -**Programmatic Example** +## Programmatic Example of Object Pool Pattern in Java + +In our war game we need to use oliphaunts, massive and mythic beasts, but the problem is that they are extremely expensive to create. The solution is to create a pool of them, track which ones are in-use, and instead of disposing them re-use the instances. Here's the basic `Oliphaunt` class. These giants are very expensive to create. ```java public class Oliphaunt { - private static final AtomicInteger counter = new AtomicInteger(0); + private static final AtomicInteger counter = new AtomicInteger(0); - private final int id; + @Getter + private final int id; - public Oliphaunt() { - id = counter.incrementAndGet(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); + public Oliphaunt() { + id = counter.incrementAndGet(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + LOGGER.error("Error occurred: ", e); + } } - } - public int getId() { - return id; - } - - @Override - public String toString() { - return String.format("Oliphaunt id=%d", id); - } + @Override + public String toString() { + return String.format("Oliphaunt id=%d", id); + } } ``` -Next we present the `ObjectPool` and more specifically `OliphauntPool`. +Next, we present the `ObjectPool` and more specifically `OliphauntPool`. ```java public abstract class ObjectPool { @@ -108,40 +108,87 @@ public class OliphauntPool extends ObjectPool { Finally, here's how we utilize the pool. ```java +public static void main(String[] args) { var pool = new OliphauntPool(); + LOGGER.info(pool.toString()); var oliphaunt1 = pool.checkOut(); + String checkedOut = "Checked out {}"; + + LOGGER.info(checkedOut, oliphaunt1); + LOGGER.info(pool.toString()); var oliphaunt2 = pool.checkOut(); + LOGGER.info(checkedOut, oliphaunt2); var oliphaunt3 = pool.checkOut(); + LOGGER.info(checkedOut, oliphaunt3); + LOGGER.info(pool.toString()); + LOGGER.info("Checking in {}", oliphaunt1); pool.checkIn(oliphaunt1); + LOGGER.info("Checking in {}", oliphaunt2); pool.checkIn(oliphaunt2); + LOGGER.info(pool.toString()); var oliphaunt4 = pool.checkOut(); + LOGGER.info(checkedOut, oliphaunt4); var oliphaunt5 = pool.checkOut(); + LOGGER.info(checkedOut, oliphaunt5); + LOGGER.info(pool.toString()); +} ``` Program output: ``` -Pool available=0 inUse=0 -Checked out Oliphaunt id=1 -Pool available=0 inUse=1 -Checked out Oliphaunt id=2 -Checked out Oliphaunt id=3 -Pool available=0 inUse=3 -Checking in Oliphaunt id=1 -Checking in Oliphaunt id=2 -Pool available=2 inUse=1 -Checked out Oliphaunt id=2 -Checked out Oliphaunt id=1 -Pool available=0 inUse=3 +21:21:55.126 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=0 +21:21:56.130 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=1 +21:21:56.132 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=1 +21:21:57.137 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=2 +21:21:58.143 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=3 +21:21:58.145 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=3 +21:21:58.145 [main] INFO com.iluwatar.object.pool.App -- Checking in Oliphaunt id=1 +21:21:58.145 [main] INFO com.iluwatar.object.pool.App -- Checking in Oliphaunt id=2 +21:21:58.146 [main] INFO com.iluwatar.object.pool.App -- Pool available=2 inUse=1 +21:21:58.146 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=2 +21:21:58.146 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=1 +21:21:58.147 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=3 ``` -## Class diagram +## When to Use the Object Pool Pattern in Java + +Use the Object Pool pattern when -![alt text](./etc/object-pool.png "Object Pool") +* You need to frequently create and destroy objects, leading to high resource allocation and deallocation costs. +* The objects are expensive to create and maintain (e.g., database connections, thread pools). +* A fixed number of objects need to be controlled, like in connection pooling. +* Object reuse can significantly improve system performance and resource management. -## Applicability +## Real-World Applications of Object Mother Pattern in Java -Use the Object Pool pattern when +* Database connection pooling in Java applications. +* Thread pooling in Java concurrent programming. +* Pooling of socket connections in network applications. +* Object pools in game development for frequently created and destroyed game objects. + +## Benefits and Trade-offs of Object Pool Pattern + +Benefits: + +* Improved Performance: Reduces the overhead of object creation and garbage collection. +* Resource Management: Controls the number of instances, reducing resource contention and limiting resource usage. +* Scalability: Allows the application to handle more requests by reusing a fixed number of objects. + +Trade-offs: + +* Complexity: Adds complexity to the codebase, requiring careful management of the pool. +* Thread Safety: Requires careful handling of concurrent access to the pool, introducing potential synchronization issues. +* Initialization Cost: Initial creation of the pool can be resource-intensive. + +## Related Java Design Patterns + +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Ensures a single instance of the pool is used, providing a global point of access. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Shares fine-grained objects to reduce memory usage, complementing object pooling by managing object state efficiently. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Often used to create objects within the pool, abstracting the instantiation process. + +## References and Credits -* The objects are expensive to create (allocation cost). -* You need a large number of short-lived objects (memory fragmentation). +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) diff --git a/object-pool/pom.xml b/object-pool/pom.xml index 52233e375512..72e46e4b5035 100644 --- a/object-pool/pom.xml +++ b/object-pool/pom.xml @@ -34,6 +34,14 @@ object-pool + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java index 5d97e4201274..dfd1b118dfb9 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java @@ -39,9 +39,7 @@ public abstract class ObjectPool { protected abstract T create(); - /** - * Checkout object from pool. - */ + /** Checkout object from pool. */ public synchronized T checkOut() { if (available.isEmpty()) { available.add(create()); diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java index e7300fc9695a..97e8fea49ce0 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java @@ -25,32 +25,27 @@ package com.iluwatar.object.pool; import java.util.concurrent.atomic.AtomicInteger; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; -/** - * Oliphaunts are expensive to create. - */ +/** Oliphaunts are expensive to create. */ +@Slf4j public class Oliphaunt { private static final AtomicInteger counter = new AtomicInteger(0); - private final int id; + @Getter private final int id; - /** - * Constructor. - */ + /** Constructor. */ public Oliphaunt() { id = counter.incrementAndGet(); try { Thread.sleep(1000); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } - public int getId() { - return id; - } - @Override public String toString() { return String.format("Oliphaunt id=%d", id); diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java b/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java index 0b1a2af18e1f..c13fde73ba74 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java @@ -24,9 +24,7 @@ */ package com.iluwatar.object.pool; -/** - * Oliphaunt object pool. - */ +/** Oliphaunt object pool. */ public class OliphauntPool extends ObjectPool { @Override diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java index 74d9b2154ea7..8318e50bf1d0 100644 --- a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java +++ b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java @@ -28,15 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * - * Application test - * - */ +/** Application test. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java index 3556abe68e9b..b929951241a0 100644 --- a/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java +++ b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java @@ -34,11 +34,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 1:05 AM - * - * @author Jeroen Meulemeester - */ +/** OliphauntPoolTest. */ class OliphauntPoolTest { /** @@ -47,27 +43,29 @@ class OliphauntPoolTest { */ @Test void testSubsequentCheckinCheckout() { - assertTimeout(ofMillis(5000), () -> { - final var pool = new OliphauntPool(); - assertEquals("Pool available=0 inUse=0", pool.toString()); - - final var expectedOliphaunt = pool.checkOut(); - assertEquals("Pool available=0 inUse=1", pool.toString()); - - pool.checkIn(expectedOliphaunt); - assertEquals("Pool available=1 inUse=0", pool.toString()); - - for (int i = 0; i < 100; i++) { - final var oliphaunt = pool.checkOut(); - assertEquals("Pool available=0 inUse=1", pool.toString()); - assertSame(expectedOliphaunt, oliphaunt); - assertEquals(expectedOliphaunt.getId(), oliphaunt.getId()); - assertEquals(expectedOliphaunt.toString(), oliphaunt.toString()); - - pool.checkIn(oliphaunt); - assertEquals("Pool available=1 inUse=0", pool.toString()); - } - }); + assertTimeout( + ofMillis(5000), + () -> { + final var pool = new OliphauntPool(); + assertEquals("Pool available=0 inUse=0", pool.toString()); + + final var expectedOliphaunt = pool.checkOut(); + assertEquals("Pool available=0 inUse=1", pool.toString()); + + pool.checkIn(expectedOliphaunt); + assertEquals("Pool available=1 inUse=0", pool.toString()); + + for (int i = 0; i < 100; i++) { + final var oliphaunt = pool.checkOut(); + assertEquals("Pool available=0 inUse=1", pool.toString()); + assertSame(expectedOliphaunt, oliphaunt); + assertEquals(expectedOliphaunt.getId(), oliphaunt.getId()); + assertEquals(expectedOliphaunt.toString(), oliphaunt.toString()); + + pool.checkIn(oliphaunt); + assertEquals("Pool available=1 inUse=0", pool.toString()); + } + }); } /** @@ -76,50 +74,51 @@ void testSubsequentCheckinCheckout() { */ @Test void testConcurrentCheckinCheckout() { - assertTimeout(ofMillis(5000), () -> { - final var pool = new OliphauntPool(); - assertEquals(pool.toString(), "Pool available=0 inUse=0"); - - final var firstOliphaunt = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=1"); - - final var secondOliphaunt = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - - assertNotSame(firstOliphaunt, secondOliphaunt); - assertEquals(firstOliphaunt.getId() + 1, secondOliphaunt.getId()); - - // After checking in the second, we should get the same when checking out a new oliphaunt ... - pool.checkIn(secondOliphaunt); - assertEquals(pool.toString(), "Pool available=1 inUse=1"); - - final var oliphaunt3 = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - assertSame(secondOliphaunt, oliphaunt3); - - // ... and the same applies for the first one - pool.checkIn(firstOliphaunt); - assertEquals(pool.toString(), "Pool available=1 inUse=1"); - - final var oliphaunt4 = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - assertSame(firstOliphaunt, oliphaunt4); - - // When both oliphaunt return to the pool, we should still get the same instances - pool.checkIn(firstOliphaunt); - assertEquals(pool.toString(), "Pool available=1 inUse=1"); - - pool.checkIn(secondOliphaunt); - assertEquals(pool.toString(), "Pool available=2 inUse=0"); - - // The order of the returned instances is not determined, so just put them in a list - // and verify if both expected instances are in there. - final var oliphaunts = List.of(pool.checkOut(), pool.checkOut()); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - assertTrue(oliphaunts.contains(firstOliphaunt)); - assertTrue(oliphaunts.contains(secondOliphaunt)); - }); + assertTimeout( + ofMillis(5000), + () -> { + final var pool = new OliphauntPool(); + assertEquals(pool.toString(), "Pool available=0 inUse=0"); + + final var firstOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + + final var secondOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + + assertNotSame(firstOliphaunt, secondOliphaunt); + assertEquals(firstOliphaunt.getId() + 1, secondOliphaunt.getId()); + + // After checking in the second, we should get the same when checking out a new oliphaunt + // ... + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final var oliphaunt3 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(secondOliphaunt, oliphaunt3); + + // ... and the same applies for the first one + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final var oliphaunt4 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(firstOliphaunt, oliphaunt4); + + // When both oliphaunt return to the pool, we should still get the same instances + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=2 inUse=0"); + + // The order of the returned instances is not determined, so just put them in a list + // and verify if both expected instances are in there. + final var oliphaunts = List.of(pool.checkOut(), pool.checkOut()); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertTrue(oliphaunts.contains(firstOliphaunt)); + assertTrue(oliphaunts.contains(secondOliphaunt)); + }); } - - -} \ No newline at end of file +} diff --git a/observer/README.md b/observer/README.md index b1c33cd0833e..1bf11fbe331b 100644 --- a/observer/README.md +++ b/observer/README.md @@ -1,40 +1,41 @@ --- -title: Observer +title: "Observer Pattern in Java: Mastering Reactive Interfaces in Java Applications" +shortTitle: Observer +description: "Learn the Observer design pattern in Java. Discover its intent, applicability, and real-world examples. Understand how it promotes loose coupling and dynamic observer management. Ideal for software developers and architects." category: Behavioral language: en tag: - - Gang Of Four - - Reactive + - Decoupling + - Event-driven + - Gang Of Four + - Publish/subscribe --- ## Also known as -Dependents, Publish-Subscribe +* Dependents -## Intent +## Intent of Observer Design Pattern -Define a one-to-many dependency between objects so that when one object changes state, all its -dependents are notified and updated automatically. +The Observer pattern in Java defines a one-to-many relationship between objects, ensuring that when one object updates its state, all dependent observers are notified and updated automatically, enhancing system responsiveness and modularity. -## Explanation +## Detailed Explanation of Observer Pattern with Real-World Examples Real-world example -> In a land far away live the races of hobbits and orcs. Both of them are mostly outdoors so they -> closely follow the weather changes. One could say that they are constantly observing the -> weather. +> In a real-world example, consider a news agency system where the agency (subject) publishes news articles, and multiple news outlets (observers) subscribe to receive updates. Whenever the news agency publishes a new article, it automatically notifies all the subscribed news outlets. These outlets can then update their platforms (like websites, TV broadcasts, or newspapers) with the latest news. This ensures that all subscribers get the latest information without the news agency needing to know the specifics of each outlet's update process. This decouples the news agency from the subscribers, promoting flexibility and modularity in how updates are handled. In plain words -> Register as an observer to receive state changes in the object. +> Implement the Observer interface to actively monitor and respond to state changes in Java applications, improving event-driven programming efficiency. Wikipedia says -> The observer pattern is a software design pattern in which an object, called the subject, -> maintains a list of its dependents, called observers, and notifies them automatically of any state -> changes, usually by calling one of their methods. +> The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. -**Programmatic Example** +## Programmatic Example of Observer Pattern in Java + +In a land far away live the races of hobbits and orcs. Both of them are mostly outdoors, so they closely follow the weather changes. One could say that they are constantly observing the weather. Let's first introduce the `WeatherObserver` interface and our races, `Orcs` and `Hobbits`. @@ -108,57 +109,99 @@ public class Weather { Here's the full example in action. ```java + public static void main(String[] args) { + var weather = new Weather(); weather.addObserver(new Orcs()); weather.addObserver(new Hobbits()); + weather.timePasses(); weather.timePasses(); weather.timePasses(); weather.timePasses(); + + // Generic observer inspired by Java Generics and Collections by Naftalin & Wadler + LOGGER.info("--Running generic version--"); + var genericWeather = new GenWeather(); + genericWeather.addObserver(new GenOrcs()); + genericWeather.addObserver(new GenHobbits()); + + genericWeather.timePasses(); + genericWeather.timePasses(); + genericWeather.timePasses(); + genericWeather.timePasses(); + } ``` Program output: ``` -The weather changed to rainy. -The orcs are facing rainy weather now -The hobbits are facing rainy weather now -The weather changed to windy. -The orcs are facing windy weather now -The hobbits are facing windy weather now -The weather changed to cold. -The orcs are facing cold weather now -The hobbits are facing cold weather now -The weather changed to sunny. -The orcs are facing sunny weather now -The hobbits are facing sunny weather now +21:28:08.310 [main] INFO com.iluwatar.observer.Weather -- The weather changed to rainy. +21:28:08.312 [main] INFO com.iluwatar.observer.Orcs -- The orcs are facing Rainy weather now +21:28:08.312 [main] INFO com.iluwatar.observer.Hobbits -- The hobbits are facing Rainy weather now +21:28:08.312 [main] INFO com.iluwatar.observer.Weather -- The weather changed to windy. +21:28:08.312 [main] INFO com.iluwatar.observer.Orcs -- The orcs are facing Windy weather now +21:28:08.312 [main] INFO com.iluwatar.observer.Hobbits -- The hobbits are facing Windy weather now +21:28:08.312 [main] INFO com.iluwatar.observer.Weather -- The weather changed to cold. +21:28:08.312 [main] INFO com.iluwatar.observer.Orcs -- The orcs are facing Cold weather now +21:28:08.312 [main] INFO com.iluwatar.observer.Hobbits -- The hobbits are facing Cold weather now +21:28:08.312 [main] INFO com.iluwatar.observer.Weather -- The weather changed to sunny. +21:28:08.312 [main] INFO com.iluwatar.observer.Orcs -- The orcs are facing Sunny weather now +21:28:08.312 [main] INFO com.iluwatar.observer.Hobbits -- The hobbits are facing Sunny weather now +21:28:08.312 [main] INFO com.iluwatar.observer.App -- --Running generic version-- +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenWeather -- The weather changed to rainy. +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenOrcs -- The orcs are facing Rainy weather now +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenHobbits -- The hobbits are facing Rainy weather now +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenWeather -- The weather changed to windy. +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenOrcs -- The orcs are facing Windy weather now +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenHobbits -- The hobbits are facing Windy weather now +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenWeather -- The weather changed to cold. +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenOrcs -- The orcs are facing Cold weather now +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenHobbits -- The hobbits are facing Cold weather now +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenWeather -- The weather changed to sunny. +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenOrcs -- The orcs are facing Sunny weather now +21:28:08.313 [main] INFO com.iluwatar.observer.generic.GenHobbits -- The hobbits are facing Sunny weather now ``` -## Class diagram - -![alt text](./etc/observer.png "Observer") - -## Applicability +## When to Use the Observer Pattern in Java Use the Observer pattern in any of the following situations: -* When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in -separate objects lets you vary and reuse them independently. -* When a change to one object requires changing others, and you don't know how many objects need to -be changed. -* When an object should be able to notify other objects without making assumptions about who these -objects are. In other words, you don't want these objects tightly coupled. +* When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently. +* When a change to one object requires changing others, and you don't know how many objects need to be changed. +* When an object should be able to notify other objects without making assumptions about who these objects are. In other words, you don't want these objects tightly coupled. -## Known uses +## Real-World Applications of Observer Pattern in Java * [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) * [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) * [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) * [RxJava](https://github.com/ReactiveX/RxJava) +* Model-View-Controller (MVC) frameworks. +* Event handling systems. + +## Benefits and Trade-offs of Observer Pattern + +Benefits: + +* This Java design pattern promotes loose coupling, allowing the subject and its observers to interact without tight dependencies, facilitating easier maintenance and scalability. +* Allows dynamic subscription and unsubscription of observers. + +Trade-offs: + +* Can lead to memory leaks if observers are not properly deregistered. +* The order of notification is not specified, leading to potential unexpected behavior. +* Potential for performance issues with a large number of observers. + +## Related Java Design Patterns + +* [Mediator](https://java-design-patterns.com/patterns/mediator/): Encapsulates how a set of objects interact, which can be used to reduce the direct dependencies among objects. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used with the Observer pattern to ensure a single instance of the subject. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Java Generics and Collections](https://www.amazon.com/gp/product/0596527756/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596527756&linkCode=as2&tag=javadesignpat-20&linkId=246e5e2c26fe1c3ada6a70b15afcb195) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Generics and Collections](https://amzn.to/3VhOBxp) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3xZ1ELU) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/observer/pom.xml b/observer/pom.xml index af7f26e7f7e1..41fe814e06fc 100644 --- a/observer/pom.xml +++ b/observer/pom.xml @@ -34,6 +34,14 @@ observer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/observer/src/main/java/com/iluwatar/observer/Hobbits.java b/observer/src/main/java/com/iluwatar/observer/Hobbits.java index 43ce675f4227..0698a8aaf7d4 100644 --- a/observer/src/main/java/com/iluwatar/observer/Hobbits.java +++ b/observer/src/main/java/com/iluwatar/observer/Hobbits.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Hobbits. - */ +/** Hobbits. */ @Slf4j public class Hobbits implements WeatherObserver { diff --git a/observer/src/main/java/com/iluwatar/observer/Orcs.java b/observer/src/main/java/com/iluwatar/observer/Orcs.java index 542101840690..ff09ea8ad955 100644 --- a/observer/src/main/java/com/iluwatar/observer/Orcs.java +++ b/observer/src/main/java/com/iluwatar/observer/Orcs.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Orcs. - */ +/** Orcs. */ @Slf4j public class Orcs implements WeatherObserver { diff --git a/observer/src/main/java/com/iluwatar/observer/Weather.java b/observer/src/main/java/com/iluwatar/observer/Weather.java index 8769292d4c02..eb19f0acc5bc 100644 --- a/observer/src/main/java/com/iluwatar/observer/Weather.java +++ b/observer/src/main/java/com/iluwatar/observer/Weather.java @@ -51,9 +51,7 @@ public void removeObserver(WeatherObserver obs) { observers.remove(obs); } - /** - * Makes time pass for weather. - */ + /** Makes time pass for weather. */ public void timePasses() { var enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java b/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java index 17cac1fff0e7..106d016ba3fa 100644 --- a/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java +++ b/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java @@ -1,34 +1,31 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.observer; - -/** - * Observer interface. - */ -public interface WeatherObserver { - - void update(WeatherType currentWeather); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.observer; + +/** Observer interface. */ +public interface WeatherObserver { + + void update(WeatherType currentWeather); +} diff --git a/observer/src/main/java/com/iluwatar/observer/WeatherType.java b/observer/src/main/java/com/iluwatar/observer/WeatherType.java index 700fb72158a9..7f52d64328a5 100644 --- a/observer/src/main/java/com/iluwatar/observer/WeatherType.java +++ b/observer/src/main/java/com/iluwatar/observer/WeatherType.java @@ -24,26 +24,21 @@ */ package com.iluwatar.observer; -/** - * WeatherType enumeration. - */ -public enum WeatherType { +import lombok.Getter; +/** WeatherType enumeration. */ +public enum WeatherType { SUNNY("Sunny"), RAINY("Rainy"), WINDY("Windy"), COLD("Cold"); - private final String description; + @Getter private final String description; WeatherType(String description) { this.description = description; } - public String getDescription() { - return this.description; - } - @Override public String toString() { return this.name().toLowerCase(); diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java b/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java index 1d4e9baa6078..22c1404c7b9e 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java @@ -27,9 +27,7 @@ import com.iluwatar.observer.WeatherType; import lombok.extern.slf4j.Slf4j; -/** - * GHobbits. - */ +/** GHobbits. */ @Slf4j public class GenHobbits implements Race { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java b/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java index bbf1c197f751..163c38582e83 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java @@ -27,9 +27,7 @@ import com.iluwatar.observer.WeatherType; import lombok.extern.slf4j.Slf4j; -/** - * GOrcs. - */ +/** GOrcs. */ @Slf4j public class GenOrcs implements Race { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java b/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java index 485f2ab36048..c05277028bb1 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java @@ -27,9 +27,7 @@ import com.iluwatar.observer.WeatherType; import lombok.extern.slf4j.Slf4j; -/** - * GWeather. - */ +/** GWeather. */ @Slf4j public class GenWeather extends Observable { @@ -39,9 +37,7 @@ public GenWeather() { currentWeather = WeatherType.SUNNY; } - /** - * Makes time pass for weather. - */ + /** Makes time pass for weather. */ public void timePasses() { var enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java index ff7e917d4099..2e10d7de266c 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java @@ -50,9 +50,7 @@ public void removeObserver(O observer) { this.observers.remove(observer); } - /** - * Notify observers. - */ + /** Notify observers. */ @SuppressWarnings("unchecked") public void notifyObservers(A argument) { for (var observer : observers) { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Race.java b/observer/src/main/java/com/iluwatar/observer/generic/Race.java index 6b602e2f761e..2f30d5881fc8 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Race.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Race.java @@ -26,8 +26,5 @@ import com.iluwatar.observer.WeatherType; -/** - * Race. - */ -public interface Race extends Observer { -} +/** Race. */ +public interface Race extends Observer {} diff --git a/observer/src/test/java/com/iluwatar/observer/AppTest.java b/observer/src/test/java/com/iluwatar/observer/AppTest.java index 21d680f03e15..2322ff31b33d 100644 --- a/observer/src/test/java/com/iluwatar/observer/AppTest.java +++ b/observer/src/test/java/com/iluwatar/observer/AppTest.java @@ -28,15 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * - * Application test - * - */ +/** Application test. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java index 13e8025f0ad2..abce5cfa42db 100644 --- a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java @@ -27,27 +27,20 @@ import java.util.Collection; import java.util.List; -/** - * Date: 12/27/15 - 12:07 PM - * - * @author Jeroen Meulemeester - */ +/** HobbitsTest */ class HobbitsTest extends WeatherObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The hobbits are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The hobbits are facing Cold weather now"}); + new Object[] {WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The hobbits are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The hobbits are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response - */ + /** Create a new test with the given weather and expected response */ public HobbitsTest() { super(Hobbits::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java index e7a0dfdd9156..05e34758c5c7 100644 --- a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java @@ -27,27 +27,20 @@ import java.util.Collection; import java.util.List; -/** - * Date: 12/27/15 - 12:07 PM - * - * @author Jeroen Meulemeester - */ +/** OrcsTest */ class OrcsTest extends WeatherObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The orcs are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The orcs are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The orcs are facing Cold weather now"}); + new Object[] {WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The orcs are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The orcs are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The orcs are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response - */ + /** Create a new test with the given weather and expected response */ public OrcsTest() { super(Orcs::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java index 491c6f611563..95958139ec86 100644 --- a/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java +++ b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.observer; -import com.iluwatar.observer.utils.InMemoryAppender; +import static org.junit.jupiter.api.Assertions.assertEquals; +import com.iluwatar.observer.utils.InMemoryAppender; import java.util.Collection; import java.util.function.Supplier; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** - * Date: 12/27/15 - 11:44 AM * Weather Observer Tests + * * @param Type of WeatherObserver - * @author Jeroen Meulemeester */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class WeatherObserverTest { @@ -58,15 +55,13 @@ void tearDown() { appender.stop(); } - /** - * The observer instance factory - */ + /** The observer instance factory */ private final Supplier factory; /** * Create a new test instance using the given parameters * - * @param factory The factory, used to create an instance of the tested observer + * @param factory The factory, used to create an instance of the tested observer */ WeatherObserverTest(final Supplier factory) { this.factory = factory; @@ -74,9 +69,7 @@ void tearDown() { public abstract Collection dataProvider(); - /** - * Verify if the weather has the expected influence on the observer - */ + /** Verify if the weather has the expected influence on the observer */ @ParameterizedTest @MethodSource("dataProvider") void testObserver(WeatherType weather, String response) { @@ -87,5 +80,4 @@ void testObserver(WeatherType weather, String response) { assertEquals(response, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java index bdb055f42e65..9d33eaaa0a30 100644 --- a/observer/src/test/java/com/iluwatar/observer/WeatherTest.java +++ b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java @@ -35,11 +35,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 11:08 AM - * - * @author Jeroen Meulemeester - */ +/** WeatherTest */ class WeatherTest { private InMemoryAppender appender; @@ -78,9 +74,7 @@ void testAddRemoveObserver() { assertEquals(2, appender.getLogSize()); } - /** - * Verify if the weather passes in the order of the {@link WeatherType}s - */ + /** Verify if the weather passes in the order of the {@link WeatherType}s */ @Test void testTimePasses() { final var observer = mock(WeatherObserver.class); @@ -96,5 +90,4 @@ void testTimePasses() { verifyNoMoreInteractions(observer); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java index 2c260bebb4c9..d1c9af42cf3a 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java @@ -28,28 +28,20 @@ import java.util.Collection; import java.util.List; -/** - * Date: 12/27/15 - 12:07 PM - * - * @author Jeroen Meulemeester - */ +/** GHobbitsTest. */ class GHobbitsTest extends ObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The hobbits are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The hobbits are facing Cold weather now"} - ); + new Object[] {WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The hobbits are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The hobbits are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response - */ + /** Create a new test with the given weather and expected response. */ public GHobbitsTest() { super(GenHobbits::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java index 4b8be12e250f..a052cf2c16ab 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java @@ -37,11 +37,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 11:08 AM - * - * @author Jeroen Meulemeester - */ +/** GWeatherTest */ class GWeatherTest { private InMemoryAppender appender; @@ -80,9 +76,7 @@ void testAddRemoveObserver() { assertEquals(2, appender.getLogSize()); } - /** - * Verify if the weather passes in the order of the {@link WeatherType}s - */ + /** Verify if the weather passes in the order of the {@link WeatherType}s */ @Test void testTimePasses() { final var observer = mock(Race.class); @@ -98,5 +92,4 @@ void testTimePasses() { verifyNoMoreInteractions(observer); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java index f7af260f14c0..2a06651b2f6a 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java @@ -24,24 +24,22 @@ */ package com.iluwatar.observer.generic; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.observer.WeatherType; import com.iluwatar.observer.utils.InMemoryAppender; +import java.util.Collection; +import java.util.function.Supplier; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.Collection; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.assertEquals; - /** - * Date: 12/27/15 - 11:44 AM * Test for Observers + * * @param Type of Observer - * @author Jeroen Meulemeester */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class ObserverTest> { @@ -58,15 +56,13 @@ void tearDown() { appender.stop(); } - /** - * The observer instance factory - */ + /** The observer instance factory */ private final Supplier factory; /** * Create a new test instance using the given parameters * - * @param factory The factory, used to create an instance of the tested observer + * @param factory The factory, used to create an instance of the tested observer */ ObserverTest(final Supplier factory) { this.factory = factory; @@ -74,9 +70,7 @@ void tearDown() { public abstract Collection dataProvider(); - /** - * Verify if the weather has the expected influence on the observer - */ + /** Verify if the weather has the expected influence on the observer */ @ParameterizedTest @MethodSource("dataProvider") void testObserver(WeatherType weather, String response) { @@ -87,5 +81,4 @@ void testObserver(WeatherType weather, String response) { assertEquals(response, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java index 2a558447e66e..47d604fd7ec3 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java @@ -28,28 +28,20 @@ import java.util.Collection; import java.util.List; -/** - * Date: 12/27/15 - 12:07 PM - * - * @author Jeroen Meulemeester - */ +/** OrcsTest */ class OrcsTest extends ObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The orcs are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The orcs are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The orcs are facing Cold weather now"} - ); + new Object[] {WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The orcs are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The orcs are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The orcs are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response - */ + /** Create a new test with the given weather and expected response */ public OrcsTest() { super(GenOrcs::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java b/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java index 59d699c18ae6..36e92d66ef4c 100644 --- a/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java +++ b/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java @@ -27,13 +27,11 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; -import org.slf4j.LoggerFactory; import java.util.LinkedList; import java.util.List; +import org.slf4j.LoggerFactory; -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/optimistic-offline-lock/README.md b/optimistic-offline-lock/README.md index 7447a30503c7..98bf4140b6e7 100644 --- a/optimistic-offline-lock/README.md +++ b/optimistic-offline-lock/README.md @@ -1,147 +1,137 @@ --- -title: Optimistic Offline Lock -category: Concurrency +title: "Optimistic Offline Lock Pattern in Java: Mastering Conflict Resolution in Database Transactions" +shortTitle: Optimistic Offline Lock +description: "Explore the Optimistic Offline Lock design pattern in Java with detailed implementation guidelines and practical examples. Learn how to manage data concurrency effectively in your Java applications." +category: Data access language: en tag: -- Data access + - Concurrency + - Data access + - Fault tolerance + - Isolation + - Persistence + - Transactions --- -## Intent +## Also known as -Provide an ability to avoid concurrent changes of one record in relational databases. +* Optimistic Concurrency Control -## Explanation +## Intent of Optimistic Offline Lock Design Pattern -Each transaction during object modifying checks equation of object's version before start of transaction -and before commit itself. +The Optimistic Offline Lock pattern in Java is specifically designed to manage concurrent data modifications without the need for long-duration database locks, thus enhancing system performance and scalability. -**Real world example** -> Since people love money, the best (and most common) example is banking system: -> imagine you have 100$ on your e-wallet and two people are trying to send you 50$ both at a time. -> Without locking, your system will start **two different thread**, each of whose will read your current balance -> and just add 50$. The last thread won't re-read balance and will just rewrite it. -> So, instead 200$ you will have only 150$. +## Detailed Explanation of Optimistic Offline Lock Pattern with Real-World Examples -**In plain words** -> Each transaction during object modifying will save object's last version and check it before saving. -> If it differs, the transaction will be rolled back. +Real-world example -**Wikipedia says** -> Optimistic concurrency control (OCC), also known as optimistic locking, -> is a concurrency control method applied to transactional systems such as -> relational database management systems and software transactional memory. +> Imagine a library with multiple users checking out and returning books. Instead of locking each book while a user is browsing or deciding whether to borrow it, the library uses an optimistic approach. Each book has a timestamp or version number. When a user decides to borrow a book, they check the book's version number. If it matches the current version, the transaction proceeds. If another user has borrowed the book in the meantime, causing a version mismatch, the first user is informed to retry. This approach allows multiple users to browse and attempt to borrow books concurrently, improving the library's efficiency and user satisfaction without locking the entire catalog. -**Programmatic Example** -Let's simulate the case from *real world example*. Imagine we have next entity: +In plain words + +> The Optimistic Offline Lock pattern manages concurrent data modifications by allowing transactions to proceed without locks, resolving conflicts only when they occur to enhance performance and scalability. + +Wikipedia says + +> Optimistic concurrency control (OCC), also known as optimistic locking, is a concurrency control method applied to transactional systems such as relational database management systems and software transactional memory. + +## Programmatic Example of Optimistic Offline Lock Pattern in Java + +In this section, we delve into the practical implementation of the Optimistic Offline Lock in Java. By following these steps, you can ensure that your application handles data conflicts and concurrency with minimal overhead. + +The Optimistic Offline Lock pattern is a concurrency control method that allows multiple transactions to proceed without locks, resolving conflicts only when they occur. This pattern is useful in scenarios where the likelihood of conflicting transactions is low and long-duration locks could hamper performance and scalability. + +First, we have a `Card` entity that represents a bank card with a sum of money and a version number. ```java -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -/** - * Bank card entity. - */ @Data @NoArgsConstructor @AllArgsConstructor public class Card { - /** - * Primary key. - */ - private long id; - - /** - * Foreign key points to card's owner. - */ - private long personId; - - /** - * Sum of money. - */ - private float sum; - - /** - * Current version of object; - */ - private int version; + private long id; + + private long personId; + + private float sum; + + private int version; } ``` -Then the correct modifying will be like this: +The `CardUpdateService` class implements the `UpdateService` interface and provides a method `doUpdate` to update the Card entity. The `doUpdate` method first retrieves the current version of the `Card` entity. It then performs some business logic to update the sum of money in the `Card`. Before updating the `Card` in the database, it checks if the version of the `Card` in the database is the same as the initial version it retrieved. If the versions match, it proceeds with the update. If the versions do not match, it means that another transaction has updated the `Card` in the meantime, and it throws an `ApplicationException`. ```java -import lombok.RequiredArgsConstructor; - -/** - * Service to update {@link Card} entity. - */ @RequiredArgsConstructor public class CardUpdateService implements UpdateService { - private final JpaRepository cardJpaRepository; - - @Override - @Transactional(rollbackFor = ApplicationException.class) //will roll back transaction in case ApplicationException - public Card doUpdate(Card card, long cardId) { - float additionalSum = card.getSum(); - Card cardToUpdate = cardJpaRepository.findById(cardId); - int initialVersion = cardToUpdate.getVersion(); - float resultSum = cardToUpdate.getSum() + additionalSum; - cardToUpdate.setSum(resultSum); - //Maybe more complex business-logic e.g. HTTP-requests and so on - - if (initialVersion != cardJpaRepository.getEntityVersionById(cardId)) { - String exMessage = String.format("Entity with id %s were updated in another transaction", cardId); - throw new ApplicationException(exMessage); + private final JpaRepository cardJpaRepository; + + @Override + @Transactional(rollbackFor = ApplicationException.class) //will roll back transaction in case ApplicationException + public Card doUpdate(Card card, long cardId) { + float additionalSum = card.getSum(); + Card cardToUpdate = cardJpaRepository.findById(cardId); + int initialVersion = cardToUpdate.getVersion(); + float resultSum = cardToUpdate.getSum() + additionalSum; + cardToUpdate.setSum(resultSum); + //Maybe more complex business-logic e.g. HTTP-requests and so on + + if (initialVersion != cardJpaRepository.getEntityVersionById(cardId)) { + String exMessage = String.format("Entity with id %s were updated in another transaction", cardId); + throw new ApplicationException(exMessage); + } + + cardJpaRepository.update(cardToUpdate); + return cardToUpdate; } - - cardJpaRepository.update(cardToUpdate); - return cardToUpdate; - } } ``` -## Applicability +In this code snippet, the doUpdate method in the CardUpdateService class is a programmatic example of the Optimistic Offline Lock pattern. It allows the Card entity to be updated without locks and resolves conflicts by checking the version of the Card before the update. -Since optimistic locking can cause degradation of system's efficiency and reliability due to -many retries/rollbacks, it's important to use it safely. They are useful in case when transactions are not so long -and does not distributed among many microservices, when you need to reduce network/database overhead. +## When to Use the Optimistic Offline Lock Pattern in Java -Important to note that you should not choose this approach in case when modifying one object -in different threads is common situation. +* When multiple transactions need to access and modify the same data simultaneously without causing data inconsistencies. +* In systems where the likelihood of conflicting transactions is low. +* When you want to avoid long-duration locks that could hamper performance and scalability. -## Tutorials +## Optimistic Offline Lock Pattern Java Tutorials -- [Offline Concurrency Control](https://www.baeldung.com/cs/offline-concurrency-control) -- [Optimistic lock in JPA](https://www.baeldung.com/jpa-optimistic-locking) +* [Offline Concurrency Control (Baeldung)](https://www.baeldung.com/cs/offline-concurrency-control) +* [Optimistic Locking in JPA (Baeldung)](https://www.baeldung.com/jpa-optimistic-locking) -## Known uses +## Real-World Applications of Optimistic Offline Lock Pattern in Java -- [Hibernate ORM](https://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch05.html) +* Web-based applications with high-read, low-write access patterns. +* Distributed systems where locking resources for long durations is not feasible. +* Java enterprise applications using JPA or Hibernate for data persistence. -## Consequences +## Benefits and Trade-offs of Optimistic Offline Lock Pattern -**Advantages**: +Benefits: -- Reduces network/database overhead -- Let to avoid database deadlock -- Improve the performance and scalability of the application +* Reduces the need for locking resources, which improves performance. +* Increases system scalability by allowing more transactions to proceed concurrently. +* Simplifies transaction management by handling conflicts only when they occur. -**Disadvantages**: +Trade-offs: -- Increases complexity of the application -- Requires mechanism of versioning -- Requires rollback/retry mechanisms +* Requires additional logic (versioning, rollback/retry) to handle conflicts, which can complicate the application code. +* Can lead to more frequent retries of transactions if conflicts are common. +* Not suitable for high-conflict scenarios where frequent data modification collisions occur. -## Related patterns +## Related Java Design Patterns -- [Pessimistic Offline Lock](https://martinfowler.com/eaaCatalog/pessimisticOfflineLock.html) +* Pessimistic Offline Lock: Unlike the Optimistic Offline Lock, this pattern uses locks to prevent conflicts by locking the data during the entire transaction. It is useful in high-conflict scenarios. +* [Unit of Work](https://java-design-patterns.com/patterns/unit-of-work/): Helps in managing a set of changes as a single transaction, ensuring data integrity. It can be used in conjunction with Optimistic Offline Lock to handle complex transactions. +* [Version Number](https://java-design-patterns.com/patterns/version-number/): A common technique used in Optimistic Offline Lock to detect conflicts by maintaining a version number for each data entity. -## Credits +## References and Credits -- [Source (Martin Fowler)](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) -- [Advantages and disadvantages](https://www.linkedin.com/advice/0/what-benefits-drawbacks-using-optimistic) -- [Comparison of optimistic and pessimistic locks](https://www.linkedin.com/advice/0/what-advantages-disadvantages-using-optimistic) \ No newline at end of file +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Optimistic Offline Lock (Martin Fowler)](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) diff --git a/optimistic-offline-lock/etc/optimistic-offline-lock.urm.puml b/optimistic-offline-lock/etc/optimistic-offline-lock.urm.puml new file mode 100644 index 000000000000..d2e861ad8f22 --- /dev/null +++ b/optimistic-offline-lock/etc/optimistic-offline-lock.urm.puml @@ -0,0 +1,59 @@ +@startuml +package com.iluwatar.repository { + interface JpaRepository { + + findById(long) : T {abstract} + + getEntityVersionById(long) : int {abstract} + + update(T) : int {abstract} + } +} +package com.iluwatar.api { + interface UpdateService { + + doUpdate(T, long) : T {abstract} + } +} +package com.iluwatar.service { + class CardUpdateService { + - cardJpaRepository : JpaRepository + + CardUpdateService(cardJpaRepository : JpaRepository) + + doUpdate(obj : Card, id : long) : Card + } +} +package com.iluwatar.model { + class Card { + - id : long + - personId : long + - sum : float + - version : int + + Card() + + Card(id : long, personId : long, sum : float, version : int) + + builder() : CardBuilder {static} + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : long + + getPersonId() : long + + getSum() : float + + getVersion() : int + + hashCode() : int + + setId(id : long) + + setPersonId(personId : long) + + setSum(sum : float) + + setVersion(version : int) + + toString() : String + } + class CardBuilder { + - id : long + - personId : long + - sum : float + - version : int + ~ CardBuilder() + + build() : Card + + id(id : long) : CardBuilder + + personId(personId : long) : CardBuilder + + sum(sum : float) : CardBuilder + + toString() : String + + version(version : int) : CardBuilder + } +} +CardUpdateService --> "-cardJpaRepository" JpaRepository +CardUpdateService ..|> UpdateService +@enduml \ No newline at end of file diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java b/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java index b3c29d22f0b9..5b7489c4a9a2 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.api; /** @@ -11,7 +35,7 @@ public interface UpdateService { * Update entity. * * @param obj entity to update - * @param id primary key + * @param id primary key * @return modified entity */ T doUpdate(T obj, long id); diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java b/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java index 7d12c3350e8b..ed065c597c1e 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java @@ -1,8 +1,30 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.exception; -/** - * Exception happens in application during business-logic execution. - */ +/** Exception happens in application during business-logic execution. */ public class ApplicationException extends RuntimeException { /** diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java b/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java index b36c779d6ba4..9728d7c7f3e2 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java @@ -1,37 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.model; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -/** - * Bank card entity. - */ +/** Bank card entity. */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Card { - /** - * Primary key. - */ + /** Primary key. */ private long id; - /** - * Foreign key points to card's owner. - */ + /** Foreign key points to card's owner. */ private long personId; - /** - * Sum of money. - */ + /** Sum of money. */ private float sum; - /** - * Current version of object. - */ + /** Current version of object. */ private int version; } diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/repository/JpaRepository.java b/optimistic-offline-lock/src/main/java/com/iluwatar/repository/JpaRepository.java index b1d3c240a0ce..cab713db4d3b 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/repository/JpaRepository.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/repository/JpaRepository.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.repository; /** @@ -8,7 +32,7 @@ public interface JpaRepository { /** - * Get object by it's PK. + * Get object by its PK. * * @param id primary key * @return {@link T} diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java b/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java index 0f67c1e5e85f..9e70ee24ed5f 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.service; import com.iluwatar.api.UpdateService; @@ -6,9 +30,7 @@ import com.iluwatar.repository.JpaRepository; import lombok.RequiredArgsConstructor; -/** - * Service to update {@link Card} entity. - */ +/** Service to update {@link Card} entity. */ @RequiredArgsConstructor public class CardUpdateService implements UpdateService { @@ -21,11 +43,10 @@ public Card doUpdate(Card obj, long id) { int initialVersion = cardToUpdate.getVersion(); float resultSum = cardToUpdate.getSum() + additionalSum; cardToUpdate.setSum(resultSum); - //Maybe more complex business-logic e.g. HTTP-requests and so on + // Maybe more complex business-logic e.g. HTTP-requests and so on if (initialVersion != cardJpaRepository.getEntityVersionById(id)) { - String exMessage = - String.format("Entity with id %s were updated in another transaction", id); + String exMessage = String.format("Entity with id %s were updated in another transaction", id); throw new ApplicationException(exMessage); } diff --git a/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java b/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java index 479b5807838e..7731d12d7e6d 100644 --- a/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java +++ b/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java @@ -1,5 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; + import com.iluwatar.exception.ApplicationException; import com.iluwatar.model.Card; import com.iluwatar.repository.JpaRepository; @@ -9,9 +36,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.eq; - @SuppressWarnings({"rawtypes", "unchecked"}) public class OptimisticLockTest { @@ -29,27 +53,19 @@ public void setUp() { public void shouldNotUpdateEntityOnDifferentVersion() { int initialVersion = 1; long cardId = 123L; - Card card = Card.builder() - .id(cardId) - .version(initialVersion) - .sum(123f) - .build(); + Card card = Card.builder().id(cardId).version(initialVersion).sum(123f).build(); when(cardRepository.findById(eq(cardId))).thenReturn(card); when(cardRepository.getEntityVersionById(Mockito.eq(cardId))).thenReturn(initialVersion + 1); - Assertions.assertThrows(ApplicationException.class, - () -> cardUpdateService.doUpdate(card, cardId)); + Assertions.assertThrows( + ApplicationException.class, () -> cardUpdateService.doUpdate(card, cardId)); } @Test public void shouldUpdateOnSameVersion() { int initialVersion = 1; long cardId = 123L; - Card card = Card.builder() - .id(cardId) - .version(initialVersion) - .sum(123f) - .build(); + Card card = Card.builder().id(cardId).version(initialVersion).sum(123f).build(); when(cardRepository.findById(eq(cardId))).thenReturn(card); when(cardRepository.getEntityVersionById(Mockito.eq(cardId))).thenReturn(initialVersion); diff --git a/page-controller/README.md b/page-controller/README.md index 6a0e35d1e207..b91215d9b443 100644 --- a/page-controller/README.md +++ b/page-controller/README.md @@ -1,62 +1,61 @@ --- -title: Page Controller -categories: Structural +title: "Page Controller Pattern in Java: Centralizing Web Page Logic for Cleaner Design" +shortTitle: Page Controller +description: "Explore the Page Controller design pattern in Java with detailed examples. Learn how it handles web application requests and improves architectural organization." +category: Architectural language: en tags: -- Decoupling + - API design + - Business + - Client-server + - Decoupling + - Enterprise patterns + - Layered architecture + - Presentation + - Web development --- -## Name / classification +## Intent of Page Controller Design Pattern -Page Controller +The Page Controller pattern is intended to handle requests for a specific page or action within a web application, processing input, and determining the appropriate view for rendering the response. -## Intent - -It is an approach of one page leading to one logical file that handles actions or requests on a website. - -## Explanation +## Detailed Explanation of Page Controller Pattern with Real-World Examples Real-world example -> In a shopping website, there is a signup page to register a user profile. -> After finishing to signup, the signup page will be redirected to a user page to show the user's registered information. +> Imagine a large department store with multiple specialized counters: Customer Service, Returns, Electronics, and Clothing. Each counter has a dedicated staff member who handles specific tasks for that department. +> +> In this analogy, the department store is the web application, and each specialized counter represents a Page Controller. The Customer Service counter (Page Controller) handles customer inquiries, the Returns counter processes returns and exchanges, the Electronics counter assists with electronic goods, and the Clothing counter manages clothing-related requests. Each counter operates independently, addressing the specific needs of their respective department, just as each Page Controller handles requests for a specific page or action within the web application. In plain words -> Page controller manages HTTP requests and data in a specific page using MVC idea. -> The idea is that one page contains one Controller that handles Model and View. +> The Page Controller pattern handles requests for specific pages or actions within a Java web application, processing input, executing business logic, and determining the appropriate view for rendering the response, enhancing response handling and system architecture. + +Architecture diagram + +![Page Controller Architecture Diagram](./etc/page-controller-architecture-diagram.png) + +## Programmatic Example of Page Controller Pattern in Java -**Programmatic Example** +The Page Controller design pattern is a pattern used in web development where each page of a website is associated with a class or function known as a controller. The controller handles the HTTP requests for that page and determines which model and view to use. Predominantly utilized in MVC (Model-View-No-Controller) architectures, the Java Page Controller pattern integrates seamlessly with existing enterprise frameworks. -Here's Signup controller when a user signup their information for a website. +In the provided code, we have an example of the Page Controller pattern implemented using Spring Boot in Java. Let's break it down: + +1. **SignupController**: This is a Page Controller for the signup page. It handles HTTP GET and POST requests at the "/signup" path. The GET request returns the signup page, and the POST request processes the signup form and redirects to the user page. ```java -@Slf4j @Controller @Component public class SignupController { SignupView view = new SignupView(); - /** - * Signup Controller can handle http request and decide which model and view use. - */ - SignupController() { - } - /** - * Handle http GET request. - */ @GetMapping("/signup") public String getSignup() { return view.display(); } - /** - * Handle http POST request and access model and view. - */ @PostMapping("/signup") public String create(SignupModel form, RedirectAttributes redirectAttributes) { - LOGGER.info(form.getName()); - LOGGER.info(form.getEmail()); redirectAttributes.addAttribute("name", form.getName()); redirectAttributes.addAttribute("email", form.getEmail()); redirectAttributes.addFlashAttribute("userInfo", form); @@ -64,44 +63,8 @@ public class SignupController { } } ``` -Here's Signup model and view that are handled by Signup controller. - -```java -@Component -@Getter -@Setter -public class SignupModel { - private String name; - private String email; - private String password; - - public SignupModel() { - } -} -``` - -```java -@Slf4j -public class SignupView { - public SignupView() { - } - - public String display() { - LOGGER.info("display signup front page"); - return "/signup"; - } - - /** - * redirect to user page. - */ - public String redirect(SignupModel form) { - LOGGER.info("Redirect to user page with " + "name " + form.getName() + " email " + form.getEmail()); - return "redirect:/user"; - } -} -``` -Here's User Controller to handle Get request in a user page. +2. **UserController**: This is another Page Controller, this time for the user page. It handles HTTP GET requests at the "/user" path, returning the user page. ```java @Slf4j @@ -109,11 +72,6 @@ Here's User Controller to handle Get request in a user page. public class UserController { UserView view = new UserView(); - public UserController() {} - - /** - * Handle http GET request and access view and model. - */ @GetMapping("/user") public String getUserPath(SignupModel form, Model model) { model.addAttribute("name", form.getName()); @@ -123,40 +81,84 @@ public class UserController { } ``` -Here's User Model and View that are handled by User controller. +3. **SignupModel and UserModel**: These are the data models used by the controllers. They hold the data to be displayed on the page. + ```java +@Component @Getter @Setter -public class UserModel { +public class SignupModel { private String name; private String email; + private String password; +} - public UserModel() {} +@Getter +@Setter +public class UserModel { + private String name; + private String email; } ``` +4. **SignupView and UserView**: These are the views used by the controllers. They determine how the data is presented to the user. + ```java +@Slf4j +public class SignupView { + public String display() { + return "/signup"; + } + + public String redirect(SignupModel form) { + return "redirect:/user"; + } +} + @Slf4j public class UserView { - /** - * displaying command to generate html. - * @param user model content. - */ public String display(SignupModel user) { - LOGGER.info("display user html" + " name " + user.getName() + " email " + user.getEmail()); return "/user"; } } ``` -## Class diagram -![alt text](./etc/page-controller.urm.png) +In this example, the controllers (`SignupController` and `UserController`) are the Page Controllers. They handle the HTTP requests for their respective pages and determine which model and view to use. The models (`SignupModel` and `UserModel`) hold the data for the page, and the views (`SignupView` and `UserView`) determine how that data is presented. This separation of concerns makes the code easier to manage and maintain. + +## When to Use the Page Controller Pattern in Java + +* When developing a web application where each page or action needs specific processing. +* When aiming to separate the request handling logic from the view rendering logic. +* In scenarios where a clear separation of concerns between different layers (controller, view) is required. + +## Real-World Applications of Page Controller Pattern in Java + +* Spring MVC (Java) +* Apache Struts +* JSF (JavaServer Faces) + +## Benefits and Trade-offs of Page Controller Pattern + +Benefits: + +* [Separation of Concerns](https://java-design-patterns.com/principles/#separation-of-concerns): Clearly separates the controller logic from the view, making the application easier to manage and maintain. +* Reusability: Common logic can be reused across multiple controllers, reducing code duplication. +* Testability: Controllers can be tested independently of the view, improving unit test coverage. + +Trade-offs: + +* Complexity: Can add complexity to the application structure, requiring careful organization and documentation. +* Overhead: May introduce performance overhead due to additional layers of abstraction and processing. + +## Related Java Design Patterns + +* [Front Controller](https://java-design-patterns.com/patterns/front-controller/): Often used in conjunction with Page Controller to handle common pre-processing logic such as authentication and logging. +* View Helper: Works alongside Page Controller to assist in preparing the view, often handling formatting and other presentation logic. +* [Model-View-Controller (MVC)](https://java-design-patterns.com/patterns/model-view-controller/): Page Controller is a fundamental part of the MVC architecture, acting as the Controller. -## Applicability -Use the Page Controller pattern when -- you implement a site where most controller logic is simple -- you implement a site where particular actions are handled with a particular server page +## References and Credits -## Credits -- [Page Controller](https://www.martinfowler.com/eaaCatalog/pageController.html) -- [Pattern of Enterprise Application Architecture](https://www.martinfowler.com/books/eaa.html) \ No newline at end of file +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Page Controller (Martin Fowler)](https://www.martinfowler.com/eaaCatalog/pageController.html) diff --git a/page-controller/etc/page-controller-architecture-diagram.png b/page-controller/etc/page-controller-architecture-diagram.png new file mode 100644 index 000000000000..e6ef45cca6d3 Binary files /dev/null and b/page-controller/etc/page-controller-architecture-diagram.png differ diff --git a/page-controller/pom.xml b/page-controller/pom.xml index 1e75f7f86221..c314459e9d14 100644 --- a/page-controller/pom.xml +++ b/page-controller/pom.xml @@ -29,16 +29,20 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - page-controller com.iluwatar java-design-patterns 1.26.0-SNAPSHOT + page-controller + page-controller + page-controller + org.springframework spring-webmvc + 6.2.5 org.springframework.boot @@ -47,10 +51,12 @@ org.springframework spring-context + 6.2.5 org.springframework.boot spring-boot-starter-thymeleaf + 3.4.4 org.junit.jupiter @@ -63,24 +69,13 @@ test - org.springframework - spring-test - test + org.springframework.boot + spring-boot-starter-test + test - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - org.apache.maven.plugins maven-assembly-plugin diff --git a/page-controller/src/main/java/com.iluwatar.page.controller/App.java b/page-controller/src/main/java/com/iluwatar/page/controller/App.java similarity index 86% rename from page-controller/src/main/java/com.iluwatar.page.controller/App.java rename to page-controller/src/main/java/com/iluwatar/page/controller/App.java index 278c01396221..076ebf081c24 100644 --- a/page-controller/src/main/java/com.iluwatar.page.controller/App.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/App.java @@ -30,10 +30,11 @@ /** * Page Controller pattern is utilized when we want to simplify relationship in a dynamic website. - * It is an approach of one front page leading to one logical file that handles HTTP requests and actions. - * In this example, we build a website with signup page handling an input form with Signup Controller, Signup View, and Signup Model - * and after signup, it is redirected to a user page handling with User Controller, User View, and User Model. -*/ + * It is an approach of one front page leading to one logical file that handles HTTP requests and + * actions. In this example, we build a website with signup page handling an input form with Signup + * Controller, Signup View, and Signup Model and after signup, it is redirected to a user page + * handling with User Controller, User View, and User Model. + */ @Slf4j @SpringBootApplication public class App { diff --git a/page-controller/src/main/java/com.iluwatar.page.controller/SignupController.java b/page-controller/src/main/java/com/iluwatar/page/controller/SignupController.java similarity index 89% rename from page-controller/src/main/java/com.iluwatar.page.controller/SignupController.java rename to page-controller/src/main/java/com/iluwatar/page/controller/SignupController.java index 2efdc9ef19a5..08d64cebf4a7 100644 --- a/page-controller/src/main/java/com.iluwatar.page.controller/SignupController.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/SignupController.java @@ -31,31 +31,23 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -/** - * Signup Controller. - */ +/** Signup Controller. */ @Slf4j @Controller @Component public class SignupController { SignupView view = new SignupView(); - /** - * Signup Controller can handle http request and decide which model and view use. - */ - SignupController() { - } - /** - * Handle http GET request. - */ + /** Signup Controller can handle http request and decide which model and view use. */ + SignupController() {} + + /** Handle http GET request. */ @GetMapping("/signup") public String getSignup() { return view.display(); } - /** - * Handle http POST request and access model and view. - */ + /** Handle http POST request and access model and view. */ @PostMapping("/signup") public String create(SignupModel form, RedirectAttributes redirectAttributes) { LOGGER.info(form.getName()); diff --git a/page-controller/src/main/java/com.iluwatar.page.controller/SignupModel.java b/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java similarity index 92% rename from page-controller/src/main/java/com.iluwatar.page.controller/SignupModel.java rename to page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java index 97229c40236f..88134b923fab 100644 --- a/page-controller/src/main/java/com.iluwatar.page.controller/SignupModel.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java @@ -24,21 +24,16 @@ */ package com.iluwatar.page.controller; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; -/** - * ignup model. - */ +/** ignup model. */ @Component -@Getter -@Setter +@Data +@NoArgsConstructor public class SignupModel { private String name; private String email; private String password; - - public SignupModel() { - } } diff --git a/page-controller/src/main/java/com.iluwatar.page.controller/SignupView.java b/page-controller/src/main/java/com/iluwatar/page/controller/SignupView.java similarity index 87% rename from page-controller/src/main/java/com.iluwatar.page.controller/SignupView.java rename to page-controller/src/main/java/com/iluwatar/page/controller/SignupView.java index 5d127ea2a78c..ef0cad94dc14 100644 --- a/page-controller/src/main/java/com.iluwatar.page.controller/SignupView.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/SignupView.java @@ -24,26 +24,23 @@ */ package com.iluwatar.page.controller; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Signup View. - */ +/** Signup View. */ @Slf4j +@NoArgsConstructor public class SignupView { - public SignupView() { - } public String display() { LOGGER.info("display signup front page"); return "/signup"; } - /** - * redirect to user page. - */ + /** redirect to user page. */ public String redirect(SignupModel form) { - LOGGER.info("Redirect to user page with " + "name " + form.getName() + " email " + form.getEmail()); + LOGGER.info( + "Redirect to user page with " + "name " + form.getName() + " email " + form.getEmail()); return "redirect:/user"; } } diff --git a/page-controller/src/main/java/com.iluwatar.page.controller/UserController.java b/page-controller/src/main/java/com/iluwatar/page/controller/UserController.java similarity index 93% rename from page-controller/src/main/java/com.iluwatar.page.controller/UserController.java rename to page-controller/src/main/java/com/iluwatar/page/controller/UserController.java index e3bcca6f1f6d..2e0ee66824a4 100644 --- a/page-controller/src/main/java/com.iluwatar.page.controller/UserController.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/UserController.java @@ -24,24 +24,20 @@ */ package com.iluwatar.page.controller; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -/** - * User Controller. - */ +/** User Controller. */ @Slf4j @Controller +@NoArgsConstructor public class UserController { private final UserView view = new UserView(); - public UserController() {} - - /** - * Handle http GET request and access view and model. - */ + /** Handle http GET request and access view and model. */ @GetMapping("/user") public String getUserPath(SignupModel form, Model model) { model.addAttribute("name", form.getName()); diff --git a/page-controller/src/main/java/com.iluwatar.page.controller/UserModel.java b/page-controller/src/main/java/com/iluwatar/page/controller/UserModel.java similarity index 92% rename from page-controller/src/main/java/com.iluwatar.page.controller/UserModel.java rename to page-controller/src/main/java/com/iluwatar/page/controller/UserModel.java index 86e9548a2a7a..3dcb0e43e39d 100644 --- a/page-controller/src/main/java/com.iluwatar.page.controller/UserModel.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/UserModel.java @@ -24,17 +24,13 @@ */ package com.iluwatar.page.controller; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; +import lombok.NoArgsConstructor; -/** - * User model. - */ -@Getter -@Setter +/** User model. */ +@Data +@NoArgsConstructor public class UserModel { private String name; private String email; - - public UserModel() {} } diff --git a/page-controller/src/main/java/com.iluwatar.page.controller/UserView.java b/page-controller/src/main/java/com/iluwatar/page/controller/UserView.java similarity index 97% rename from page-controller/src/main/java/com.iluwatar.page.controller/UserView.java rename to page-controller/src/main/java/com/iluwatar/page/controller/UserView.java index 3f4469d7dbc8..e759cc54f177 100644 --- a/page-controller/src/main/java/com.iluwatar.page.controller/UserView.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/UserView.java @@ -26,13 +26,12 @@ import lombok.extern.slf4j.Slf4j; -/** - * User view class generating html file. - */ +/** User view class generating html file. */ @Slf4j public class UserView { /** * displaying command to generate html. + * * @param user model content. */ public String display(SignupModel user) { diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java index 9dcbfe3f8ef8..b3d374210dfe 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java @@ -24,15 +24,14 @@ */ package com.iluwatar.page.controller; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ public class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java index 7260c24d4291..08440d88d3d9 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java @@ -24,19 +24,16 @@ */ package com.iluwatar.page.controller; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; -import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Signup Controller - */ +/** Test for Signup Controller */ public class SignupControllerTest { - /** - * Verify if user can sign up and redirect to user page - */ + /** Verify if user can sign up and redirect to user page */ @Test void testSignup() { var controller = new SignupController(); diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java index 46e60eb94745..237ea138c395 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java @@ -24,16 +24,13 @@ */ package com.iluwatar.page.controller; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Signup Model - */ +import org.junit.jupiter.api.Test; + +/** Test for Signup Model */ public class SignupModelTest { - /** - * Verify if a user can set a name properly - */ + /** Verify if a user can set a name properly */ @Test void testSetName() { SignupModel model = new SignupModel(); @@ -41,9 +38,7 @@ void testSetName() { assertEquals("Lily", model.getName()); } - /** - * Verify if a user can set an email properly - */ + /** Verify if a user can set an email properly */ @Test void testSetEmail() { SignupModel model = new SignupModel(); @@ -51,9 +46,7 @@ void testSetEmail() { assertEquals("Lily@email", model.getEmail()); } - /** - * Verify if a user can set a password properly - */ + /** Verify if a user can set a password properly */ @Test void testSetPassword() { SignupModel model = new SignupModel(); diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java index 7fd1c0cd1c18..bf5eef59536d 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java @@ -24,6 +24,10 @@ */ package com.iluwatar.page.controller; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -31,9 +35,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(SpringExtension.class) @SpringBootTest @@ -41,17 +42,13 @@ public class UserControllerTest { private UserController userController; - @Autowired - MockMvc mockMvc; + @Autowired MockMvc mockMvc; - /** - * Verify if view and model are directed properly - */ + /** Verify if view and model are directed properly */ @Test - void testGetUserPath () throws Exception { - this.mockMvc.perform(get("/user") - .param("name", "Lily") - .param("email", "Lily@email.com")) + void testGetUserPath() throws Exception { + this.mockMvc + .perform(get("/user").param("name", "Lily").param("email", "Lily@email.com")) .andExpect(status().isOk()) .andExpect(model().attribute("name", "Lily")) .andExpect(model().attribute("email", "Lily@email.com")) diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java index 49e1102c8005..8ecafae2dbfc 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; public class UserModelTest { - /** - * Verify if a user can set a name properly - */ + /** Verify if a user can set a name properly */ @Test void testSetName() { UserModel model = new UserModel(); @@ -39,9 +37,7 @@ void testSetName() { assertEquals("Lily", model.getName()); } - /** - * Verify if a user can set an email properly - */ + /** Verify if a user can set an email properly */ @Test void testSetEmail() { UserModel model = new UserModel(); diff --git a/page-object/README.md b/page-object/README.md index 93777ad2e032..6f21070a3582 100644 --- a/page-object/README.md +++ b/page-object/README.md @@ -1,38 +1,51 @@ --- -title: Page Object -category: Structural +title: "Page Object Pattern in Java: Streamlining UI Testing for Better Maintainability" +shortTitle: Page Object +description: "Explore the Page Object design pattern for Java. Learn how to implement, use, and optimize this pattern for better code maintainability and test automation in Java applications." +category: Testing language: en tag: -- Decoupling + - Abstraction + - Code simplification + - Decoupling + - Encapsulation + - Testing + - Web development --- -# Page Object Pattern in Java +## Also known as -## Real World Example +* Page Object Model (POM) -Consider a web automation scenario where you need to interact with a web page using a test framework like Selenium. The Page Object pattern can be applied to model each web page as a Java class. Each class encapsulates the structure and behavior of the corresponding web page, making it easier to manage and update the automation code. +## Intent of Page Object Design Pattern -## Intent +The Page Object pattern in Java aims to create a model of the UI elements of a web page to improve the maintainability and readability of test automation code. -Page Object encapsulates the UI, hiding the underlying UI widgetry of an application (commonly a web application) and providing an application-specific API to allow the manipulation of UI components required for tests. In doing so, it allows the test class itself to focus on the test logic instead. +## Detailed Explanation of Page Object Pattern with Real-World Examples -## In Plain Words +Real-world example -The Page Object pattern in Java is a design pattern used in test automation to represent web pages as Java classes. Each class corresponds to a specific web page and contains methods to interact with the elements on that page. This pattern enhances code maintainability and readability in automated testing. +> Imagine a large corporate office where a receptionist directs visitors to the appropriate department. The receptionist serves as a single point of contact for all incoming visitors, simplifying the process of navigation within the building. Each department provides the receptionist with specific instructions on how to direct visitors to their office. +> +> In this analogy, the receptionist is like a Page Object in a testing framework. The receptionist abstracts the complexities of the office layout from the visitors, just as the Page Object abstracts the details of interacting with web elements from the test scripts. When the layout of the office changes, only the receptionist's instructions need to be updated, not the way visitors are directed, similar to how only the Page Object needs to be updated when the web UI changes, not the test scripts. -## Wikipedia Says +In plain words -While there isn't a specific Wikipedia entry for the Page Object pattern, it is widely used in software testing, particularly in the context of UI automation. The Page Object pattern helps abstract the details of a web page, providing a cleaner and more maintainable way to interact with web elements in automated tests. +> The Page Object design pattern creates an abstraction layer for web pages, encapsulating their elements and interactions to simplify and maintain automated UI testing scripts. -## Programmatic Example +selenium.dev says -Let's create a simple programmatic example of the Page Object pattern for a login page using Selenium in Java: +> Within your web app’s UI, there are areas where your tests interact with. A Page Object only models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix needs only to be applied in one place. +> +> Page Object is a Design Pattern that has become popular in test automation for enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your AUT. The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently, all changes to support that new UI are located in one place. -```java -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; +## Programmatic Example of Page Object Pattern in Java + +The Page Object design pattern is a popular design pattern in test automation. It helps in enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your Application Under Test (AUT). The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently, all changes to support that new UI are located in one place. + +Let's consider a simple programmatic example of the Page Object pattern for a login page using Selenium in Java: +```java public class LoginPage { private final WebDriver driver; @@ -70,17 +83,45 @@ In this example, the `LoginPage` class represents the login page of a web applic This Page Object can be used in test scripts to interact with the login page without exposing the details of the page structure in the test code, promoting maintainability and reusability. -## Applicability +## When to Use the Page Object Pattern in Java Use the Page Object pattern when -* You are writing automated tests for your web application and you want to separate the UI manipulation required for the tests from the actual test logic. -* Make your tests less brittle, and more readable and robust +* Automating UI tests for web applications. +* You want to separate the UI actions from the test logic. +* Enhancing test code readability and reducing duplication. +* Simplifying maintenance when the web UI changes. + +## Real-World Applications of Page Object Pattern in Java + +* Selenium WebDriver tests for web applications. +* Automated UI testing frameworks in Java. +* Popular test automation frameworks like TestNG and JUnit. + +## Benefits and Trade-offs of Page Object Pattern + +Benefits: + +* Encapsulation: Isolates the page elements and actions from test scripts. +* Code simplification: Reduces code duplication and improves readability. +* Maintainability: Easy to update when the UI changes, as changes are confined to page objects. +* Abstraction: Test scripts focus on what the user does rather than how the actions are performed on the UI. + +Trade-offs: + +* Initial Setup: Requires extra effort to design and implement page objects. +* Complexity: Overuse may lead to a complex structure with many page objects and methods. + +## Related Java Design Patterns -## Another example with Class diagram -![alt text](./etc/page-object.png "Page Object") +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used alongside Page Objects to add additional responsibilities to objects dynamically. +* [Facade](https://java-design-patterns.com/patterns/facade/): Both provide a simplified interface to a complex subsystem. Page Object abstracts the complexities of the UI. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Acts as a surrogate or placeholder, which can also be used for lazy initialization of page objects. -## Credits +## References and Credits -* [Martin Fowler - PageObject](http://martinfowler.com/bliki/PageObject.html) -* [Selenium - Page Objects](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) +* [Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation](https://amzn.to/4bjhTSK) +* [Selenium Design Patterns and Best Practices](https://amzn.to/4aofYv8) +* [Selenium Testing Tools Cookbook](https://amzn.to/3K1QxEN) +* [Page Object (Martin Fowler)](http://martinfowler.com/bliki/PageObject.html) +* [Page Objects (Selenium)](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) diff --git a/page-object/pom.xml b/page-object/pom.xml index 3e78d2f0c431..380288a53cc1 100644 --- a/page-object/pom.xml +++ b/page-object/pom.xml @@ -27,17 +27,6 @@ --> 4.0.0 - - 11 - 11 - - - - net.sourceforge.htmlunit - htmlunit - test - - com.iluwatar java-design-patterns @@ -49,6 +38,13 @@ sample-application test-automation + + + org.htmlunit + htmlunit + test + + diff --git a/page-object/sample-application/pom.xml b/page-object/sample-application/pom.xml index e74f4f539795..cf1b96e0901c 100644 --- a/page-object/sample-application/pom.xml +++ b/page-object/sample-application/pom.xml @@ -32,5 +32,15 @@ com.iluwatar 1.26.0-SNAPSHOT + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + sample-application diff --git a/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java b/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java index 0913af68e538..aaf19fb0cee2 100644 --- a/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java +++ b/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java @@ -52,8 +52,7 @@ @Slf4j public final class App { - private App() { - } + private App() {} /** * Application entry point @@ -85,6 +84,5 @@ public static void main(String[] args) { } catch (IOException ex) { LOGGER.error("An error occurred.", ex); } - } } diff --git a/page-object/src/main/java/com/iluwatar/pageobject/App.java b/page-object/src/main/java/com/iluwatar/pageobject/App.java index c779ff9e3e56..a0eda082f726 100644 --- a/page-object/src/main/java/com/iluwatar/pageobject/App.java +++ b/page-object/src/main/java/com/iluwatar/pageobject/App.java @@ -50,8 +50,7 @@ */ public final class App { - private App() { - } + private App() {} /** * Application entry point @@ -84,6 +83,5 @@ public static void main(String[] args) { } catch (IOException ex) { ex.printStackTrace(); } - } } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java index 9bab578a5d47..bbaf6bfea859 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java @@ -26,14 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import com.gargoylesoftware.htmlunit.WebClient; import com.iluwatar.pageobject.pages.AlbumListPage; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Selection and Album Listing - */ +/** Test Album Selection and Album Listing */ class AlbumListPageTest { private final AlbumListPage albumListPage = new AlbumListPage(new WebClient()); @@ -49,5 +47,4 @@ void testSelectAlbum() { albumPage.navigateToPage(); assertTrue(albumPage.isAt()); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java index 4448efbd8d1e..08dbd6d59335 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java @@ -26,14 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import com.gargoylesoftware.htmlunit.WebClient; import com.iluwatar.pageobject.pages.AlbumPage; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Page Operations - */ +/** Test Album Page Operations */ class AlbumPageTest { private final AlbumPage albumPage = new AlbumPage(new WebClient()); @@ -46,16 +44,16 @@ void setUp() { @Test void testSaveAlbum() { - var albumPageAfterChanges = albumPage - .changeAlbumTitle("25") - .changeArtist("Adele Laurie Blue Adkins") - .changeAlbumYear(2015) - .changeAlbumRating("B") - .changeNumberOfSongs(20) - .saveChanges(); + var albumPageAfterChanges = + albumPage + .changeAlbumTitle("25") + .changeArtist("Adele Laurie Blue Adkins") + .changeAlbumYear(2015) + .changeAlbumRating("B") + .changeNumberOfSongs(20) + .saveChanges(); assertTrue(albumPageAfterChanges.isAt()); - } @Test @@ -64,5 +62,4 @@ void testCancelChanges() { albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java index ac74aee1274e..8cf22176904d 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java @@ -26,14 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import com.gargoylesoftware.htmlunit.WebClient; import com.iluwatar.pageobject.pages.LoginPage; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Login Page Object - */ +/** Test Login Page Object */ class LoginPageTest { private final LoginPage loginPage = new LoginPage(new WebClient()); @@ -45,12 +43,8 @@ void setUp() { @Test void testLogin() { - var albumListPage = loginPage - .enterUsername("admin") - .enterPassword("password") - .login(); + var albumListPage = loginPage.enterUsername("admin").enterPassword("password").login(); albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java index f5126db59567..bd59d9292897 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java @@ -24,15 +24,13 @@ */ package com.iluwatar.pageobject.pages; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlAnchor; -import com.gargoylesoftware.htmlunit.html.HtmlPage; import java.io.IOException; import java.util.List; +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlAnchor; +import org.htmlunit.html.HtmlPage; -/** - * Page Object encapsulating the Album List page (album-list.html) - */ +/** Page Object encapsulating the Album List page (album-list.html) */ public class AlbumListPage extends Page { private static final String ALBUM_LIST_HTML_FILE = "album-list.html"; @@ -40,15 +38,11 @@ public class AlbumListPage extends Page { private HtmlPage page; - - /** - * Constructor - */ + /** Constructor */ public AlbumListPage(WebClient webClient) { super(webClient); } - /** * Navigates to the Album List Page * @@ -63,9 +57,7 @@ public AlbumListPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album List".equals(page.getTitleText()); @@ -92,6 +84,4 @@ public AlbumPage selectAlbum(String albumTitle) { } throw new IllegalArgumentException("No links with the album title: " + albumTitle); } - - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java index 92df556a5e41..e21898b8c7ad 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java @@ -24,18 +24,15 @@ */ package com.iluwatar.pageobject.pages; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlNumberInput; -import com.gargoylesoftware.htmlunit.html.HtmlOption; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlSelect; -import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; -import com.gargoylesoftware.htmlunit.html.HtmlTextInput; import java.io.IOException; - -/** - * Page Object encapsulating the Album Page (album-page.html) - */ +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlNumberInput; +import org.htmlunit.html.HtmlPage; +import org.htmlunit.html.HtmlSelect; +import org.htmlunit.html.HtmlSubmitInput; +import org.htmlunit.html.HtmlTextInput; + +/** Page Object encapsulating the Album Page (album-page.html) */ public class AlbumPage extends Page { private static final String ALBUM_PAGE_HTML_FILE = "album-page.html"; @@ -43,15 +40,11 @@ public class AlbumPage extends Page { private HtmlPage page; - - /** - * Constructor - */ + /** Constructor */ public AlbumPage(WebClient webClient) { super(webClient); } - /** * Navigates to the album page * @@ -66,16 +59,12 @@ public AlbumPage navigateToPage() { return this; } - - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album Page".equals(page.getTitleText()); } - /** * Sets the album title input text field * @@ -88,7 +77,6 @@ public AlbumPage changeAlbumTitle(String albumTitle) { return this; } - /** * Sets the artist input text field * @@ -101,7 +89,6 @@ public AlbumPage changeArtist(String artist) { return this; } - /** * Selects the select's option value based on the year value given * @@ -115,7 +102,6 @@ public AlbumPage changeAlbumYear(int year) { return this; } - /** * Sets the album rating input text field * @@ -140,7 +126,6 @@ public AlbumPage changeNumberOfSongs(int numberOfSongs) { return this; } - /** * Cancel changes made by clicking the cancel button * @@ -156,7 +141,6 @@ public AlbumListPage cancelChanges() { return new AlbumListPage(webClient); } - /** * Saves changes made by clicking the save button * @@ -171,5 +155,4 @@ public AlbumPage saveChanges() { } return this; } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java index afdc9650ea18..b113696c026d 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java @@ -24,16 +24,14 @@ */ package com.iluwatar.pageobject.pages; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; -import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; -import com.gargoylesoftware.htmlunit.html.HtmlTextInput; import java.io.IOException; +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlPage; +import org.htmlunit.html.HtmlPasswordInput; +import org.htmlunit.html.HtmlSubmitInput; +import org.htmlunit.html.HtmlTextInput; -/** - * Page Object encapsulating the Login Page (login.html) - */ +/** Page Object encapsulating the Login Page (login.html) */ public class LoginPage extends Page { private static final String LOGIN_PAGE_HTML_FILE = "login.html"; @@ -64,15 +62,12 @@ public LoginPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Login".equals(page.getTitleText()); } - /** * Enters the username into the username input text field * @@ -85,7 +80,6 @@ public LoginPage enterUsername(String username) { return this; } - /** * Enters the password into the password input password field * @@ -98,7 +92,6 @@ public LoginPage enterPassword(String password) { return this; } - /** * Clicking on the login button to 'login' * @@ -114,5 +107,4 @@ public AlbumListPage login() { } return new AlbumListPage(webClient); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java index eb164d31e4da..499a15774f9e 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java @@ -24,16 +24,12 @@ */ package com.iluwatar.pageobject.pages; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; -/** - * Encapsulation for a generic 'Page' - */ +/** Encapsulation for a generic 'Page' */ public abstract class Page { - /** - * Application Under Test path This directory location is where html web pages are located - */ + /** Application Under Test path This directory location is where html web pages are located */ public static final String AUT_PATH = "src/main/resources/sample-ui/"; protected final WebClient webClient; @@ -53,6 +49,4 @@ public Page(WebClient webClient) { * @return true if so, otherwise false */ public abstract boolean isAt(); - - } diff --git a/page-object/test-automation/pom.xml b/page-object/test-automation/pom.xml index 851fdf376598..ea5ac819f726 100644 --- a/page-object/test-automation/pom.xml +++ b/page-object/test-automation/pom.xml @@ -34,13 +34,21 @@ test-automation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine test - net.sourceforge.htmlunit + org.htmlunit htmlunit diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java index 83917be917aa..9b78b0a8b43e 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java @@ -24,16 +24,14 @@ */ package com.iluwatar.pageobject; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlAnchor; -import com.gargoylesoftware.htmlunit.html.HtmlPage; import java.io.IOException; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlAnchor; +import org.htmlunit.html.HtmlPage; -/** - * Page Object encapsulating the Album List page (album-list.html) - */ +/** Page Object encapsulating the Album List page (album-list.html) */ @Slf4j public class AlbumListPage extends Page { private static final String ALBUM_LIST_HTML_FILE = "album-list.html"; @@ -41,15 +39,11 @@ public class AlbumListPage extends Page { private HtmlPage page; - - /** - * Constructor. - */ + /** Constructor. */ public AlbumListPage(WebClient webClient) { super(webClient); } - /** * Navigates to the Album List Page. * @@ -64,9 +58,7 @@ public AlbumListPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album List".equals(page.getTitleText()); @@ -93,6 +85,4 @@ public AlbumPage selectAlbum(String albumTitle) { } throw new IllegalArgumentException("No links with the album title: " + albumTitle); } - - } diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java index 625293a7a2cc..6da0b05f37af 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java @@ -24,18 +24,16 @@ */ package com.iluwatar.pageobject; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlNumberInput; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlSelect; -import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; -import com.gargoylesoftware.htmlunit.html.HtmlTextInput; import java.io.IOException; import lombok.extern.slf4j.Slf4j; - -/** - * Page Object encapsulating the Album Page (album-page.html) - */ +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlNumberInput; +import org.htmlunit.html.HtmlPage; +import org.htmlunit.html.HtmlSelect; +import org.htmlunit.html.HtmlSubmitInput; +import org.htmlunit.html.HtmlTextInput; + +/** Page Object encapsulating the Album Page (album-page.html) */ @Slf4j public class AlbumPage extends Page { private static final String ALBUM_PAGE_HTML_FILE = "album-page.html"; @@ -43,15 +41,11 @@ public class AlbumPage extends Page { private HtmlPage page; - - /** - * Constructor. - */ + /** Constructor. */ public AlbumPage(WebClient webClient) { super(webClient); } - /** * Navigates to the album page. * @@ -66,16 +60,12 @@ public AlbumPage navigateToPage() { return this; } - - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album Page".equals(page.getTitleText()); } - /** * Sets the album title input text field. * @@ -88,7 +78,6 @@ public AlbumPage changeAlbumTitle(String albumTitle) { return this; } - /** * Sets the artist input text field. * @@ -101,7 +90,6 @@ public AlbumPage changeArtist(String artist) { return this; } - /** * Selects the select's option value based on the year value given. * @@ -115,7 +103,6 @@ public AlbumPage changeAlbumYear(int year) { return this; } - /** * Sets the album rating input text field. * @@ -140,7 +127,6 @@ public AlbumPage changeNumberOfSongs(int numberOfSongs) { return this; } - /** * Cancel changes made by clicking the cancel button. * @@ -156,7 +142,6 @@ public AlbumListPage cancelChanges() { return new AlbumListPage(webClient); } - /** * Saves changes made by clicking the save button. * @@ -171,5 +156,4 @@ public AlbumPage saveChanges() { } return this; } - } diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java index f87b58e76867..3d4f7c8f2d96 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java @@ -24,17 +24,15 @@ */ package com.iluwatar.pageobject; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; -import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; -import com.gargoylesoftware.htmlunit.html.HtmlTextInput; import java.io.IOException; import lombok.extern.slf4j.Slf4j; +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlPage; +import org.htmlunit.html.HtmlPasswordInput; +import org.htmlunit.html.HtmlSubmitInput; +import org.htmlunit.html.HtmlTextInput; -/** - * Page Object encapsulating the Login Page (login.html) - */ +/** Page Object encapsulating the Login Page (login.html) */ @Slf4j public class LoginPage extends Page { private static final String LOGIN_PAGE_HTML_FILE = "login.html"; @@ -65,15 +63,12 @@ public LoginPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Login".equals(page.getTitleText()); } - /** * Enters the username into the username input text field. * @@ -86,7 +81,6 @@ public LoginPage enterUsername(String username) { return this; } - /** * Enters the password into the password input password field. * @@ -99,7 +93,6 @@ public LoginPage enterPassword(String password) { return this; } - /** * Clicking on the login button to 'login'. * @@ -115,5 +108,4 @@ public AlbumListPage login() { } return new AlbumListPage(webClient); } - } diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java index 49e7397fa5c5..cbc24bd6eaf9 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java @@ -24,16 +24,12 @@ */ package com.iluwatar.pageobject; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; -/** - * Encapsulation for a generic 'Page'. - */ +/** Encapsulation for a generic 'Page'. */ public abstract class Page { - /** - * Application Under Test path This directory location is where html web pages are located. - */ + /** Application Under Test path This directory location is where html web pages are located. */ public static final String AUT_PATH = "../sample-application/src/main/resources/sample-ui/"; protected final WebClient webClient; @@ -53,6 +49,4 @@ public Page(WebClient webClient) { * @return true if so, otherwise false */ public abstract boolean isAt(); - - } diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java index 519baa4edef9..1b710a37bc35 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java @@ -26,13 +26,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Selection and Album Listing - */ +/** Test Album Selection and Album Listing */ class AlbumListPageTest { private final AlbumListPage albumListPage = new AlbumListPage(new WebClient()); @@ -48,5 +46,4 @@ void testSelectAlbum() { albumPage.navigateToPage(); assertTrue(albumPage.isAt()); } - } diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java index f0586ee40320..85a9afe807cc 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java @@ -26,13 +26,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Page Operations - */ +/** Test Album Page Operations */ class AlbumPageTest { private final AlbumPage albumPage = new AlbumPage(new WebClient()); @@ -45,16 +43,16 @@ void setUp() { @Test void testSaveAlbum() { - var albumPageAfterChanges = albumPage - .changeAlbumTitle("25") - .changeArtist("Adele Laurie Blue Adkins") - .changeAlbumYear(2015) - .changeAlbumRating("B") - .changeNumberOfSongs(20) - .saveChanges(); + var albumPageAfterChanges = + albumPage + .changeAlbumTitle("25") + .changeArtist("Adele Laurie Blue Adkins") + .changeAlbumYear(2015) + .changeAlbumRating("B") + .changeNumberOfSongs(20) + .saveChanges(); assertTrue(albumPageAfterChanges.isAt()); - } @Test @@ -63,5 +61,4 @@ void testCancelChanges() { albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java index 3e0bb6736d10..660487002b68 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java @@ -26,13 +26,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import com.gargoylesoftware.htmlunit.WebClient; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Login Page Object - */ +/** Test Login Page Object */ class LoginPageTest { private final LoginPage loginPage = new LoginPage(new WebClient()); @@ -44,12 +42,8 @@ void setUp() { @Test void testLogin() { - var albumListPage = loginPage - .enterUsername("admin") - .enterPassword("password") - .login(); + var albumListPage = loginPage.enterUsername("admin").enterPassword("password").login(); albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/parameter-object/README.md b/parameter-object/README.md index f579e6aff348..bbb1c73d95cc 100644 --- a/parameter-object/README.md +++ b/parameter-object/README.md @@ -1,136 +1,172 @@ --- -title: Parameter Object -category: Behavioral +title: "Parameter Object Pattern in Java: Simplifying Method Signatures with Structured Data" +shortTitle: Parameter Object +description: "Explore the Parameter Object pattern at Java Design Patterns. Learn how it simplifies method signatures, enhances maintainability, and promotes encapsulation with real-world examples and detailed code snippets." +category: Structural language: en tag: - - Extensibility + - Abstraction + - Code simplification + - Decoupling + - Encapsulation + - Object composition --- -## Intent +## Also known as -The syntax of Java language doesn’t allow you to declare a method with a predefined value -for a parameter. Probably the best option to achieve default method parameters in Java is -by using the method overloading. Method overloading allows you to declare several methods -with the same name but with a different number of parameters. But the main problem with -method overloading as a solution for default parameter values reveals itself when a method -accepts multiple parameters. Creating an overloaded method for each possible combination of -parameters might be cumbersome. To deal with this issue, the Parameter Object pattern is used. +* Argument Object -## Explanation +## Intent of Parameter Object Design Pattern -The Parameter Object is simply a wrapper object for all parameters of a method. -It is nothing more than just a regular POJO. The advantage of the Parameter Object over a -regular method parameter list is the fact that class fields can have default values. -Once the wrapper class is created for the method parameter list, a corresponding builder class -is also created. Usually it's an inner static class. The final step is to use the builder -to construct a new parameter object. For those parameters that are skipped, -their default values are going to be used. +The Parameter Object pattern is a key Java design pattern aimed at improving code maintainability by reducing method complexity through encapsulation of parameters into a single object. +## Detailed Explanation of Parameter Object Pattern with Real-World Examples -**Programmatic Example** +Real-world example -Here's the simple `SearchService` class where Method Overloading is used to default values here. To use method overloading, either the number of arguments or argument type has to be different. +> Imagine booking a travel package that includes a flight, hotel, and car rental. Instead of asking the customer to provide separate details for each component (flight details, hotel details, and car rental details) every time, a travel agent asks the customer to fill out a single comprehensive form that encapsulates all the necessary information: +> +> - Flight details: Departure city, destination city, departure date, return date. +> - Hotel details: Hotel name, check-in date, check-out date, room type. +> - Car rental details: Pickup location, drop-off location, rental dates, car type. +> +> In this analogy, the comprehensive form is the parameter object. It groups together all related details (parameters) into a single entity, making the booking process more streamlined and manageable. The travel agent (method) only needs to handle one form (parameter object) instead of juggling multiple pieces of information. -```java -public class SearchService { - //Method Overloading example. SortOrder is defaulted in this method - public String search(String type, String sortBy) { - return getQuerySummary(type, sortBy, SortOrder.DESC); - } - - /* Method Overloading example. SortBy is defaulted in this method. Note that the type has to be - different here to overload the method */ - public String search(String type, SortOrder sortOrder) { - return getQuerySummary(type, "price", sortOrder); - } - - private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) { - return "Requesting shoes of type \"" + type + "\" sorted by \"" + sortBy + "\" in \"" - + sortOrder.getValue() + "ending\" order..."; - } -} +In plain words -``` +> The Parameter Object pattern encapsulates multiple related parameters into a single object to simplify method signatures and enhance code maintainability. -Next we present the `SearchService` with `ParameterObject` created with Builder pattern. +wiki.c2.com says -```java -public class SearchService { +> Replace the LongParameterList with a ParameterObject; an object or structure with data members representing the arguments to be passed in. - /* Parameter Object example. Default values are abstracted into the Parameter Object - at the time of Object creation */ - public String search(ParameterObject parameterObject) { - return getQuerySummary(parameterObject.getType(), parameterObject.getSortBy(), - parameterObject.getSortOrder()); - } - - private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) { - return "Requesting shoes of type \"" + type + "\" sorted by \"" + sortBy + "\" in \"" - + sortOrder.getValue() + "ending\" order..."; - } -} +## Programmatic Example of Parameter Object Pattern in Java + +The Parameter Object design pattern is a way to group multiple parameters into a single object. This simplifies method signatures and enhances code maintainability enabling Java developers to streamline complex method calls, focusing on cleaner and more maintainable Java code. +First, let's look at the `ParameterObject` class. This class encapsulates the parameters needed for the search operation. It uses [Builder pattern](https://java-design-patterns.com/patterns/builder/) to allow for easy creation of objects, even when there are many parameters. + +```java public class ParameterObject { - public static final String DEFAULT_SORT_BY = "price"; - public static final SortOrder DEFAULT_SORT_ORDER = SortOrder.ASC; - private String type; - private String sortBy = DEFAULT_SORT_BY; - private SortOrder sortOrder = DEFAULT_SORT_ORDER; + private final String type; + private final String sortBy; + private final SortOrder sortOrder; - private ParameterObject(Builder builder) { - type = builder.type; - sortBy = builder.sortBy != null && !builder.sortBy.isBlank() ? builder.sortBy : sortBy; - sortOrder = builder.sortOrder != null ? builder.sortOrder : sortOrder; - } + private ParameterObject(Builder builder) { + this.type = builder.type; + this.sortBy = builder.sortBy; + this.sortOrder = builder.sortOrder; + } - public static Builder newBuilder() { - return new Builder(); - } + public static Builder newBuilder() { + return new Builder(); + } - //Getters and Setters... + // getters and Builder class omitted for brevity +} +``` - public static final class Builder { +The `Builder` class inside `ParameterObject` provides a way to construct a `ParameterObject` instance. It has methods for setting each of the parameters, and a `build` method to create the `ParameterObject`. - private String type; - private String sortBy; - private SortOrder sortOrder; +```java +public static class Builder { - private Builder() { - } + private String type = "all"; + private String sortBy = "price"; + private SortOrder sortOrder = SortOrder.ASCENDING; public Builder withType(String type) { - this.type = type; - return this; + this.type = type; + return this; } public Builder sortBy(String sortBy) { - this.sortBy = sortBy; - return this; + this.sortBy = sortBy; + return this; } public Builder sortOrder(SortOrder sortOrder) { - this.sortOrder = sortOrder; - return this; + this.sortOrder = sortOrder; + return this; } public ParameterObject build() { - return new ParameterObject(this); + return new ParameterObject(this); } - } } +``` + +The `SearchService` class has a `search()` method that takes a `ParameterObject` as a parameter. This method uses the parameters encapsulated in the `ParameterObject` to perform a search operation. +```java +public class SearchService { + public String search(ParameterObject parameterObject) { + return getQuerySummary(parameterObject.getType(), parameterObject.getSortBy(), + parameterObject.getSortOrder()); + } + + // getQuerySummary method omitted for brevity +} +``` + +Finally, in the `App` class, we create a `ParameterObject` using its builder, and then pass it to the `search()` method of `SearchService`. + +```java +public class App { + + public static void main(String[] args) { + ParameterObject params = ParameterObject.newBuilder() + .withType("sneakers") + .sortBy("brand") + .build(); + LOGGER.info(params.toString()); + LOGGER.info(new SearchService().search(params)); + } +} ``` -## Class diagram +This example demonstrates how the Parameter Object pattern can simplify method signatures and make the code more maintainable. It also shows how the pattern can be combined with the Builder pattern to make object creation more flexible and readable. + +## When to Use the Parameter Object Pattern in Java + +* Methods require multiple parameters that logically belong together. +* There is a need to reduce the complexity of method signatures. +* The parameters may need to evolve over time, adding more properties without breaking existing method signatures. +* It’s beneficial to pass data through a method chain. + +## Parameter Object Pattern Java Tutorials + +* [Does Java have default parameters? (Daniel Olszewski)](http://dolszewski.com/java/java-default-parameters) + +## Real-World Applications of Parameter Object Pattern in Java + +* Java Libraries: Many Java frameworks and libraries use this pattern. For example, Java’s java.util.Calendar class has various methods where parameter objects are used to represent date and time components. +* Enterprise Applications: In large enterprise systems, parameter objects are used to encapsulate configuration data passed to services or API endpoints. + +## Benefits and Trade-offs of Parameter Object Pattern + +Benefits: + +* Encapsulation: Groups related parameters into a single object, promoting encapsulation. +* Maintainability: Reduces method signature changes when parameters need to be added or modified. +* Readability: Simplifies method signatures, making the code easier to read and understand. +* Reusability: Parameter objects can be reused across different methods, reducing redundancy. + +Trade-offs: -![alt text](./etc/parameter-object.png "Parameter Object") +* Overhead: Introducing parameter objects can add some overhead, especially for simple methods that do not benefit significantly from this abstraction. +* Complexity: The initial creation of parameter objects might add complexity, especially for beginners. -## Applicability +## Related Patterns -This pattern shows us the way to have default parameters for a method in Java as the language doesn't default parameters feature out of the box. +* [Builder](https://java-design-patterns.com/patterns/builder/): Helps in creating complex objects step-by-step, often used in conjunction with parameter objects to manage the construction of these objects. +* [Composite](https://java-design-patterns.com/patterns/composite/): Sometimes used with parameter objects to handle hierarchical parameter data. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Can be used to create instances of parameter objects, particularly when different parameter combinations are needed. -## Credits +## References and Credits -- [Does Java have default parameters?](http://dolszewski.com/java/java-default-parameters) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Refactoring: Improving the Design of Existing Code](https://amzn.to/3TVEgaB) diff --git a/parameter-object/pom.xml b/parameter-object/pom.xml index a9cfec4732f3..9906e880adfb 100644 --- a/parameter-object/pom.xml +++ b/parameter-object/pom.xml @@ -34,6 +34,14 @@ parameter-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java index 8e02434a764f..d4d1e45abd92 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java @@ -28,19 +28,18 @@ import org.slf4j.LoggerFactory; /** - * The syntax of Java language doesn’t allow you to declare a method with a predefined value - * for a parameter. Probably the best option to achieve default method parameters in Java is - * by using the method overloading. Method overloading allows you to declare several methods - * with the same name but with a different number of parameters. But the main problem with - * method overloading as a solution for default parameter values reveals itself when a method - * accepts multiple parameters. Creating an overloaded method for each possible combination of - * parameters might be cumbersome. To deal with this issue, the Parameter Object pattern is used. - * The Parameter Object is simply a wrapper object for all parameters of a method. - * It is nothing more than just a regular POJO. The advantage of the Parameter Object over a - * regular method parameter list is the fact that class fields can have default values. - * Once the wrapper class is created for the method parameter list, a corresponding builder class - * is also created. Usually it's an inner static class. The final step is to use the builder - * to construct a new parameter object. For those parameters that are skipped, + * The syntax of Java language doesn’t allow you to declare a method with a predefined value for a + * parameter. Probably the best option to achieve default method parameters in Java is by using the + * method overloading. Method overloading allows you to declare several methods with the same name + * but with a different number of parameters. But the main problem with method overloading as a + * solution for default parameter values reveals itself when a method accepts multiple parameters. + * Creating an overloaded method for each possible combination of parameters might be cumbersome. To + * deal with this issue, the Parameter Object pattern is used. The Parameter Object is simply a + * wrapper object for all parameters of a method. It is nothing more than just a regular POJO. The + * advantage of the Parameter Object over a regular method parameter list is the fact that class + * fields can have default values. Once the wrapper class is created for the method parameter list, + * a corresponding builder class is also created. Usually it's an inner static class. The final step + * is to use the builder to construct a new parameter object. For those parameters that are skipped, * their default values are going to be used. */ public class App { @@ -53,10 +52,8 @@ public class App { * @param args command line args */ public static void main(String[] args) { - ParameterObject params = ParameterObject.newBuilder() - .withType("sneakers") - .sortBy("brand") - .build(); + ParameterObject params = + ParameterObject.newBuilder().withType("sneakers").sortBy("brand").build(); LOGGER.info(params.toString()); LOGGER.info(new SearchService().search(params)); } diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java index 3ad217f1e156..d211d50892cb 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java @@ -24,28 +24,27 @@ */ package com.iluwatar.parameter.object; -/** - * ParameterObject. - */ +import lombok.Getter; +import lombok.Setter; + +/** ParameterObject. */ +@Getter +@Setter public class ParameterObject { - /** - * Default values are defined here. - */ + /** Default values are defined here. */ public static final String DEFAULT_SORT_BY = "price"; + public static final SortOrder DEFAULT_SORT_ORDER = SortOrder.ASC; private String type; - /** - * Default values are assigned here. - */ + /** Default values are assigned here. */ private String sortBy = DEFAULT_SORT_BY; + private SortOrder sortOrder = DEFAULT_SORT_ORDER; - /** - * Overriding default values on object creation only when builder object has a valid value. - */ + /** Overriding default values on object creation only when builder object has a valid value. */ private ParameterObject(Builder builder) { setType(builder.type); setSortBy(builder.sortBy != null && !builder.sortBy.isBlank() ? builder.sortBy : sortBy); @@ -56,47 +55,20 @@ public static Builder newBuilder() { return new Builder(); } - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getSortBy() { - return sortBy; - } - - public void setSortBy(String sortBy) { - this.sortBy = sortBy; - } - - public SortOrder getSortOrder() { - return sortOrder; - } - - public void setSortOrder(SortOrder sortOrder) { - this.sortOrder = sortOrder; - } - @Override public String toString() { - return String.format("ParameterObject[type='%s', sortBy='%s', sortOrder='%s']", - type, sortBy, sortOrder); + return String.format( + "ParameterObject[type='%s', sortBy='%s', sortOrder='%s']", type, sortBy, sortOrder); } - /** - * Builder for ParameterObject. - */ + /** Builder for ParameterObject. */ public static final class Builder { private String type; private String sortBy; private SortOrder sortOrder; - private Builder() { - } + private Builder() {} public Builder withType(String type) { this.type = type; diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java index 5179f886bebc..9adf9fbfd40d 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java @@ -24,17 +24,15 @@ */ package com.iluwatar.parameter.object; -/** - * SearchService to demonstrate parameter object pattern. - */ +/** SearchService to demonstrate parameter object pattern. */ public class SearchService { /** - * Below two methods of name `search` is overloaded so that we can send a default value for - * one of the criteria and call the final api. A default SortOrder is sent in the first method - * and a default SortBy is sent in the second method. So two separate method definitions are - * needed for having default values for one argument in each case. Hence multiple overloaded - * methods are needed as the number of argument increases. + * Below two methods of name `search` is overloaded so that we can send a default value for one of + * the criteria and call the final api. A default SortOrder is sent in the first method and a + * default SortBy is sent in the second method. So two separate method definitions are needed for + * having default values for one argument in each case. Hence, multiple overloaded methods are + * needed as the number of argument increases. */ public String search(String type, String sortBy) { return getQuerySummary(type, sortBy, SortOrder.ASC); @@ -44,21 +42,19 @@ public String search(String type, SortOrder sortOrder) { return getQuerySummary(type, "price", sortOrder); } - /** - * The need for multiple method definitions can be avoided by the Parameter Object pattern. - * Below is the example where only one method is required and all the logic for having default - * values are abstracted into the Parameter Object at the time of object creation. + * The need for multiple method definitions can be avoided by the Parameter Object pattern. Below + * is the example where only one method is required and all the logic for having default values + * are abstracted into the Parameter Object at the time of object creation. */ public String search(ParameterObject parameterObject) { - return getQuerySummary(parameterObject.getType(), parameterObject.getSortBy(), - parameterObject.getSortOrder()); + return getQuerySummary( + parameterObject.getType(), parameterObject.getSortBy(), parameterObject.getSortOrder()); } private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) { - return String.format("Requesting shoes of type \"%s\" sorted by \"%s\" in \"%sending\" order..", - type, - sortBy, - sortOrder.getValue()); + return String.format( + "Requesting shoes of type \"%s\" sorted by \"%s\" in \"%sending\" order..", + type, sortBy, sortOrder.getValue()); } } diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java index 546e4111b642..68a7b701ffd5 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java @@ -24,20 +24,16 @@ */ package com.iluwatar.parameter.object; -/** - * enum for sort order types. - */ +import lombok.Getter; + +/** enum for sort order types. */ public enum SortOrder { ASC("asc"), DESC("desc"); - private String value; + @Getter private String value; SortOrder(String value) { this.value = value; } - - public String getValue() { - return value; - } } diff --git a/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java b/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java index d51b71cd86e0..c28e77fed956 100644 --- a/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java +++ b/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java @@ -28,12 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java b/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java index 93b1aff5e4e0..0db0c0d36f72 100644 --- a/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java +++ b/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java @@ -24,41 +24,38 @@ */ package com.iluwatar.parameter.object; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.jupiter.api.Assertions.assertEquals; - class ParameterObjectTest { private static final Logger LOGGER = LoggerFactory.getLogger(ParameterObjectTest.class); @Test void testForDefaultSortBy() { - //Creating parameter object with default value for SortBy set - ParameterObject params = ParameterObject.newBuilder() - .withType("sneakers") - .sortOrder(SortOrder.DESC) - .build(); - - assertEquals(ParameterObject.DEFAULT_SORT_BY, params.getSortBy(), - "Default SortBy is not set."); - LOGGER.info("{} Default parameter value is set during object creation as no value is passed." - , "SortBy"); + // Creating parameter object with default value for SortBy set + ParameterObject params = + ParameterObject.newBuilder().withType("sneakers").sortOrder(SortOrder.DESC).build(); + + assertEquals(ParameterObject.DEFAULT_SORT_BY, params.getSortBy(), "Default SortBy is not set."); + LOGGER.info( + "{} Default parameter value is set during object creation as no value is passed.", + "SortBy"); } @Test void testForDefaultSortOrder() { - //Creating parameter object with default value for SortOrder set - ParameterObject params = ParameterObject.newBuilder() - .withType("sneakers") - .sortBy("brand") - .build(); - - assertEquals(ParameterObject.DEFAULT_SORT_ORDER, params.getSortOrder(), - "Default SortOrder is not set."); - LOGGER.info("{} Default parameter value is set during object creation as no value is passed." - , "SortOrder"); + // Creating parameter object with default value for SortOrder set + ParameterObject params = + ParameterObject.newBuilder().withType("sneakers").sortBy("brand").build(); + + assertEquals( + ParameterObject.DEFAULT_SORT_ORDER, params.getSortOrder(), "Default SortOrder is not set."); + LOGGER.info( + "{} Default parameter value is set during object creation as no value is passed.", + "SortOrder"); } } diff --git a/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java b/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java index 32e6698320af..ce7a0afccce8 100644 --- a/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java +++ b/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java @@ -24,13 +24,13 @@ */ package com.iluwatar.parameter.object; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.jupiter.api.Assertions.assertEquals; - class SearchServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(SearchServiceTest.class); private ParameterObject parameterObject; @@ -38,25 +38,25 @@ class SearchServiceTest { @BeforeEach void setUp() { - //Creating parameter object with default values set - parameterObject = ParameterObject.newBuilder() - .withType("sneakers") - .build(); + // Creating parameter object with default values set + parameterObject = ParameterObject.newBuilder().withType("sneakers").build(); searchService = new SearchService(); } - /** - * Testing parameter object against the overloaded method to verify if the behaviour is same. - */ + /** Testing parameter object against the overloaded method to verify if the behaviour is same. */ @Test void testDefaultParametersMatch() { - assertEquals(searchService.search(parameterObject), searchService.search("sneakers", - SortOrder.ASC), "Default Parameter values do not not match."); + assertEquals( + searchService.search(parameterObject), + searchService.search("sneakers", SortOrder.ASC), + "Default Parameter values do not not match."); LOGGER.info("SortBy Default parameter value matches."); - assertEquals(searchService.search(parameterObject), searchService.search("sneakers", - "price"), "Default Parameter values do not not match."); + assertEquals( + searchService.search(parameterObject), + searchService.search("sneakers", "price"), + "Default Parameter values do not not match."); LOGGER.info("SortOrder Default parameter value matches."); LOGGER.info("testDefaultParametersMatch executed successfully without errors."); diff --git a/partial-response/README.md b/partial-response/README.md index 2729fe1b8908..a3704a4c3453 100644 --- a/partial-response/README.md +++ b/partial-response/README.md @@ -1,24 +1,181 @@ --- -title: Partial Response +title: "Partial Response Pattern in Java: Optimizing Data Delivery for Efficient Web Services" +shortTitle: Partial Response +description: "Explore the Partial Response design pattern for APIs, a strategy to boost performance by allowing clients to process data as soon as it becomes available. Learn how it improves scalability and reduces server load." category: Behavioral language: en tag: - - Decoupling + - API design + - Asynchronous + - Client-server + - Decoupling + - Performance + - Scalability + - Web development --- -## Intent -Send partial response from server to client on need basis. Client will specify the fields -that it needs to server, instead of serving all details for resource. +## Also known as -## Class diagram -![alt text](./etc/partial-response.urm.png "partial-response") +* Incremental Response +* Partial Result + +## Intent of Partial Response Design Pattern + +To enable an application to return a partial response to a client, improving perceived performance and enabling the client to start processing parts of the data before the entire response is available. + +## Detailed Explanation of Partial Response Pattern with Real-World Examples + +Real-world example + +> Imagine a restaurant where a customer orders a multi-course meal. Instead of waiting until all courses are prepared before serving, the restaurant brings out each dish as soon as it's ready. This allows the customer to start enjoying the meal without delay, improves the dining experience, and optimizes the kitchen's workflow by letting them prepare and serve courses incrementally. Similarly, in software, the Partial Response design pattern delivers portions of data as they become available, allowing the client to begin processing immediately and improving overall performance and responsiveness. + +In plain words + +> The Partial Response design pattern allows a system to send portions of data to the client as they become available, enabling the client to start processing the data before the complete response is received. + +## Programmatic Example of Partial Response Pattern in Java + +The Partial Response design pattern allows clients to specify which fields of a resource they need. This pattern is useful for reducing the amount of data transferred over the network and allowing clients to start processing data sooner. + +The programmatic example shows a simple video streaming application. + +`Video` class represents a video object with several fields. + +```java +public class Video { + private String id; + private String title; + private String description; + private String url; + + // Getters and setters... +} +``` + +`FieldJsonMapper` utility class converts video objects to JSON, including only the requested fields. Method `mapFields` takes a `Video` object and a set of field names. It creates a JSON object including only the specified fields. The `ObjectMapper` from Jackson library is used to build the JSON object. + +```java +public class FieldJsonMapper { + private static final ObjectMapper mapper = new ObjectMapper(); + + public static ObjectNode mapFields(Video video, Set fields) { + ObjectNode node = mapper.createObjectNode(); + + if (fields.contains("id")) { + node.put("id", video.getId()); + } + if (fields.contains("title")) { + node.put("title", video.getTitle()); + } + if (fields.contains("description")) { + node.put("description", video.getDescription()); + } + if (fields.contains("url")) { + node.put("url", video.getUrl()); + } + + return node; + } +} +``` + +`VideoResource` class handles HTTP requests and returns only the requested fields of the video data. + +- The `VideoResource` class is a RESTful resource handling HTTP GET requests. +- The `getVideo` method fetches a `Video` by its ID and processes the `fields` query parameter. +- It splits the `fields` parameter into a set of field names, uses `FieldJsonMapper` to include only those fields in the response, and returns the partial JSON response. + +```java +@Path("/videos") +public class VideoResource { + @GET + @Path("/{id}") + @Produces(MediaType.APPLICATION_JSON) + public Response getVideo(@PathParam("id") String id, @QueryParam("fields") String fieldsParam) { + Video video = findVideoById(id); // Assume this method fetches the video by ID + + Set fields = new HashSet<>(Arrays.asList(fieldsParam.split(","))); + ObjectNode responseNode = FieldJsonMapper.mapFields(video, fields); + + return Response.ok(responseNode.toString()).build(); + } + + private Video findVideoById(String id) { + // Dummy data for demonstration purposes + Video video = new Video(); + video.setId(id); + video.setTitle("Sample Video"); + video.setDescription("This is a sample video."); + video.setUrl("http://example.com/sample-video"); + + return video; + } +} +``` + +`App` class initializes the web server and registers the `VideoResource`. + +- The `App` class sets up the server using Jersey. +- It registers the `VideoResource` class, which will handle incoming HTTP requests. +- The server listens on `http://localhost:8080/`. + +```java +public class App { + public static void main(String[] args) { + ResourceConfig config = new ResourceConfig(); + config.register(VideoResource.class); + SimpleContainerFactory.create("http://localhost:8080/", config); + } +} +``` + +To summarize, in this example: + +- The `Video` class defines the video data structure. +- The `FieldJsonMapper` class helps create JSON responses including only the requested fields. +- The `VideoResource` class processes client requests, fetching the necessary video data and returning partial responses based on the specified fields. +- The `App` class configures and starts the web server. + +By implementing the Partial Response design pattern, clients can request only the necessary data, enhancing performance and reducing bandwidth usage. + +## When to Use the Partial Response Pattern in Java -## Applicability Use the Partial Response pattern when -* Client needs only subset of data from resource. -* To avoid too much data transfer over wire +* Utilize the Partial Response pattern when dealing with large data sets or APIs that require improved load time and performance. +* When it’s beneficial for the client to begin processing the data as it arrives rather than waiting for the complete response. +* In APIs where different clients might need different subsets of data, allowing them to specify what they need. + +## Real-World Applications of Partial Response Pattern in Java + +This pattern is widely adopted in + +* RESTful APIs allowing clients to specify fields they want using query parameters. +* Streaming large datasets where initial parts of the data can be sent immediately (e.g., video streaming). +* GraphQL queries where clients can request only specific fields to be returned. + +## Benefits and Trade-offs of Partial Response Pattern + +Benefits: + +* Improved Performance: Reduces wait time for the client by allowing it to start processing data as soon as it begins to arrive. +* Resource Optimization: Decreases server load and bandwidth usage by sending only the required data. +* Scalability: Enhances system scalability by handling large datasets more efficiently and reducing the likelihood of timeouts. + +Trade-offs: + +* Complexity: Increases the complexity of both client and server implementations to handle partial responses properly. +* Error Handling: May complicate error handling and recovery if only parts of the data are received correctly. +* State Management: Requires careful management of state, especially if the partial responses are to be processed incrementally. + +## Related Java Design Patterns + +* Asynchronous Messaging: Often used together with asynchronous messaging patterns to handle partial responses without blocking the client. +* [Caching](https://java-design-patterns.com/patterns/caching/): Can be combined with caching patterns to store partial responses and avoid redundant data transfers. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): The proxy can intercept requests and manage partial responses, providing a buffer between the client and server. -## Credits +## References and Credits -* [Common Design Patterns](https://cloud.google.com/apis/design/design_patterns) +* [Building Microservices](https://amzn.to/3UACtrU) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/4dKEwBa) +* [RESTful Web APIs: Services for a Changing World](https://amzn.to/3wG4fu3) diff --git a/partial-response/pom.xml b/partial-response/pom.xml index 20c136598ccb..7b10001cabae 100644 --- a/partial-response/pom.xml +++ b/partial-response/pom.xml @@ -34,9 +34,18 @@ 4.0.0 partial-response + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.mockito mockito-junit-jupiter + 5.16.1 test diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/App.java b/partial-response/src/main/java/com/iluwatar/partialresponse/App.java index 2a729f889c44..a9f1be453676 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/App.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/App.java @@ -34,7 +34,6 @@ * *

{@link VideoResource} act as server to serve video information. */ - @Slf4j public class App { @@ -44,17 +43,22 @@ public class App { * @param args program argument. */ public static void main(String[] args) throws Exception { - var videos = Map.of( - 1, new Video(1, "Avatar", 178, "epic science fiction film", - "James Cameron", "English"), - 2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", - "Hideaki Anno", "Japanese"), - 3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", - "Christopher Nolan", "English") - ); + var videos = + Map.of( + 1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"), + 2, + new Video( + 2, + "Godzilla Resurgence", + 120, + "Action & drama movie|", + "Hideaki Anno", + "Japanese"), + 3, + new Video( + 3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English")); var videoResource = new VideoResource(new FieldJsonMapper(), videos); - LOGGER.info("Retrieving full response from server:-"); LOGGER.info("Get all video information:"); var videoDetails = videoResource.getDetails(1); diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java b/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java index cb153e062fd2..2e4109fdfec1 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java @@ -25,32 +25,28 @@ package com.iluwatar.partialresponse; import java.lang.reflect.Field; +import java.util.StringJoiner; -/** - * Map a video to json. - */ +/** Map a video to json. */ public class FieldJsonMapper { /** * Gets json of required fields from video. * - * @param video object containing video information + * @param video object containing video information * @param fields fields information to get * @return json of required fields from video */ public String toJson(Video video, String[] fields) throws Exception { - var json = new StringBuilder().append("{"); + var json = new StringJoiner(",", "{", "}"); var i = 0; var fieldsLength = fields.length; while (i < fieldsLength) { - json.append(getString(video, Video.class.getDeclaredField(fields[i]))); - if (i != fieldsLength - 1) { - json.append(","); - } + json.add(getString(video, Video.class.getDeclaredField(fields[i]))); i++; } - json.append("}"); + return json.toString(); } diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java b/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java index 28604ca0acf9..36ce69d265ab 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java @@ -25,11 +25,16 @@ package com.iluwatar.partialresponse; /** - * {@link Video} is a entity to serve from server.It contains all video related information. - * Video is a record class. + * {@link Video} is an entity to serve from server.It contains all video related information. Video + * is a record class. */ - -public record Video(Integer id, String title, Integer length, String description, String director, String language) { +public record Video( + Integer id, + String title, + Integer length, + String description, + String director, + String language) { /** * ToString. * @@ -38,12 +43,24 @@ public record Video(Integer id, String title, Integer length, String description @Override public String toString() { return "{" - + "\"id\": " + id + "," - + "\"title\": \"" + title + "\"," - + "\"length\": " + length + "," - + "\"description\": \"" + description + "\"," - + "\"director\": \"" + director + "\"," - + "\"language\": \"" + language + "\"," - + "}"; + + "\"id\": " + + id + + "," + + "\"title\": \"" + + title + + "\"," + + "\"length\": " + + length + + "," + + "\"description\": \"" + + description + + "\"," + + "\"director\": \"" + + director + + "\"," + + "\"language\": \"" + + language + + "\"" + + "}"; } } diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java index 84dd35a3e0d8..6522f143d795 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java @@ -27,18 +27,17 @@ import java.util.Map; /** - * The resource record class which serves video information. This class act as server in the demo. Which - * has all video details. + * The resource record class which serves video information. This class act as server in the demo. + * Which has all video details. * * @param fieldJsonMapper map object to json. - * @param videos initialize resource with existing videos. Act as database. + * @param videos initialize resource with existing videos. Act as database. */ - public record VideoResource(FieldJsonMapper fieldJsonMapper, Map videos) { /** * Get Details. * - * @param id video id + * @param id video id * @param fields fields to get information about * @return full response if no fields specified else partial response for given field. */ diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java index 16f5fff29c94..6d712b820dc6 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java @@ -24,17 +24,14 @@ */ package com.iluwatar.partialresponse; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - Assertions.assertDoesNotThrow(() -> App.main(new String[]{})); + Assertions.assertDoesNotThrow(() -> App.main(new String[] {})); } - -} \ No newline at end of file +} diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java index eb8677130fcc..59e075e6b2b4 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.partialresponse; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -/** - * tests {@link FieldJsonMapper}. - */ +/** tests {@link FieldJsonMapper}. */ class FieldJsonMapperTest { private static FieldJsonMapper mapper; @@ -41,15 +39,14 @@ static void setUp() { @Test void shouldReturnJsonForSpecifiedFieldsInVideo() throws Exception { - var fields = new String[]{"id", "title", "length"}; - var video = new Video( - 2, "Godzilla Resurgence", 120, - "Action & drama movie|", "Hideaki Anno", "Japanese" - ); + var fields = new String[] {"id", "title", "length"}; + var video = + new Video( + 2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese"); var jsonFieldResponse = mapper.toJson(video, fields); var expectedDetails = "{\"id\": 2,\"title\": \"Godzilla Resurgence\",\"length\": 120}"; Assertions.assertEquals(expectedDetails, jsonFieldResponse); } -} \ No newline at end of file +} diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java index b54dc8712565..630f68c02cbc 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java @@ -36,25 +36,29 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -/** - * tests {@link VideoResource}. - */ +/** tests {@link VideoResource}. */ @ExtendWith(MockitoExtension.class) class VideoResourceTest { - @Mock - private static FieldJsonMapper fieldJsonMapper; + @Mock private static FieldJsonMapper fieldJsonMapper; private static VideoResource resource; @BeforeEach void setUp() { - var videos = Map.of( - 1, new Video(1, "Avatar", 178, "epic science fiction film", - "James Cameron", "English"), - 2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", - "Hideaki Anno", "Japanese"), - 3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", - "Christopher Nolan", "English")); + var videos = + Map.of( + 1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"), + 2, + new Video( + 2, + "Godzilla Resurgence", + 120, + "Action & drama movie|", + "Hideaki Anno", + "Japanese"), + 3, + new Video( + 3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English")); resource = new VideoResource(fieldJsonMapper, videos); } @@ -62,14 +66,15 @@ void setUp() { void shouldGiveVideoDetailsById() throws Exception { var actualDetails = resource.getDetails(1); - var expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " - + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\",}"; + var expectedDetails = + "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " + + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\"}"; Assertions.assertEquals(expectedDetails, actualDetails); } @Test void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception { - var fields = new String[]{"id", "title", "length"}; + var fields = new String[] {"id", "title", "length"}; var expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178}"; Mockito.when(fieldJsonMapper.toJson(any(Video.class), eq(fields))).thenReturn(expectedDetails); @@ -78,4 +83,18 @@ void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception { Assertions.assertEquals(expectedDetails, actualFieldsDetails); } -} \ No newline at end of file + + @Test + void shouldAllSpecifiedFieldsInformationOfVideo() throws Exception { + var fields = new String[] {"id", "title", "length", "description", "director", "language"}; + + var expectedDetails = + "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " + + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\"}"; + Mockito.when(fieldJsonMapper.toJson(any(Video.class), eq(fields))).thenReturn(expectedDetails); + + var actualFieldsDetails = resource.getDetails(1, fields); + + Assertions.assertEquals(expectedDetails, actualFieldsDetails); + } +} diff --git a/pipeline/README.md b/pipeline/README.md index 4e5f23a6320c..e05f151e25ac 100644 --- a/pipeline/README.md +++ b/pipeline/README.md @@ -1,46 +1,58 @@ --- -title: Pipeline +title: "Pipeline Pattern in Java: Streamlining Data Processing with Modular Components" +shortTitle: Pipeline +description: "Master the Pipeline design pattern in Java with our comprehensive guide. Learn how to implement data processing in discrete stages for improved code scalability and flexibility. Ideal for developers looking to advance their software engineering skills." category: Behavioral language: en tag: - - Decoupling + - API design + - Data processing + - Decoupling + - Extensibility + - Functional decomposition + - Scalability --- -## Intent +## Also known as -Allows processing of data in a series of stages by giving in an initial input and passing the -processed output to be used by the next stages. +* Chain of Operations +* Processing Pipeline -## Explanation +## Intent of Pipeline Design Pattern -The Pipeline pattern uses ordered stages to process a sequence of input values. Each implemented -task is represented by a stage of the pipeline. You can think of pipelines as similar to assembly -lines in a factory, where each item in the assembly line is constructed in stages. The partially -assembled item is passed from one assembly stage to another. The outputs of the assembly line occur -in the same order as that of the inputs. - -Real world example +The Pipeline design pattern in Java is engineered to facilitate data processing across discrete stages, enhancing modular development and operational efficiency. -> Suppose we wanted to pass through a string to a series of filtering stages and convert it as a -> char array on the last stage. +## Detailed Explanation of Pipeline Pattern with Real-World Examples + +Real-world example + +> A practical example of the Java Pipeline design pattern can be seen in assembly lines, such as those in car manufacturing, illustrating its efficiency and scalability. +> +> In this analogy, the car manufacturing process is divided into several discrete stages, each stage handling a specific part of the car assembly. For example: +> +> 1. **Chassis Assembly:** The base frame of the car is assembled. +> 2. **Engine Installation:** The engine is installed onto the chassis. +> 3. **Painting:** The car is painted. +> 4. **Interior Assembly:** The interior, including seats and dashboard, is installed. +> 5. **Quality Control:** The finished car is inspected for defects. +> +> In the Java Pipeline pattern, each stage functions independently and sequentially, ensuring smooth data flow and easy modifications. The output of one stage (e.g., a partially assembled car) becomes the input for the next stage. This modular approach allows for easy maintenance, scalability (e.g., adding more workers to a stage), and flexibility (e.g., replacing a stage with a more advanced version). Just like in a software pipeline, changes in one stage do not affect the others, facilitating continuous improvements and efficient production. In plain words -> Pipeline pattern is an assembly line where partial results are passed from one stage to another. +> Pipeline pattern is an assembly line where partial results are passed from one stage to another. Wikipedia says -> In software engineering, a pipeline consists of a chain of processing elements (processes, -> threads, coroutines, functions, etc.), arranged so that the output of each element is the input -> of the next; the name is by analogy to a physical pipeline. +> In software engineering, a pipeline consists of a chain of processing elements (processes, threads, coroutines, functions, etc.), arranged so that the output of each element is the input of the next; the name is by analogy to a physical pipeline. -**Programmatic Example** +## Programmatic Example of Pipeline Pattern in Java -The stages of our pipeline are called `Handler`s. +Let's create a string processing pipeline example. The stages of our pipeline are called `Handler`s. ```java interface Handler { - O process(I input); + O process(I input); } ``` @@ -48,15 +60,15 @@ In our string processing example we have 3 different concrete `Handler`s. ```java class RemoveAlphabetsHandler implements Handler { - ... + // ... } class RemoveDigitsHandler implements Handler { - ... + // ... } class ConvertToCharArrayHandler implements Handler { - ... + // ... } ``` @@ -65,56 +77,92 @@ Here is the `Pipeline` that will gather and execute the handlers one by one. ```java class Pipeline { - private final Handler currentHandler; + private final Handler currentHandler; - Pipeline(Handler currentHandler) { - this.currentHandler = currentHandler; - } + Pipeline(Handler currentHandler) { + this.currentHandler = currentHandler; + } - Pipeline addHandler(Handler newHandler) { - return new Pipeline<>(input -> newHandler.process(currentHandler.process(input))); - } + Pipeline addHandler(Handler newHandler) { + return new Pipeline<>(input -> newHandler.process(currentHandler.process(input))); + } - O execute(I input) { - return currentHandler.process(input); - } + O execute(I input) { + return currentHandler.process(input); + } } ``` And here's the `Pipeline` in action processing the string. ```java +public static void main(String[] args) { + LOGGER.info("Creating pipeline"); var filters = new Pipeline<>(new RemoveAlphabetsHandler()) - .addHandler(new RemoveDigitsHandler()) - .addHandler(new ConvertToCharArrayHandler()); - filters.execute("GoYankees123!"); + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); + var input = "GoYankees123!"; + LOGGER.info("Executing pipeline with input: {}", input); + var output = filters.execute(input); + LOGGER.info("Pipeline output: {}", output); +} ``` -## Class diagram +Console output: -![alt text](./etc/pipeline.urm.png "Pipeline pattern class diagram") +``` +07:34:27.069 [main] INFO com.iluwatar.pipeline.App -- Creating pipeline +07:34:27.072 [main] INFO com.iluwatar.pipeline.App -- Executing pipeline with input: GoYankees123! +07:34:27.074 [main] INFO com.iluwatar.pipeline.RemoveAlphabetsHandler -- Current handler: class com.iluwatar.pipeline.RemoveAlphabetsHandler, input is GoYankees123! of type class java.lang.String, output is 123!, of type class java.lang.String +07:34:27.075 [main] INFO com.iluwatar.pipeline.RemoveDigitsHandler -- Current handler: class com.iluwatar.pipeline.RemoveDigitsHandler, input is 123! of type class java.lang.String, output is !, of type class java.lang.String +07:34:27.075 [main] INFO com.iluwatar.pipeline.ConvertToCharArrayHandler -- Current handler: class com.iluwatar.pipeline.ConvertToCharArrayHandler, input is ! of type class java.lang.String, output is [!], of type class [Ljava.lang.Character; +07:34:27.075 [main] INFO com.iluwatar.pipeline.App -- Pipeline output: [!] +``` -## Applicability +## When to Use the Pipeline Pattern in Java Use the Pipeline pattern when you want to -* Execute individual stages that yields a final value. -* Add readability to complex sequence of operations by providing a fluent builder as an interface. -* Improve testability of code since stages will most likely be doing a single thing, complying to -the [Single Responsibility Principle (SRP)](https://java-design-patterns.com/principles/#single-responsibility-principle) +* When you need to process data in a sequence of stages. +* When each stage of processing is independent and can be easily replaced or reordered. +* When you want to improve the scalability and maintainability of data processing code. + +## Pipeline Pattern Java Tutorials + +* [The Pipeline design pattern (in Java) (Medium)](https://medium.com/@deepakbapat/the-pipeline-design-pattern-in-java-831d9ce2fe21) +* [The Pipeline Pattern — for fun and profit (Aaron Weatherall)](https://medium.com/@aaronweatherall/the-pipeline-pattern-for-fun-and-profit-9b5f43a98130) +* [Pipelines (Microsoft)](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff963548(v=pandp.10)) + +## Real-World Applications of Pipeline Pattern in Java + +* Data transformation and ETL (Extract, Transform, Load) processes. +* Compilers for processing source code through various stages such as lexical analysis, syntax analysis, semantic analysis, and code generation. +* Image processing applications where multiple filters are applied sequentially. +* Logging frameworks where messages pass through multiple handlers for formatting, filtering, and output. + +## Benefits and Trade-offs of Pipeline Pattern + +Benefits: + +* Decoupling: Each stage of the pipeline is a separate component, making the system more modular and easier to maintain. +* Reusability: Individual stages can be reused in different pipelines. +* Extensibility: New stages can be added without modifying existing ones. +* Scalability: Pipelines can be parallelized by running different stages on different processors or threads. -## Known uses +Trade-offs: -* [java.util.Stream](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) -* [Maven Build Lifecycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html) -* [Functional Java](https://github.com/functionaljava/functionaljava) +* Complexity: Managing the flow of data through multiple stages can introduce complexity. +* Performance Overhead: Each stage introduces some performance overhead due to context switching and data transfer between stages. +* Debugging Difficulty: Debugging pipelines can be more challenging since the data flows through multiple components. -## Related patterns +## Related Java Design Patterns -* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/) +* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Both patterns involve passing data through a series of handlers, but in Chain of Responsibility, handlers can decide not to pass the data further. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Both patterns involve adding behavior dynamically, but Decorator wraps additional behavior around objects, whereas Pipeline processes data in discrete steps. +* [Composite](https://java-design-patterns.com/patterns/composite/): Like Pipeline, Composite also involves hierarchical processing, but Composite is more about part-whole hierarchies. -## Credits +## References and Credits -* [The Pipeline Pattern — for fun and profit](https://medium.com/@aaronweatherall/the-pipeline-pattern-for-fun-and-profit-9b5f43a98130) -* [The Pipeline design pattern (in Java)](https://medium.com/@deepakbapat/the-pipeline-design-pattern-in-java-831d9ce2fe21) -* [Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff963548(v=pandp.10)) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/pipeline/pom.xml b/pipeline/pom.xml index a45118dd8f62..f28889c47aa0 100644 --- a/pipeline/pom.xml +++ b/pipeline/pom.xml @@ -34,6 +34,14 @@ pipeline + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/App.java b/pipeline/src/main/java/com/iluwatar/pipeline/App.java index 5f6dbd142926..9f82a6e9eb3a 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/App.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/App.java @@ -24,6 +24,8 @@ */ package com.iluwatar.pipeline; +import lombok.extern.slf4j.Slf4j; + /** * The Pipeline pattern uses ordered stages to process a sequence of input values. Each implemented * task is represented by a stage of the pipeline. You can think of pipelines as similar to assembly @@ -34,6 +36,7 @@ *

Classes used in this example are suffixed with "Handlers", and synonymously refers to the * "stage". */ +@Slf4j public class App { /** * Specify the initial input type for the first stage handler and the expected output type of the @@ -42,27 +45,32 @@ public class App { */ public static void main(String[] args) { /* - Suppose we wanted to pass through a String to a series of filtering stages and convert it - as a char array on the last stage. + Suppose we wanted to pass through a String to a series of filtering stages and convert it + as a char array on the last stage. - - Stage handler 1 (pipe): Removing the alphabets, accepts a String input and returns the - processed String output. This will be used by the next handler as its input. + - Stage handler 1 (pipe): Removing the alphabets, accepts a String input and returns the + processed String output. This will be used by the next handler as its input. - - Stage handler 2 (pipe): Removing the digits, accepts a String input and returns the - processed String output. This shall also be used by the last handler we have. + - Stage handler 2 (pipe): Removing the digits, accepts a String input and returns the + processed String output. This shall also be used by the last handler we have. - - Stage handler 3 (pipe): Converting the String input to a char array handler. We would - be returning a different type in here since that is what's specified by the requirement. - This means that at any stages along the pipeline, the handler can return any type of data - as long as it fulfills the requirements for the next handler's input. + - Stage handler 3 (pipe): Converting the String input to a char array handler. We would + be returning a different type in here since that is what's specified by the requirement. + This means that at any stages along the pipeline, the handler can return any type of data + as long as it fulfills the requirements for the next handler's input. - Suppose we wanted to add another handler after ConvertToCharArrayHandler. That handler - then is expected to receive an input of char[] array since that is the type being returned - by the previous handler, ConvertToCharArrayHandler. - */ - var filters = new Pipeline<>(new RemoveAlphabetsHandler()) - .addHandler(new RemoveDigitsHandler()) - .addHandler(new ConvertToCharArrayHandler()); - filters.execute("GoYankees123!"); + Suppose we wanted to add another handler after ConvertToCharArrayHandler. That handler + then is expected to receive an input of char[] array since that is the type being returned + by the previous handler, ConvertToCharArrayHandler. + */ + LOGGER.info("Creating pipeline"); + var filters = + new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); + var input = "GoYankees123!"; + LOGGER.info("Executing pipeline with input: {}", input); + var output = filters.execute(input); + LOGGER.info("Pipeline output: {}", output); } } diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java index 24393333f1cd..f7edde565c54 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java @@ -28,9 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Stage handler that converts an input String to its char[] array counterpart. - */ +/** Stage handler that converts an input String to its char[] array counterpart. */ class ConvertToCharArrayHandler implements Handler { private static final Logger LOGGER = LoggerFactory.getLogger(ConvertToCharArrayHandler.class); @@ -40,9 +38,9 @@ public char[] process(String input) { var characters = input.toCharArray(); var string = Arrays.toString(characters); LOGGER.info( - String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s", - ConvertToCharArrayHandler.class, input, String.class, string, Character[].class) - ); + String.format( + "Current handler: %s, input is %s of type %s, output is %s, of type %s", + ConvertToCharArrayHandler.class, input, String.class, string, Character[].class)); return characters; } diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java b/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java index 1e72f863c186..a27e0592c27c 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java @@ -33,4 +33,4 @@ */ interface Handler { O process(I input); -} \ No newline at end of file +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java b/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java index 6c030e513d05..b7a619692a74 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java @@ -46,4 +46,4 @@ Pipeline addHandler(Handler newHandler) { O execute(I input) { return currentHandler.process(input); } -} \ No newline at end of file +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java index 85a23e69065c..3bf01bf8a9a4 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java @@ -40,7 +40,8 @@ class RemoveAlphabetsHandler implements Handler { public String process(String input) { var inputWithoutAlphabets = new StringBuilder(); var isAlphabetic = (IntPredicate) Character::isAlphabetic; - input.chars() + input + .chars() .filter(isAlphabetic.negate()) .mapToObj(x -> (char) x) .forEachOrdered(inputWithoutAlphabets::append); @@ -49,11 +50,12 @@ public String process(String input) { LOGGER.info( String.format( "Current handler: %s, input is %s of type %s, output is %s, of type %s", - RemoveAlphabetsHandler.class, input, - String.class, inputWithoutAlphabetsStr, String.class - ) - ); + RemoveAlphabetsHandler.class, + input, + String.class, + inputWithoutAlphabetsStr, + String.class)); return inputWithoutAlphabetsStr; } -} \ No newline at end of file +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java index 75e7a460ef87..e84b1693ad64 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java @@ -40,7 +40,8 @@ class RemoveDigitsHandler implements Handler { public String process(String input) { var inputWithoutDigits = new StringBuilder(); var isDigit = (IntPredicate) Character::isDigit; - input.chars() + input + .chars() .filter(isDigit.negate()) .mapToObj(x -> (char) x) .forEachOrdered(inputWithoutDigits::append); @@ -49,10 +50,8 @@ public String process(String input) { LOGGER.info( String.format( "Current handler: %s, input is %s of type %s, output is %s, of type %s", - RemoveDigitsHandler.class, input, String.class, inputWithoutDigitsStr, String.class - ) - ); + RemoveDigitsHandler.class, input, String.class, inputWithoutDigitsStr, String.class)); return inputWithoutDigitsStr; } -} \ No newline at end of file +} diff --git a/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java index 2a49dbe0b5ba..5f1c0d2311b5 100644 --- a/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java +++ b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.pipeline; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Test - */ +import org.junit.jupiter.api.Test; + +/** Application Test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java b/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java index 677c8eb50495..d26069dc7fb8 100644 --- a/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java +++ b/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java @@ -28,20 +28,17 @@ import org.junit.jupiter.api.Test; -/** - * Test for {@link Pipeline} - */ +/** Test for {@link Pipeline} */ class PipelineTest { @Test void testAddHandlersToPipeline() { - var filters = new Pipeline<>(new RemoveAlphabetsHandler()) - .addHandler(new RemoveDigitsHandler()) - .addHandler(new ConvertToCharArrayHandler()); + var filters = + new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); assertArrayEquals( - new char[]{'#', '!', '(', '&', '%', '#', '!'}, - filters.execute("#H!E(L&L0O%THE3R#34E!") - ); + new char[] {'#', '!', '(', '&', '%', '#', '!'}, filters.execute("#H!E(L&L0O%THE3R#34E!")); } } diff --git a/poison-pill/README.md b/poison-pill/README.md index 287ce1111df6..31921326e347 100644 --- a/poison-pill/README.md +++ b/poison-pill/README.md @@ -1,38 +1,44 @@ --- -title: Poison Pill -category: Behavioral +title: "Poison Pill Pattern in Java: Gracefully Terminating Multithreaded Processes" +shortTitle: Poison Pill +description: "Explore the Poison Pill design pattern in Java, used for gracefully shutting down multi-threaded applications. Understand its intent, applicability, and see a real-world example. Perfect for developers looking to enhance concurrency and messaging systems." +category: Concurrency language: en tag: - - Cloud distributed - - Reactive + - Decoupling + - Fault tolerance + - Messaging + - Thread management --- -## Intent +## Also known as -Poison Pill is known predefined data item that allows to provide graceful shutdown for separate -distributed consumption process. +* Shutdown Signal -## Explanation +## Intent of Poison Pill Design Pattern -Real world example +The Poison Pill design pattern is used to gracefully shut down a service or a producer-consumer system by sending a special message (the "poison pill") to message queue which indicates that no more messages will be sent, allowing the consumers to terminate. -> Let's think about a message queue with one producer and one consumer. The producer keeps pushing -> new messages in the queue and the consumer keeps reading them. Finally when it's time to -> gracefully shut down the producer sends the poison pill message. +## Detailed Explanation of Poison Pill Pattern with Real-World Examples + +Real-world example + +> A real-world analogy for the Poison Pill design pattern is the use of a "closed" sign in a retail store. When the store is ready to close for the day, the manager places a "closed" sign on the door. This sign acts as a signal to any new customers that no more customers will be admitted, but it doesn't immediately force out the customers already inside. The store staff will then attend to the remaining customers, allowing them to complete their purchases before finally locking up and turning off the lights. Similarly, in the Poison Pill pattern, a special "poison pill" message signals consumers to stop accepting new tasks while allowing them to finish processing the current tasks before shutting down gracefully. In plain words > Poison Pill is a known message structure that ends the message exchange. -**Programmatic Example** +## Programmatic Example of Poison Pill Pattern in Java + +In this Java example, the Poison Pill serves as a shutdown signal within message queues, demonstrating effective thread management and consumer communication. -Let's define the message structure first. There's interface `Message` and implementation -`SimpleMessage`. +Let's define the message structure first. There's interface `Message` and implementation `SimpleMessage`. ```java public interface Message { - - ... + + // Other properties and methods... enum Headers { DATE, SENDER @@ -81,23 +87,18 @@ public class SimpleMessage implements Message { } ``` -To pass messages we are using message queues. Here we define the types related to the message queue: -`MqPublishPoint`, `MqSubscribePoint` and `MessageQueue`. `SimpleMessageQueue` implements all these -interfaces. +To pass messages we are using message queues. Here we define the types related to the message queue: `MqPublishPoint`, `MqSubscribePoint` and `MessageQueue`. `SimpleMessageQueue` implements all these interfaces. ```java public interface MqPublishPoint { - void put(Message msg) throws InterruptedException; } public interface MqSubscribePoint { - Message take() throws InterruptedException; } -public interface MessageQueue extends MqPublishPoint, MqSubscribePoint { -} +public interface MessageQueue extends MqPublishPoint, MqSubscribePoint {} public class SimpleMessageQueue implements MessageQueue { @@ -119,14 +120,12 @@ public class SimpleMessageQueue implements MessageQueue { } ``` -Next we need message `Producer` and `Consumer`. Internally they use the message queues from above. -It's important to notice that when `Producer` stops, it sends out the poison pill to inform -`Consumer` that the messaging has finished. +Next, we need message `Producer` and `Consumer`. Internally they use the message queues from above. It's important to notice that when `Producer` stops, it sends out the poison pill to inform `Consumer` that the messaging has finished. ```java public class Producer { - - ... + + // Other properties and methods... public void send(String body) { if (isStopped) { @@ -159,7 +158,7 @@ public class Producer { public class Consumer { - ... + // Other properties and methods... public void consume() { while (true) { @@ -182,9 +181,10 @@ public class Consumer { } ``` -Finally we are ready to present the whole example in action. +Finally, we are ready to present the whole example in action. ```java + public static void main(String[] args) { var queue = new SimpleMessageQueue(10000); final var producer = new Producer("PRODUCER_1", queue); @@ -193,32 +193,62 @@ Finally we are ready to present the whole example in action. new Thread(consumer::consume).start(); new Thread(() -> { - producer.send("hand shake"); - producer.send("some very important information"); - producer.send("bye!"); - producer.stop(); + producer.send("hand shake"); + producer.send("some very important information"); + producer.send("bye!"); + producer.stop(); }).start(); +} ``` Program output: ``` -Message [hand shake] from [PRODUCER_1] received by [CONSUMER_1] -Message [some very important information] from [PRODUCER_1] received by [CONSUMER_1] -Message [bye!] from [PRODUCER_1] received by [CONSUMER_1] -Consumer CONSUMER_1 receive request to terminate. +07:43:01.518 [Thread-0] INFO com.iluwatar.poison.pill.Consumer -- Message [hand shake] from [PRODUCER_1] received by [CONSUMER_1] +07:43:01.520 [Thread-0] INFO com.iluwatar.poison.pill.Consumer -- Message [some very important information] from [PRODUCER_1] received by [CONSUMER_1] +07:43:01.520 [Thread-0] INFO com.iluwatar.poison.pill.Consumer -- Message [bye!] from [PRODUCER_1] received by [CONSUMER_1] +07:43:01.520 [Thread-0] INFO com.iluwatar.poison.pill.Consumer -- Consumer CONSUMER_1 receive request to terminate. ``` -## Class diagram +## Detailed Explanation of Poison Pill Pattern with Real-World Examples -![alt text](./etc/poison-pill.png "Poison Pill") +![Poison Pill](./etc/poison-pill.png "Poison Pill") -## Applicability +## When to Use the Poison Pill Pattern in Java Use the Poison Pill idiom when: -* There's a need to send signal from one thread/process to another to terminate. +* Systems require robust fault tolerance and seamless consumer shutdown in multithreaded environments. +* In producer-consumer scenarios where consumers need to be informed about the end of message processing. +* To ensure that consumers can finish processing remaining messages before shutting down. + +## Real-World Applications of Poison Pill Pattern in Java + +* Java ExecutorService shutdown using a special task to signal shutdown. +* Messaging systems where a specific message indicates the end of the queue processing. +* [Akka framework](https://doc.akka.io/japi/akka/2.5/akka/actor/typed/internal/PoisonPill.html) + +## Benefits and Trade-offs of Poison Pill Pattern + +Benefits: + +* Simplifies the shutdown process of consumers. +* Ensures that all pending tasks are completed before termination. +* Decouples the shutdown logic from the main processing logic. + +Trade-offs: + +* Requires consumers to check for the poison pill, adding some overhead. +* If not managed properly, could lead to consumers not recognizing the poison pill, causing indefinite blocking. + +## Related Java Design Patterns + +* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Works in tandem with the Poison Pill pattern to handle the communication and shutdown of consumers. +* Message Queue: Often uses poison pills to signal the end of message processing in the queue. +* [Observer](https://java-design-patterns.com/patterns/observer/): Can be used to notify subscribers about the shutdown event. -## Real world examples +## References and Credits -* [akka.actor.PoisonPill](http://doc.akka.io/docs/akka/2.1.4/java/untyped-actors.html) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Effective Java](https://amzn.to/4cGk2Jz) diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index 803c9fd755dc..74f4ce8a9a3d 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -34,6 +34,14 @@ poison-pill + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java index 0dd2b2e45e66..222bfdbb59b4 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java @@ -50,11 +50,13 @@ public static void main(String[] args) { new Thread(consumer::consume).start(); - new Thread(() -> { - producer.send("hand shake"); - producer.send("some very important information"); - producer.send("bye!"); - producer.stop(); - }).start(); + new Thread( + () -> { + producer.send("hand shake"); + producer.send("some very important information"); + producer.send("bye!"); + producer.stop(); + }) + .start(); } } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java index 8fc05f8ab5b5..c6c24af95d42 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java @@ -27,9 +27,7 @@ import com.iluwatar.poison.pill.Message.Headers; import lombok.extern.slf4j.Slf4j; -/** - * Class responsible for receiving and handling submitted to the queue messages. - */ +/** Class responsible for receiving and handling submitted to the queue messages. */ @Slf4j public class Consumer { @@ -41,9 +39,7 @@ public Consumer(String name, MqSubscribePoint queue) { this.queue = queue; } - /** - * Consume message. - */ + /** Consume message. */ public void consume() { while (true) { try { diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java index 09cb20a6c9d3..ab7dbdf31ed7 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java @@ -32,44 +32,43 @@ */ public interface Message { - Message POISON_PILL = new Message() { - - @Override - public void addHeader(Headers header, String value) { - throw poison(); - } - - @Override - public String getHeader(Headers header) { - throw poison(); - } - - @Override - public Map getHeaders() { - throw poison(); - } - - @Override - public void setBody(String body) { - throw poison(); - } - - @Override - public String getBody() { - throw poison(); - } - - private RuntimeException poison() { - return new UnsupportedOperationException("Poison"); - } - - }; - - /** - * Enumeration of Type of Headers. - */ + Message POISON_PILL = + new Message() { + + @Override + public void addHeader(Headers header, String value) { + throw poison(); + } + + @Override + public String getHeader(Headers header) { + throw poison(); + } + + @Override + public Map getHeaders() { + throw poison(); + } + + @Override + public void setBody(String body) { + throw poison(); + } + + @Override + public String getBody() { + throw poison(); + } + + private RuntimeException poison() { + return new UnsupportedOperationException("Poison"); + } + }; + + /** Enumeration of Type of Headers. */ enum Headers { - DATE, SENDER + DATE, + SENDER } void addHeader(Headers header, String value); diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java index 9477a80b2df8..91584b5171a8 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java @@ -27,6 +27,4 @@ /** * Represents abstraction of channel (or pipe) that bounds {@link Producer} and {@link Consumer}. */ -public interface MessageQueue extends MqPublishPoint, MqSubscribePoint { - -} +public interface MessageQueue extends MqPublishPoint, MqSubscribePoint {} diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java index f4fdac9441ca..a4c986d76f7b 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java @@ -24,9 +24,7 @@ */ package com.iluwatar.poison.pill; -/** - * Endpoint to publish {@link Message} to queue. - */ +/** Endpoint to publish {@link Message} to queue. */ public interface MqPublishPoint { void put(Message msg) throws InterruptedException; diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java index a89c6fb82113..d0521cc7e8fd 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java @@ -24,9 +24,7 @@ */ package com.iluwatar.poison.pill; -/** - * Endpoint to retrieve {@link Message} from queue. - */ +/** Endpoint to retrieve {@link Message} from queue. */ public interface MqSubscribePoint { Message take() throws InterruptedException; diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java index 23e703db6210..1d9966139772 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java @@ -39,22 +39,19 @@ public class Producer { private final String name; private boolean isStopped; - /** - * Constructor. - */ + /** Constructor. */ public Producer(String name, MqPublishPoint queue) { this.name = name; this.queue = queue; this.isStopped = false; } - /** - * Send message to queue. - */ + /** Send message to queue. */ public void send(String body) { if (isStopped) { - throw new IllegalStateException(String.format( - "Producer %s was stopped and fail to deliver requested message [%s].", body, name)); + throw new IllegalStateException( + String.format( + "Producer %s was stopped and fail to deliver requested message [%s].", body, name)); } var msg = new SimpleMessage(); msg.addHeader(Headers.DATE, new Date().toString()); @@ -69,9 +66,7 @@ public void send(String body) { } } - /** - * Stop system by sending poison pill. - */ + /** Stop system by sending poison pill. */ public void stop() { isStopped = true; try { diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java index 013b0e5d4d0d..c8efb7ec2fcb 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java @@ -28,9 +28,7 @@ import java.util.HashMap; import java.util.Map; -/** - * {@link Message} basic implementation. - */ +/** {@link Message} basic implementation. */ public class SimpleMessage implements Message { private final Map headers = new HashMap<>(); diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java index 288664c7fa23..32fc48a2a1c7 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java @@ -27,9 +27,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -/** - * Bounded blocking queue wrapper. - */ +/** Bounded blocking queue wrapper. */ public class SimpleMessageQueue implements MessageQueue { private final BlockingQueue queue; diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java index b121663829e2..a4c2844fe42b 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.poison.pill; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java index 2ac70899042e..3d03a5043520 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java @@ -37,11 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/27/15 - 9:45 PM - * - * @author Jeroen Meulemeester - */ +/** ConsumerTest */ class ConsumerTest { private InMemoryAppender appender; @@ -58,12 +54,12 @@ void tearDown() { @Test void testConsume() throws Exception { - final var messages = List.of( - createMessage("you", "Hello!"), - createMessage("me", "Hi!"), - Message.POISON_PILL, - createMessage("late_for_the_party", "Hello? Anyone here?") - ); + final var messages = + List.of( + createMessage("you", "Hello!"), + createMessage("me", "Hi!"), + Message.POISON_PILL, + createMessage("late_for_the_party", "Hello? Anyone here?")); final var queue = new SimpleMessageQueue(messages.size()); for (final var message : messages) { @@ -80,7 +76,7 @@ void testConsume() throws Exception { /** * Create a new message from the given sender with the given message body * - * @param sender The sender's name + * @param sender The sender's name * @param message The message body * @return The message instance */ @@ -92,7 +88,7 @@ private static Message createMessage(final String sender, final String message) return msg; } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { @@ -109,5 +105,4 @@ public boolean logContains(String message) { return log.stream().map(ILoggingEvent::getFormattedMessage).anyMatch(message::equals); } } - } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java index e653cadb452c..40688f8c386d 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java @@ -30,25 +30,18 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 10:30 PM - * - * @author Jeroen Meulemeester - */ +/** PoisonMessageTest */ class PoisonMessageTest { @Test void testAddHeader() { - assertThrows(UnsupportedOperationException.class, () -> { - POISON_PILL.addHeader(Headers.SENDER, "sender"); - }); + assertThrows( + UnsupportedOperationException.class, () -> POISON_PILL.addHeader(Headers.SENDER, "sender")); } @Test void testGetHeader() { - assertThrows(UnsupportedOperationException.class, () -> { - POISON_PILL.getHeader(Headers.SENDER); - }); + assertThrows(UnsupportedOperationException.class, () -> POISON_PILL.getHeader(Headers.SENDER)); } @Test @@ -58,14 +51,11 @@ void testGetHeaders() { @Test void testSetBody() { - assertThrows(UnsupportedOperationException.class, () -> { - POISON_PILL.setBody("Test message."); - }); + assertThrows(UnsupportedOperationException.class, () -> POISON_PILL.setBody("Test message.")); } @Test void testGetBody() { assertThrows(UnsupportedOperationException.class, POISON_PILL::getBody); } - } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java index dcfc182f4903..3d5d50f70dca 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java @@ -35,11 +35,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -/** - * Date: 12/27/15 - 10:32 PM - * - * @author Jeroen Meulemeester - */ +/** ProducerTest */ class ProducerTest { @Test @@ -77,11 +73,11 @@ void testStop() throws Exception { } catch (IllegalStateException e) { assertNotNull(e); assertNotNull(e.getMessage()); - assertEquals("Producer Hello! was stopped and fail to deliver requested message [producer].", + assertEquals( + "Producer Hello! was stopped and fail to deliver requested message [producer].", e.getMessage()); } verifyNoMoreInteractions(publishPoint); } - } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java index 27aadba91137..77c547d617da 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java @@ -32,11 +32,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 10:25 PM - * - * @author Jeroen Meulemeester - */ +/** SimpleMessageTest */ class SimpleMessageTest { @Test @@ -56,10 +52,7 @@ void testGetHeaders() { void testUnModifiableHeaders() { final var message = new SimpleMessage(); final var headers = message.getHeaders(); - assertThrows(UnsupportedOperationException.class, () -> { - headers.put(Message.Headers.SENDER, "test"); - }); + assertThrows( + UnsupportedOperationException.class, () -> headers.put(Message.Headers.SENDER, "test")); } - - -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index 44178fd1af54..4f5c62663d78 100644 --- a/pom.xml +++ b/pom.xml @@ -35,20 +35,35 @@ Java Design Patterns Java Design Patterns + UTF-8 - 3.11.0.3922 - 2.7.5 - 0.8.11 + + + 3.4.4 + 5.11.4 + 5.14.2 + 1.5.18 + 2.0.17 + + + 0.8.12 1.4 - 2.70.0 - 2.10.1 - 5.1.0 + 4.7.0 + 2.11.0 + 6.0.0 1.1.0 - 3.2.5 - 3.3.0 - 4.3 - 2.1.1 + 1.18.36 + 5.4.0 + 5.4.0 + 2.3.232 + + + 3.5.2 + 4.6 + 3.14.0 + + 5.0.0.4389 https://sonarcloud.io iluwatar iluwatar_java-design-patterns @@ -56,165 +71,177 @@ Java Design Patterns + abstract-document abstract-factory - collecting-parameter - monitor - builder - factory-method - prototype - singleton + active-object + acyclic-visitor adapter + ambassador + anti-corruption-layer + arrange-act-assert + async-method-invocation + balking + bloc bridge + builder + business-delegate + bytecode + caching + callback + chain-of-responsibility + circuit-breaker + clean-architecture + client-session + collecting-parameter + collection-pipeline + combinator + command + command-query-responsibility-segregation + commander + component composite - dao + composite-entity + composite-view + context-object + converter + curiously-recurring-template-pattern + currying + data-access-object + data-bus + data-locality data-mapper + data-transfer-object decorator + delegation + dependency-injection + dirty-flag + domain-model + double-buffer + double-checked-locking + double-dispatch + dynamic-proxy + event-aggregator + event-based-asynchronous + event-driven-architecture + event-queue + event-sourcing + execute-around + extension-objects facade + factory + factory-kit + factory-method + fanout-fanin + feature-toggle + filterer + fluent-interface + flux flyweight - proxy - chain-of-responsibility - command + front-controller + function-composition + game-loop + gateway + guarded-suspension + half-sync-half-async + health-check + hexagonal-architecture + identity-map + intercepting-filter interpreter iterator + layered-architecture + lazy-loading + leader-election + leader-followers + lockable-object + map-reduce + marker-interface + master-worker mediator memento + metadata-mapping + microservices-aggregrator + microservices-api-gateway + microservices-client-side-ui-composition + microservices-distributed-tracing + microservices-idempotent-consumer + microservices-log-aggregation + model-view-controller + model-view-intent model-view-presenter - observer - state - strategy - template-method - version-number - visitor - double-checked-locking - servant - service-locator + model-view-viewmodel + monad + money + monitor + monolithic-architecture + monostate + multiton + mute-idiom + notification null-object - event-aggregator - callback - execute-around - property - intercepting-filter - producer-consumer + object-mother + object-pool + observer + optimistic-offline-lock + page-controller + page-object + parameter-object + partial-response pipeline poison-pill - reader-writer-lock - lazy-loading - service-layer - specification - tolerant-reader - model-view-controller - flux - double-dispatch - multiton - resource-acquisition-is-initialization - thread-pool - twin + presentation-model private-class-data - object-pool - dependency-injection - front-controller - repository - async-method-invocation - monostate - step-builder - business-delegate - half-sync-half-async - layers - fluentinterface - reactor - caching - delegation - event-driven-architecture - api-gateway - factory-kit - feature-toggle - value-object - module - monad - mute-idiom - hexagonal - abstract-document - aggregator-microservices + producer-consumer promise - page-object - event-asynchronous - event-queue - queue-load-leveling - object-mother - data-bus - converter - guarded-suspension - balking - extension-objects - marker - cqrs - event-sourcing - data-transfer-object - throttling - unit-of-work - partial-response + property + prototype + proxy + publish-subscribe + queue-based-load-leveling + reactor + registry + repository + resource-acquisition-is-initialization retry - dirty-flag - trampoline - ambassador - acyclic-visitor - collection-pipeline - master-worker-pattern - spatial-partition - priority-queue - commander - typeobjectpattern - bytecode - leader-election - data-locality - subclass-sandbox - circuit-breaker role-object saga - double-buffer - sharding - game-loop - combinator - update-method - leader-followers - strangler - arrange-act-assert - transaction-script - registry - filterer - factory separated-interface - special-case - parameter-object - active-object - model-view-viewmodel - composite-entity - table-module - presentation-model - lockable-object - fanout-fanin - domain-model - composite-view - metadata-mapping - service-to-worker - client-session - model-view-intent - embedded-value - currying serialized-entity - identity-map - component - context-object - thread-local-storage - optimistic-offline-lock - crtp - log-aggregation - anti-corruption-layer - health-check - notification + serialized-lob + servant + server-session + service-layer + service-locator + service-stub + service-to-worker + session-facade + sharding single-table-inheritance - dynamic-proxy - gateway + singleton + spatial-partition + special-case + specification + state + step-builder + strangler + strategy + subclass-sandbox + table-inheritance + table-module + template-method + templateview + throttling + tolerant-reader + trampoline + transaction-script + twin + type-object + unit-of-work + update-method + value-object + version-number + virtual-proxy + visitor @@ -226,10 +253,29 @@ org.springframework.boot - spring-boot-dependencies + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-test + test ${spring-boot.version} - pom - import commons-dbcp @@ -237,7 +283,7 @@ ${commons-dbcp.version} - net.sourceforge.htmlunit + org.htmlunit htmlunit ${htmlunit.version} @@ -257,24 +303,67 @@ ${system-lambda.version} test + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + ${junit.version} + test + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + org.mockito + mockito-core + ${mockito.version} + + + org.mongodb + bson + ${bson.version} + + + org.mongodb + mongodb-driver-legacy + ${mongo.version} + + + com.h2database + h2 + ${h2.version} + - - org.slf4j - slf4j-api - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-core - org.projectlombok lombok + ${lombok.version} provided @@ -284,9 +373,17 @@ org.apache.maven.plugins maven-compiler-plugin + ${maven-compiler-plugin.version} - 17 - 17 + 21 + 21 + + + org.projectlombok + lombok + ${lombok.version} + + @@ -312,9 +409,8 @@ jar-with-dependencies - - ${project.artifactId} + + ${project.artifactId}-${project.version} false @@ -328,28 +424,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - ${maven-checkstyle-plugin.version} - - - validate - - check - - validate - - google_checks.xml - checkstyle-suppressions.xml - - true - warning - false - - - - com.mycila license-maven-plugin @@ -404,27 +478,24 @@ - com.iluwatar.urm - urm-maven-plugin - ${urm-maven-plugin.version} - - - ${project.basedir}/etc - - com.iluwatar - - true - false - plantuml - + com.diffplug.spotless + spotless-maven-plugin + 2.44.3 - process-classes - map + check + apply + + + + 1.17.0 + + + diff --git a/presentation-model/README.md b/presentation-model/README.md index f1367d0fc4dc..9b0eb6659451 100644 --- a/presentation-model/README.md +++ b/presentation-model/README.md @@ -1,192 +1,152 @@ --- -title: Presentation Model -category: Behavioral +title: "Presentation Model Pattern in Java: Enhancing UI Design with Robust Data Management" +shortTitle: Presentation Model +description: "Explore the Presentation Model Pattern at Java Design Patterns. Learn how it separates UI from business logic to enhance flexibility, maintainability, and testability. Ideal for Java developers interested in robust design solutions." +category: Architectural language: en tag: - - Decoupling + - Decoupling + - Encapsulation + - Presentation + - Testing --- ## Also known as -Application Model -## Intent -Presentation Model pulls the state and behavior of the view out into a model class that is part of the presentation. +* Application Model -## Explanation +## Intent of Presentation Model Design Pattern -Real world example +The Presentation Model pattern separates the logic of the user interface (UI) from the business logic by creating a model that represents the data and behavior of the UI independently. -> When we need to write a program with GUI, there is no need for us to put all presentation behavior in the view class. Because it will test become harder. So we can use Presentation Model Pattern to separate the behavior and view. The view only need to load the data and states from other class and show these data on the screen according to the states. +## Detailed Explanation of Presentation Model Pattern with Real-World Examples + +Real-world example + +> An analogous real-world example of the Presentation Model design pattern is the relationship between a scriptwriter, an actor, and a director in a theater production. The scriptwriter creates the script (analogous to the business logic), which the actor then interprets and performs on stage (analogous to the user interface). The director acts as the intermediary, ensuring that the actor's performance aligns with the script and the vision of the play (similar to the Presentation Model coordinating the UI and the business logic). This separation allows the script to be rewritten without changing the actor's techniques or the director's interpretation, ensuring flexibility and maintainability. In plain words -> a pattern that used to divide the presentation and controlling. +> The Presentation Model design pattern separates the UI logic from the business logic by creating an intermediate model that represents the data and behavior of the UI independently, enhancing testability, maintainability, and flexibility. -Code Example +Architecture diagram -Class `view` is the GUI of albums. Methods `saveToPMod` and `loadFromPMod` are used to achieve synchronization. +![Presentation Model Architecture Diagram](./etc/presentation-model-architecture-diagram.png) -```java -public class View { - /** - * the model that controls this view. - */ - private final PresentationModel model; - - private TextField txtTitle; - private TextField txtArtist; - private JCheckBox chkClassical; - private TextField txtComposer; - private JList albumList; - private JButton apply; - private JButton cancel; - - public View() { - model = new PresentationModel(PresentationModel.albumDataSet()); - } - - /** - * save the data to PresentationModel. - */ - public void saveToPMod() { - LOGGER.info("Save data to PresentationModel"); - model.setArtist(txtArtist.getText()); - model.setTitle(txtTitle.getText()); - model.setIsClassical(chkClassical.isSelected()); - model.setComposer(txtComposer.getText()); - } - - /** - * load the data from PresentationModel. - */ - public void loadFromPMod() { - LOGGER.info("Load data from PresentationModel"); - txtArtist.setText(model.getArtist()); - txtTitle.setText(model.getTitle()); - chkClassical.setSelected(model.getIsClassical()); - txtComposer.setEditable(model.getIsClassical()); - txtComposer.setText(model.getComposer()); - } - - public void createView() { - // the detail of GUI information like size, listenser and so on. - } -} -``` -Class `Album` is to store information of a album. +## Programmatic Example of Presentation Model Pattern in Java + +The Presentation Model design pattern is a pattern that separates the responsibility of managing the state and behavior of the GUI in a separate model class. This model class is not tied to the view and can be used to test the GUI behavior independently of the GUI itself. + +Let's take a look at the code provided and see how it implements the Presentation Model pattern. + +First, we have the `Album` class. This class represents the data model in our application. It contains properties like `title`, `artist`, `isClassical`, and `composer`. ```java +@Setter +@Getter +@AllArgsConstructor public class Album { - - private String title; - private String artist; - private boolean isClassical; - /** - * only when the album is classical, - * composer can have content. - */ - private String composer; + private String title; + private String artist; + private boolean isClassical; + private String composer; } - ``` -Class `DisplatedAlbums` is store the information of all the albums that will be displayed on GUI. +Next, we have the `DisplayedAlbums` class. This class is responsible for managing a collection of `Album` objects. ```java +@Slf4j +@Getter public class DisplayedAlbums { - private final List albums; - - public DisplayedAlbums() { - this.albums = new ArrayList<>(); - } - - public void addAlbums(final String title, - final String artist, final boolean isClassical, - final String composer) { - if (isClassical) { - this.albums.add(new Album(title, artist, true, composer)); - } else { - this.albums.add(new Album(title, artist, false, "")); + private final List albums; + + public DisplayedAlbums() { + this.albums = new ArrayList<>(); + } + + public void addAlbums(final String title, + final String artist, final boolean isClassical, + final String composer) { + if (isClassical) { + this.albums.add(new Album(title, artist, true, composer)); + } else { + this.albums.add(new Album(title, artist, false, "")); + } } - } } ``` - Class `PresentationMod` is used to control all the action of GUI. +The `PresentationModel` class is where the Presentation Model pattern is implemented. This class is responsible for managing the state and behavior of the GUI. It contains a reference to the `DisplayedAlbums` object and provides methods for interacting with the selected album. ```java public class PresentationModel { - private final DisplayedAlbums data; - - private int selectedAlbumNumber; - private Album selectedAlbum; - - public PresentationModel(final DisplayedAlbums dataOfAlbums) { - this.data = dataOfAlbums; - this.selectedAlbumNumber = 1; - this.selectedAlbum = this.data.getAlbums().get(0); - } - - /** - * Changes the value of selectedAlbumNumber. - * - * @param albumNumber the number of album which is shown on the view. - */ - public void setSelectedAlbumNumber(final int albumNumber) { - LOGGER.info("Change select number from {} to {}", - this.selectedAlbumNumber, albumNumber); - this.selectedAlbumNumber = albumNumber; - this.selectedAlbum = data.getAlbums().get(this.selectedAlbumNumber - 1); - } - - public String getTitle() { - return selectedAlbum.getTitle(); - } - // other get methods are like this, which are used to get information of selected album. - - public void setTitle(final String value) { - LOGGER.info("Change album title from {} to {}", - selectedAlbum.getTitle(), value); - selectedAlbum.setTitle(value); - } - // other set methods are like this, which are used to get information of selected album. - - /** - * Gets a list of albums. - * - * @return the names of all the albums. - */ - public String[] getAlbumList() { - var result = new String[data.getAlbums().size()]; - for (var i = 0; i < result.length; i++) { - result[i] = data.getAlbums().get(i).getTitle(); + + private final DisplayedAlbums data; + private int selectedAlbumNumber; + private Album selectedAlbum; + + public PresentationModel(final DisplayedAlbums dataOfAlbums) { + this.data = dataOfAlbums; + this.selectedAlbumNumber = 1; + this.selectedAlbum = this.data.getAlbums().get(0); } - return result; - } + + // other methods... } ``` -We can run class `App` to start this demo. the checkbox is the album classical; the first text field is the name of album artist; the second is the name of album title; the last one is the name of the composer: +The `App` class is the entry point of the application. It creates a `View` object and calls its `createView` method to start the GUI. -![](./etc/result.png) +```java +public final class App { + public static void main(final String[] args) { + var view = new View(); + view.createView(); + } +} +``` +In this example, the `PresentationModel` class is the Presentation Model. It separates the GUI's state and behavior from the `View` class, allowing the GUI to be tested independently from the actual GUI components. -## Class diagram -![](./etc/presentation-model.urm.png "presentation model") +## When to Use the Presentation Model Pattern in Java -## Applicability Use the Presentation Model Pattern when -* Testing a presentation through a GUI window is often awkward, and in some cases impossible. -* Do not determine which GUI will be used. +* Use when you want to decouple the UI from the underlying business logic to allow for easier testing, maintenance, and the ability to support multiple views or platforms. +* Ideal for applications where the UI changes frequently or needs to be different across various platforms while keeping the core logic intact. + +## Real-World Applications of Presentation Model Pattern in Java + +The Presentation Model pattern is used in: + +* JavaFX applications: Utilizing JavaFX properties and bindings to create a clear separation between the UI and business logic. +* Swing applications: Employing a Presentation Model to decouple Swing components from the application logic, enhancing testability and flexibility. +* Android apps: Implementing MVVM architecture using ViewModel classes to manage UI-related data and lifecycle-aware components. + +## Benefits and Trade-offs of Presentation Model Pattern + +Benefits: + +* Decoupling: Enhances [separation of concerns](https://java-design-patterns.com/principles/#separation-of-concerns), making the system more modular and testable. +* Testability: Facilitates unit testing of UI logic without the need for actual UI components. +* Maintainability: Simplifies maintenance by isolating changes to the UI or business logic. +* Flexibility: Supports multiple views for the same model, making it easier to adapt the UI for different platforms. + +Trade-offs: -## Related patterns +* Complexity: Can introduce additional layers and complexity in the application architecture. +* Learning Curve: May require a deeper understanding of binding mechanisms and state management. -- [Supervising Controller](https://martinfowler.com/eaaDev/SupervisingPresenter.html) -- [Passive View](https://martinfowler.com/eaaDev/PassiveScreen.html) +## Related Java Design Patterns -## Credits +* [Model-View-Controller (MVC)](https://java-design-patterns.com/patterns/model-view-controller/): Similar in that it separates concerns, but Presentation Model encapsulates more of the view logic. +* [Model-View-Presenter (MVP)](https://java-design-patterns.com/patterns/model-view-presenter/): Another UI pattern focusing on separation of concerns, but with a different interaction model. +* [Observer](https://java-design-patterns.com/patterns/observer/): Often used within the Presentation Model to update the UI when the model changes. -* [Presentation Model Patterns](https://martinfowler.com/eaaDev/PresentationModel.html) +## References and Credits +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Presentation Model (Martin Fowler)](https://martinfowler.com/eaaDev/PresentationModel.html) diff --git a/presentation-model/etc/presentation-model-architecture-diagram.png b/presentation-model/etc/presentation-model-architecture-diagram.png new file mode 100644 index 000000000000..8c6718dfb89b Binary files /dev/null and b/presentation-model/etc/presentation-model-architecture-diagram.png differ diff --git a/presentation-model/pom.xml b/presentation-model/pom.xml index 8ff4a5bf7c6f..e2701838cb60 100644 --- a/presentation-model/pom.xml +++ b/presentation-model/pom.xml @@ -32,8 +32,16 @@ java-design-patterns 1.26.0-SNAPSHOT - presentation + presentation-model + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java index aa32312defbf..af2dc0662395 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java @@ -28,28 +28,20 @@ import lombok.Getter; import lombok.Setter; -/** - *A class used to store the information of album. - */ +/** A class used to store the information of album. */ @Setter @Getter @AllArgsConstructor public class Album { - /** - * the title of the album. - */ + /** the title of the album. */ private String title; - /** - * the artist name of the album. - */ + + /** the artist name of the album. */ private String artist; - /** - * is the album classical, true or false. - */ + + /** is the album classical, true or false. */ private boolean isClassical; - /** - * only when the album is classical, - * composer can have content. - */ + + /** only when the album is classical, composer can have content. */ private String composer; } diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java index d799480b86e5..0393c48265fa 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java @@ -27,16 +27,13 @@ import lombok.extern.slf4j.Slf4j; /** - * The Presentation model pattern is used to divide the presentation and controlling. - * This demo is a used to information of some albums with GUI. + * The Presentation model pattern is used to divide the presentation and controlling. This demo is a + * used to information of some albums with GUI. */ @Slf4j public final class App { - /** - * the constructor. - */ - private App() { - } + /** the constructor. */ + private App() {} /** * main method. @@ -48,4 +45,3 @@ public static void main(final String[] args) { view.createView(); } } - diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java index 11dae6df65ce..0e74e4c6db61 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java @@ -29,21 +29,14 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * a class used to deal with albums. - * - */ +/** a class used to deal with albums. */ @Slf4j @Getter public class DisplayedAlbums { - /** - * albums a list of albums. - */ + /** albums a list of albums. */ private final List albums; - /** - * a constructor method. - */ + /** a constructor method. */ public DisplayedAlbums() { this.albums = new ArrayList<>(); } @@ -51,15 +44,13 @@ public DisplayedAlbums() { /** * a method used to add a new album to album list. * - * @param title the title of the album. - * @param artist the artist name of the album. + * @param title the title of the album. + * @param artist the artist name of the album. * @param isClassical is the album classical, true or false. - * @param composer only when the album is classical, - * composer can have content. + * @param composer only when the album is classical, composer can have content. */ - public void addAlbums(final String title, - final String artist, final boolean isClassical, - final String composer) { + public void addAlbums( + final String title, final String artist, final boolean isClassical, final String composer) { if (isClassical) { this.albums.add(new Album(title, artist, true, composer)); } else { diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java index 67414d6aeb74..8f6f7597b1ef 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java @@ -26,22 +26,16 @@ import lombok.extern.slf4j.Slf4j; -/** - * The class between view and albums, it is used to control the data. - */ +/** The class between view and albums, it is used to control the data. */ @Slf4j public class PresentationModel { - /** - * the data of all albums that will be shown. - */ + /** the data of all albums that will be shown. */ private final DisplayedAlbums data; - /** - * the no of selected album. - */ + + /** the no of selected album. */ private int selectedAlbumNumber; - /** - * the selected album. - */ + + /** the selected album. */ private Album selectedAlbum; /** @@ -50,17 +44,23 @@ public class PresentationModel { * @return a instance of DsAlbum which store the data. */ public static DisplayedAlbums albumDataSet() { - var titleList = new String[]{"HQ", "The Rough Dancer and Cyclical Night", - "The Black Light", "Symphony No.5"}; - var artistList = new String[]{"Roy Harper", "Astor Piazzola", - "The Black Light", "CBSO"}; - var isClassicalList = new boolean[]{false, false, false, true}; - var composerList = new String[]{null, null, null, "Sibelius"}; + var titleList = + new String[] { + "HQ", "The Rough Dancer and Cyclical Night", + "The Black Light", "Symphony No.5" + }; + var artistList = + new String[] { + "Roy Harper", "Astor Piazzola", + "The Black Light", "CBSO" + }; + var isClassicalList = new boolean[] {false, false, false, true}; + var composerList = new String[] {null, null, null, "Sibelius"}; var result = new DisplayedAlbums(); for (var i = 1; i <= titleList.length; i++) { - result.addAlbums(titleList[i - 1], artistList[i - 1], - isClassicalList[i - 1], composerList[i - 1]); + result.addAlbums( + titleList[i - 1], artistList[i - 1], isClassicalList[i - 1], composerList[i - 1]); } return result; } @@ -82,8 +82,7 @@ public PresentationModel(final DisplayedAlbums dataOfAlbums) { * @param albumNumber the number of album which is shown on the view. */ public void setSelectedAlbumNumber(final int albumNumber) { - LOGGER.info("Change select number from {} to {}", - this.selectedAlbumNumber, albumNumber); + LOGGER.info("Change select number from {} to {}", this.selectedAlbumNumber, albumNumber); this.selectedAlbumNumber = albumNumber; this.selectedAlbum = data.getAlbums().get(this.selectedAlbumNumber - 1); } @@ -103,8 +102,7 @@ public String getTitle() { * @param value the title which user want to user. */ public void setTitle(final String value) { - LOGGER.info("Change album title from {} to {}", - selectedAlbum.getTitle(), value); + LOGGER.info("Change album title from {} to {}", selectedAlbum.getTitle(), value); selectedAlbum.setTitle(value); } @@ -123,8 +121,7 @@ public String getArtist() { * @param value the name want artist to be. */ public void setArtist(final String value) { - LOGGER.info("Change album artist from {} to {}", - selectedAlbum.getArtist(), value); + LOGGER.info("Change album artist from {} to {}", selectedAlbum.getArtist(), value); selectedAlbum.setArtist(value); } @@ -143,8 +140,7 @@ public boolean getIsClassical() { * @param value is the album classical. */ public void setIsClassical(final boolean value) { - LOGGER.info("Change album isClassical from {} to {}", - selectedAlbum.isClassical(), value); + LOGGER.info("Change album isClassical from {} to {}", selectedAlbum.isClassical(), value); selectedAlbum.setClassical(value); } @@ -164,8 +160,7 @@ public String getComposer() { */ public void setComposer(final String value) { if (selectedAlbum.isClassical()) { - LOGGER.info("Change album composer from {} to {}", - selectedAlbum.getComposer(), value); + LOGGER.info("Change album composer from {} to {}", selectedAlbum.getComposer(), value); selectedAlbum.setComposer(value); } else { LOGGER.info("Composer can not be changed"); diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java index 2d694644f986..f62335293f0a 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java @@ -35,70 +35,52 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * Generates the GUI of albums. - */ +/** Generates the GUI of albums. */ @Getter @Slf4j public class View { - /** - * the model that controls this view. - */ + /** the model that controls this view. */ private final PresentationModel model; - /** - * the filed to show and modify title. - */ + /** the filed to show and modify title. */ private TextField txtTitle; - /** - * the filed to show and modify the name of artist. - */ + + /** the filed to show and modify the name of artist. */ private TextField txtArtist; - /** - * the checkbox for is classical. - */ + + /** the checkbox for is classical. */ private JCheckBox chkClassical; - /** - * the filed to show and modify composer. - */ + + /** the filed to show and modify composer. */ private TextField txtComposer; - /** - * a list to show all the name of album. - */ + + /** a list to show all the name of album. */ private JList albumList; - /** - * a button to apply of all the change. - */ + + /** a button to apply of all the change. */ private JButton apply; - /** - * roll back the change. - */ + + /** roll back the change. */ private JButton cancel; - /** - * the value of the text field size. - */ + /** the value of the text field size. */ static final int WIDTH_TXT = 200; + static final int HEIGHT_TXT = 50; - /** - * the value of the GUI size and location. - */ + /** the value of the GUI size and location. */ static final int LOCATION_X = 200; + static final int LOCATION_Y = 200; static final int WIDTH = 500; static final int HEIGHT = 300; - /** - * constructor method. - */ + /** constructor method. */ public View() { model = new PresentationModel(PresentationModel.albumDataSet()); } - /** - * save the data to PresentationModel. - */ + /** save the data to PresentationModel. */ public void saveToMod() { LOGGER.info("Save data to PresentationModel"); model.setArtist(txtArtist.getText()); @@ -107,9 +89,7 @@ public void saveToMod() { model.setComposer(txtComposer.getText()); } - /** - * load the data from PresentationModel. - */ + /** load the data from PresentationModel. */ public void loadFromMod() { LOGGER.info("Load data from PresentationModel"); txtArtist.setText(model.getArtist()); @@ -119,22 +99,21 @@ public void loadFromMod() { txtComposer.setText(model.getComposer()); } - /** - * initialize the GUI. - */ + /** initialize the GUI. */ public void createView() { var frame = new JFrame("Album"); var b1 = Box.createHorizontalBox(); frame.add(b1); albumList = new JList<>(model.getAlbumList()); - albumList.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - model.setSelectedAlbumNumber(albumList.getSelectedIndex() + 1); - loadFromMod(); - } - }); + albumList.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + model.setSelectedAlbumNumber(albumList.getSelectedIndex() + 1); + loadFromMod(); + } + }); b1.add(albumList); var b2 = Box.createVerticalBox(); @@ -148,30 +127,33 @@ public void mouseClicked(final MouseEvent e) { chkClassical = new JCheckBox(); txtComposer = new TextField(); - chkClassical.addActionListener(itemEvent -> { - txtComposer.setEditable(chkClassical.isSelected()); - if (!chkClassical.isSelected()) { - txtComposer.setText(""); - } - }); + chkClassical.addActionListener( + itemEvent -> { + txtComposer.setEditable(chkClassical.isSelected()); + if (!chkClassical.isSelected()) { + txtComposer.setText(""); + } + }); txtComposer.setSize(WIDTH_TXT, HEIGHT_TXT); txtComposer.setEditable(model.getIsClassical()); apply = new JButton("Apply"); - apply.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - saveToMod(); - loadFromMod(); - } - }); + apply.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + saveToMod(); + loadFromMod(); + } + }); cancel = new JButton("Cancel"); - cancel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - loadFromMod(); - } - }); + cancel.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + loadFromMod(); + } + }); b2.add(txtArtist); b2.add(txtTitle); @@ -186,5 +168,4 @@ public void mouseClicked(final MouseEvent e) { frame.setBounds(LOCATION_X, LOCATION_Y, WIDTH, HEIGHT); frame.setVisible(true); } - } diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java index dd4d84884093..1f87055b4e59 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java @@ -24,35 +24,35 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + class AlbumTest { @Test - void testSetTitle(){ + void testSetTitle() { Album album = new Album("a", "b", false, ""); album.setTitle("b"); assertEquals("b", album.getTitle()); } @Test - void testSetArtist(){ + void testSetArtist() { Album album = new Album("a", "b", false, ""); album.setArtist("c"); assertEquals("c", album.getArtist()); } @Test - void testSetClassical(){ + void testSetClassical() { Album album = new Album("a", "b", false, ""); album.setClassical(true); assertTrue(album.isClassical()); } @Test - void testSetComposer(){ + void testSetComposer() { Album album = new Album("a", "b", false, ""); album.setClassical(true); album.setComposer("w"); diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java index c3715ed5a766..fb8780a1fe67 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java @@ -24,20 +24,20 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App} + *

Solution: Inserted assertion to check whether the execution of the main method in {@link App} * throws an exception. */ class AppTest { - @Test - void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java index b9d5716427bd..8c5599fe1537 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java @@ -24,21 +24,20 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + class DisplayedAlbumsTest { @Test - void testAdd_true(){ + void testAdd_true() { DisplayedAlbums displayedAlbums = new DisplayedAlbums(); displayedAlbums.addAlbums("title", "artist", true, "composer"); assertEquals("composer", displayedAlbums.getAlbums().get(0).getComposer()); - } @Test - void testAdd_false(){ + void testAdd_false() { DisplayedAlbums displayedAlbums = new DisplayedAlbums(); displayedAlbums.addAlbums("title", "artist", false, "composer"); assertEquals("", displayedAlbums.getAlbums().get(0).getComposer()); diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java index 2a7d26caf006..0326824c0882 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java @@ -24,15 +24,16 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + class PresentationTest { - String[] albumList = {"HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5"}; + String[] albumList = { + "HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5" + }; @Test void testCreateAlbumList() { diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java index 7a0e6edd04c2..6428c6ac0036 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java @@ -24,15 +24,18 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + class ViewTest { - String[] albumList = {"HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5"}; + String[] albumList = { + "HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5" + }; @Test - void testSave_setArtistAndTitle(){ + void testSave_setArtistAndTitle() { View view = new View(); view.createView(); String testTitle = "testTitle"; @@ -46,7 +49,7 @@ void testSave_setArtistAndTitle(){ } @Test - void testSave_setClassicalAndComposer(){ + void testSave_setClassicalAndComposer() { View view = new View(); view.createView(); boolean isClassical = true; @@ -60,7 +63,7 @@ void testSave_setClassicalAndComposer(){ } @Test - void testLoad_1(){ + void testLoad_1() { View view = new View(); view.createView(); view.getModel().setSelectedAlbumNumber(2); @@ -69,7 +72,7 @@ void testLoad_1(){ } @Test - void testLoad_2(){ + void testLoad_2() { View view = new View(); view.createView(); view.getModel().setSelectedAlbumNumber(4); diff --git a/priority-queue/etc/priority-queue.urm.puml b/priority-queue/etc/priority-queue.urm.puml deleted file mode 100644 index cee118f57671..000000000000 --- a/priority-queue/etc/priority-queue.urm.puml +++ /dev/null @@ -1,54 +0,0 @@ -@startuml -package com.iluwatar.priority.queue { - class Application { - + Application() - + main(args : String[]) {static} - } - class Message { - - message : String - - priority : int - + Message(message : String, priority : int) - + compareTo(o : Message) : int - + toString() : String - } - class PriorityMessageQueue { - - LOGGER : Logger {static} - - capacity : int - - queue : T[] - - size : int - + PriorityMessageQueue(queue : T[]) - + add(t : T extends Comparable) - - ensureCapacity() - - hasLeftChild(index : int) : boolean - - hasParent(index : int) : boolean - - hasRightChild(index : int) : boolean - + isEmpty() : boolean - - left(parentIndex : int) : T extends Comparable - - leftChildIndex(parentPos : int) : int - - maxHeapifyDown() - - maxHeapifyUp() - - parent(childIndex : int) : T extends Comparable - - parentIndex(pos : int) : int - + print() - + remove() : T extends Comparable - - right(parentIndex : int) : T extends Comparable - - rightChildIndex(parentPos : int) : int - - swap(fpos : int, tpos : int) - } - class QueueManager { - - messagePriorityMessageQueue : PriorityMessageQueue - + QueueManager(initialCapacity : int) - + publishMessage(message : Message) - + receiveMessage() : Message - } - class Worker { - - LOGGER : Logger {static} - - queueManager : QueueManager - + Worker(queueManager : QueueManager) - - processMessage(message : Message) - + run() - } -} -QueueManager --> "-messagePriorityMessageQueue" PriorityMessageQueue -Worker --> "-queueManager" QueueManager -@enduml \ No newline at end of file diff --git a/priority-queue/pom.xml b/priority-queue/pom.xml deleted file mode 100644 index 63eed2c90cfc..000000000000 --- a/priority-queue/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - 4.0.0 - priority-queue - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.priority.queue.Application - - - - - - - - - diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java deleted file mode 100644 index 9d9935adee0e..000000000000 --- a/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.priority.queue; - -/** - * Prioritize requests sent to services so that requests with a higher priority are received and - * processed more quickly than those of a lower priority. This pattern is useful in applications - * that offer different service level guarantees to individual clients. Example :Send multiple - * message with different priority to worker queue. Worker execute higher priority message first - * - * @see "https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn589794(v=pandp.10)" - */ -public class Application { - /** - * main entry. - */ - public static void main(String[] args) throws Exception { - - var queueManager = new QueueManager(10); - - // push some message to queue - // Low Priority message - for (var i = 0; i < 10; i++) { - queueManager.publishMessage(new Message("Low Message Priority", 0)); - } - - // High Priority message - for (var i = 0; i < 10; i++) { - queueManager.publishMessage(new Message("High Message Priority", 1)); - } - - // run worker - var worker = new Worker(queueManager); - worker.run(); - - - } -} diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/PriorityMessageQueue.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/PriorityMessageQueue.java deleted file mode 100644 index 09704f15e45f..000000000000 --- a/priority-queue/src/main/java/com/iluwatar/priority/queue/PriorityMessageQueue.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.priority.queue; - -import static java.util.Arrays.copyOf; - -import lombok.extern.slf4j.Slf4j; - -/** - * Keep high Priority message on top using maxHeap. - * - * @param : DataType to push in Queue - */ -@Slf4j -public class PriorityMessageQueue { - - private int size = 0; - - private int capacity; - - - private T[] queue; - - public PriorityMessageQueue(T[] queue) { - this.queue = queue; - this.capacity = queue.length; - } - - /** - * Remove top message from queue. - */ - public T remove() { - if (isEmpty()) { - return null; - } - - final var root = queue[0]; - queue[0] = queue[size - 1]; - size--; - maxHeapifyDown(); - return root; - } - - /** - * Add message to queue. - */ - public void add(T t) { - ensureCapacity(); - queue[size] = t; - size++; - maxHeapifyUp(); - } - - /** - * Check queue size. - */ - public boolean isEmpty() { - return size == 0; - } - - - private void maxHeapifyDown() { - var index = 0; - while (hasLeftChild(index)) { - - var smallerIndex = leftChildIndex(index); - - if (hasRightChild(index) && right(index).compareTo(left(index)) > 0) { - smallerIndex = rightChildIndex(index); - } - - if (queue[index].compareTo(queue[smallerIndex]) > 0) { - break; - } else { - swap(index, smallerIndex); - } - - index = smallerIndex; - - - } - - } - - private void maxHeapifyUp() { - var index = size - 1; - while (hasParent(index) && parent(index).compareTo(queue[index]) < 0) { - swap(parentIndex(index), index); - index = parentIndex(index); - } - } - - - // index - private int parentIndex(int pos) { - return (pos - 1) / 2; - } - - private int leftChildIndex(int parentPos) { - return 2 * parentPos + 1; - } - - private int rightChildIndex(int parentPos) { - return 2 * parentPos + 2; - } - - // value - private T parent(int childIndex) { - return queue[parentIndex(childIndex)]; - } - - private T left(int parentIndex) { - return queue[leftChildIndex(parentIndex)]; - } - - private T right(int parentIndex) { - return queue[rightChildIndex(parentIndex)]; - } - - // check - private boolean hasLeftChild(int index) { - return leftChildIndex(index) < size; - } - - private boolean hasRightChild(int index) { - return rightChildIndex(index) < size; - } - - private boolean hasParent(int index) { - return parentIndex(index) >= 0; - } - - private void swap(int fpos, int tpos) { - var tmp = queue[fpos]; - queue[fpos] = queue[tpos]; - queue[tpos] = tmp; - } - - private void ensureCapacity() { - if (size == capacity) { - capacity = capacity * 2; - queue = copyOf(queue, capacity); - } - } - - /** - * For debug .. print current state of queue - */ - public void print() { - for (var i = 0; i <= size / 2; i++) { - LOGGER.info(" PARENT : " + queue[i] + " LEFT CHILD : " - + left(i) + " RIGHT CHILD :" + right(i)); - } - } - -} diff --git a/private-class-data/README.md b/private-class-data/README.md index 9426f273f051..18dd48053849 100644 --- a/private-class-data/README.md +++ b/private-class-data/README.md @@ -1,56 +1,65 @@ --- -title: Private Class Data -category: Idiom +title: "Private Class Data Pattern in Java: Safeguarding Data Integrity with Encapsulation" +shortTitle: Private Class Data +description: "Explore the Private Class Data pattern in Java, ideal for enhancing data security and integrity in object-oriented programming. Learn how it prevents unintended data manipulation with encapsulation." +category: Structural language: en tag: - - Data access + - Abstraction + - Encapsulation + - Security --- -## Intent +## Also known as -Private Class Data design pattern seeks to reduce exposure of attributes by limiting their -visibility. It reduces the number of class attributes by encapsulating them in single Data object. +* Data Hiding +* Encapsulation -## Explanation +## Intent of Private Class Data Design Pattern -Real world example +The Private Class Data design pattern in Java focuses on restricting access to the internal state of an object, enhancing security and reducing risks of data corruption through controlled method access. -> Imagine you are cooking a stew for your family for dinner. You want to prevent your family members -> from consuming the stew by tasting it while you are cooking, otherwise there will be no more stew -> for dinner later. +## Detailed Explanation of Private Class Data Pattern with Real-World Examples + +Real-world example + +> A real-world analogy for the Private Class Data pattern is the way a bank protects customer account information. Just like a class with private fields, a bank keeps sensitive data such as account balances, transaction history, and personal information private and only accessible through specific methods. Customers interact with their accounts through well-defined interfaces such as ATMs or online banking portals, which enforce security and validation rules, ensuring that unauthorized access or modifications are prevented. This controlled access mechanism ensures the integrity and security of the data, similar to how Private Class Data protects and manages access to class attributes in software design. In plain words -> Private class data pattern prevents manipulation of data that is meant to be immutable by -> separating the data from the methods that use it into a class that maintains the data state. +> Private class data pattern prevents manipulation of data that is meant to be immutable by separating the data from the methods that use it into a class that maintains the data state. Wikipedia says -> Private class data is a design pattern in computer programming used to encapsulate class -> attributes and their manipulation. +> Private class data is a design pattern in computer programming used to encapsulate class attributes and their manipulation. -**Programmatic Example** +## Programmatic Example of Private Class Data Pattern in Java -Taking our stew example from above. First we have a `Stew` class where its data is not protected by -private class data, making the stew's ingredient mutable to class methods. +Imagine you are cooking a stew for your family dinner. You want to stop your family members from tasting the stew while you're still preparing it. If they do, there might not be enough stew left for dinner. + +First, we have a `Stew` class where its data is not protected by private class data, making the stew's ingredient mutable to class methods. ```java @Slf4j public class Stew { + private int numPotatoes; private int numCarrots; private int numMeat; private int numPeppers; + public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { this.numPotatoes = numPotatoes; this.numCarrots = numCarrots; this.numMeat = numMeat; this.numPeppers = numPeppers; } + public void mix() { LOGGER.info("Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers", numPotatoes, numCarrots, numMeat, numPeppers); } + public void taste() { LOGGER.info("Tasting the stew"); if (numPotatoes > 0) { @@ -69,40 +78,20 @@ public class Stew { } ``` -Now, we have `ImmutableStew` class, where its data is protected by `StewData` class. Now, the -methods in are unable to manipulate the data of the `ImmutableStew` class. +Now, we have `ImmutableStew` class, where its data is protected by `StewData` record. The methods in `ImmutableStew` are unable to manipulate the data of the `StewData` class. ```java -public class StewData { - private final int numPotatoes; - private final int numCarrots; - private final int numMeat; - private final int numPeppers; - public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { - this.numPotatoes = numPotatoes; - this.numCarrots = numCarrots; - this.numMeat = numMeat; - this.numPeppers = numPeppers; - } - public int getNumPotatoes() { - return numPotatoes; - } - public int getNumCarrots() { - return numCarrots; - } - public int getNumMeat() { - return numMeat; - } - public int getNumPeppers() { - return numPeppers; - } -} +public record StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) {} + @Slf4j public class ImmutableStew { + private final StewData data; + public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); } + public void mix() { LOGGER .info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", @@ -114,20 +103,63 @@ public class ImmutableStew { Let's try creating an instance of each class and call their methods: ```java -var stew = new Stew(1, 2, 3, 4); -stew.mix(); // Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers -stew.taste(); // Tasting the stew -stew.mix(); // Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers -var immutableStew = new ImmutableStew(2, 4, 3, 6); -immutableStew.mix(); // Mixing the immutable stew we find: 2 potatoes, 4 carrots, 3 meat and 6 peppers +public static void main(String[] args) { + // stew is mutable + var stew = new Stew(1, 2, 3, 4); + stew.mix(); + stew.taste(); + stew.mix(); + + // immutable stew protected with Private Class Data pattern + var immutableStew = new ImmutableStew(2, 4, 3, 6); + immutableStew.mix(); +} ``` -## Class diagram +Program output: -![alt text](./etc/private-class-data.png "Private Class Data") +``` +08:00:08.210 [main] INFO com.iluwatar.privateclassdata.Stew -- Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers +08:00:08.212 [main] INFO com.iluwatar.privateclassdata.Stew -- Tasting the stew +08:00:08.212 [main] INFO com.iluwatar.privateclassdata.Stew -- Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers +08:00:08.213 [main] INFO com.iluwatar.privateclassdata.ImmutableStew -- Mixing the immutable stew we find: 2 potatoes, 4 carrots, 3 meat and 6 peppers +``` -## Applicability +## When to Use the Private Class Data Pattern in Java Use the Private Class Data pattern when -* You want to prevent write access to class data members. +* When you want to protect the integrity of an object’s state. +* When you need to limit the visibility of the internal data of an object to prevent unintended modification. +* In scenarios where multiple classes need to share access to some common data without exposing it directly. + +## Real-World Applications of Private Class Data Pattern in Java + +* Java Beans, where properties are accessed via getters and setters. +* In many Java libraries where the internal state is hidden from the user to ensure consistency and security. +* Enterprise applications where sensitive data needs to be protected from direct access. + +## Benefits and Trade-offs of Private Class Data Pattern + +Benefits: + +* Enhanced Security: Reduces the risk of unintended data corruption by encapsulating the data. +* Ease of Maintenance: Changes to the internal representation of data do not affect external code. +* Improved Abstraction: Users interact with a simplified interface without worrying about the complexities of data management. + +Trade-offs: + +* Performance Overhead: Additional method calls (getters/setters) can introduce slight performance overhead. +* Complexity: May increase the complexity of the class design due to the additional layer of methods for data access. + +## Related Java Design Patterns + +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Both patterns restrict access to the underlying object but Proxy controls access to the object itself, while Private Class Data controls access to the data. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Ensures that a class has only one instance and provides a global point of access to it; often used to manage shared data with controlled access. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Adds behavior to an object without altering its structure; can be combined with Private Class Data to manage additional state privately. + +## References and Credits + +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/3UJTZJk) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) diff --git a/private-class-data/pom.xml b/private-class-data/pom.xml index 57fc5eba913e..393832527dd6 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -34,6 +34,14 @@ private-class-data + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java index e13577911eb5..3a7f8c809a76 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Immutable stew class, protected with Private Class Data pattern. - */ +/** Immutable stew class, protected with Private Class Data pattern. */ @Slf4j public class ImmutableStew { @@ -38,12 +36,13 @@ public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPepper data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); } - /** - * Mix the stew. - */ + /** Mix the stew. */ public void mix() { - LOGGER - .info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", - data.numPotatoes(), data.numCarrots(), data.numMeat(), data.numPeppers()); + LOGGER.info( + "Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + data.numPotatoes(), + data.numCarrots(), + data.numMeat(), + data.numPeppers()); } } diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java index fe6122f9d547..7d58698d3427 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java @@ -1,76 +1,72 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.privateclassdata; - -import lombok.extern.slf4j.Slf4j; - -/** - * Mutable stew class. - */ -@Slf4j -public class Stew { - - private int numPotatoes; - private int numCarrots; - private int numMeat; - private int numPeppers; - - /** - * Constructor. - */ - public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { - this.numPotatoes = numPotatoes; - this.numCarrots = numCarrots; - this.numMeat = numMeat; - this.numPeppers = numPeppers; - } - - /** - * Mix the stew. - */ - public void mix() { - LOGGER.info("Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers", - numPotatoes, numCarrots, numMeat, numPeppers); - } - - /** - * Taste the stew. - */ - public void taste() { - LOGGER.info("Tasting the stew"); - if (numPotatoes > 0) { - numPotatoes--; - } - if (numCarrots > 0) { - numCarrots--; - } - if (numMeat > 0) { - numMeat--; - } - if (numPeppers > 0) { - numPeppers--; - } - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.privateclassdata; + +import lombok.extern.slf4j.Slf4j; + +/** Mutable stew class. */ +@Slf4j +public class Stew { + + private int numPotatoes; + private int numCarrots; + private int numMeat; + private int numPeppers; + + /** Constructor. */ + public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + this.numPotatoes = numPotatoes; + this.numCarrots = numCarrots; + this.numMeat = numMeat; + this.numPeppers = numPeppers; + } + + /** Mix the stew. */ + public void mix() { + LOGGER.info( + "Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + numPotatoes, + numCarrots, + numMeat, + numPeppers); + } + + /** Taste the stew. */ + public void taste() { + LOGGER.info("Tasting the stew"); + if (numPotatoes > 0) { + numPotatoes--; + } + if (numCarrots > 0) { + numCarrots--; + } + if (numMeat > 0) { + numMeat--; + } + if (numPeppers > 0) { + numPeppers--; + } + } +} diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java index a973fde6d5de..31b918afc1b9 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java @@ -24,9 +24,5 @@ */ package com.iluwatar.privateclassdata; -/** - * Stew ingredients. - */ - -public record StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { -} +/** Stew ingredients. */ +public record StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) {} diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java index 575b377196ad..4ec59c8aeda6 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.privateclassdata; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java index 62fd58141bcb..9caf7dd1b3ed 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java @@ -31,11 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 10:46 PM - * - * @author Jeroen Meulemeester - */ +/** ImmutableStewTest */ class ImmutableStewTest { private InMemoryAppender appender; @@ -50,9 +46,7 @@ void tearDown() { appender.stop(); } - /** - * Verify if mixing the stew doesn't change the internal state - */ + /** Verify if mixing the stew doesn't change the internal state */ @Test void testMix() { var stew = new Stew(1, 2, 3, 4); @@ -66,22 +60,22 @@ void testMix() { assertEquals(20, appender.getLogSize()); } - /** - * Verify if tasting the stew actually removes one of each ingredient - */ + /** Verify if tasting the stew actually removes one of each ingredient */ @Test void testDrink() { final var stew = new Stew(1, 2, 3, 4); stew.mix(); - assertEquals("Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers", appender - .getLastMessage()); + assertEquals( + "Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers", + appender.getLastMessage()); stew.taste(); assertEquals("Tasting the stew", appender.getLastMessage()); stew.mix(); - assertEquals("Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers", appender - .getLastMessage()); + assertEquals( + "Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers", + appender.getLastMessage()); } } diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java index e8153017fb55..8715160b6488 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java @@ -31,11 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 10:46 PM - * - * @author Jeroen Meulemeester - */ +/** StewTest */ class StewTest { private InMemoryAppender appender; @@ -50,14 +46,12 @@ void tearDown() { appender.stop(); } - /** - * Verify if mixing the stew doesn't change the internal state - */ + /** Verify if mixing the stew doesn't change the internal state */ @Test void testMix() { final var stew = new ImmutableStew(1, 2, 3, 4); - final var expectedMessage = "Mixing the immutable stew we find: 1 potatoes, " - + "2 carrots, 3 meat and 4 peppers"; + final var expectedMessage = + "Mixing the immutable stew we find: 1 potatoes, " + "2 carrots, 3 meat and 4 peppers"; for (var i = 0; i < 20; i++) { stew.mix(); @@ -66,5 +60,4 @@ void testMix() { assertEquals(20, appender.getLogSize()); } - } diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java index 262a41995436..edb7853541ce 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java @@ -31,9 +31,7 @@ import java.util.List; import org.slf4j.LoggerFactory; -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/producer-consumer/README.md b/producer-consumer/README.md index 7f9b96ce3cad..a40ef5a654bd 100644 --- a/producer-consumer/README.md +++ b/producer-consumer/README.md @@ -1,49 +1,80 @@ --- -title: Producer Consumer +title: "Producer-Consumer Pattern in Java: Streamlining Production and Consumption Processes" +shortTitle: Producer-Consumer +description: "Explore the Producer-Consumer pattern, a fundamental concept in Java for managing concurrent data production and consumption with buffer management. Ideal for improving system design and performance." category: Concurrency language: en tag: - - Reactive + - Asynchronous + - Buffering + - Decoupling + - Messaging + - Synchronization + - Thread management --- -## Intent -Producer Consumer Design pattern is a classic concurrency pattern which reduces - coupling between Producer and Consumer by separating Identification of work with Execution of - Work. +## Also known as -## Explanation +* Bounded Buffer +* Consumer-Producer + +## Intent of Producer-Consumer Design Pattern + +The Producer-Consumer design pattern, a critical component for concurrent Java applications, is used to decouple the tasks of producing and consuming data, enabling a producer to generate data and a consumer to process that data concurrently without direct dependency on each other. + +## Detailed Explanation of Producer-Consumer Pattern with Real-World Examples Real-world example -> Consider a manufacturing process of item, the producer will need to pause the production when -> manufacturing pipeline is full and the consumer will need to pause the consumption of item -> when the manufacturing pipeline is empty. We can separate the process of production and consumption -> which work together and pause at separate times. +> In a typical car manufacturing setup, the Producer-Consumer pattern facilitates synchronous operations, ensuring efficient assembly and installation processes. Imagine a car manufacturing plant where different stages of production occur. The "producer" could be the station that assembles car engines, while the "consumer" could be the station that installs the engines into car bodies. The engines are placed onto a conveyor belt (acting as a buffer) once they are assembled. The installation station takes engines off the conveyor belt to install them into cars. This allows the engine assembly and engine installation processes to operate independently, with the conveyor belt managing the synchronization between these two stages. If the assembly station produces engines faster than the installation station can install them, the excess engines are temporarily stored on the conveyor belt. Conversely, if the installation station needs engines but the assembly station is temporarily halted, it can still work on the engines available on the belt. In plain words -> It provides a way to share data between multiple loops running at different rates. +> It provides a way to share data between multiple loops running at different rates. Wikipedia says -> Dijkstra wrote about the case: "We consider two processes, which are called the 'producer' -> and the 'consumer' respectively. The producer is a cyclic process that produces a certain -> portion of information, that has to be processed by the consumer. The consumer is also a cyclic -> process that needs to process the next portion of information, as has been produced by the producer -> We assume the two processes to be connected for this purpose via a buffer with unbounded capacity." -**Programmatic Example** +> Dijkstra wrote about the case: "We consider two processes, which are called the 'producer' and the 'consumer' respectively. The producer is a cyclic process that produces a certain portion of information, that has to be processed by the consumer. The consumer is also a cyclic process that needs to process the next portion of information, as has been produced by the producer. We assume the two processes to be connected for this purpose via a buffer with unbounded capacity." + +## Programmatic Example of Producer-Consumer Pattern in Java + +Consider a manufacturing process of item, the producer will need to pause the production when manufacturing pipeline is full and the consumer will need to pause the consumption of item when the manufacturing pipeline is empty. We can separate the process of production and consumption which work together and pause at separate times. -Take our Producer and Consumer example from above. Consider we have a `Item` class that is produced by `Producer` class and is added to the `ItemQueue`. +In this Java example, `Producers` generate items stored in `ItemQueue`, demonstrating efficient thread management and data synchronization essential for high-performance Java applications. + +We have a simple `Item` record. They are stored in `ItemQueue`. + +```java +public record Item(String producer, int id) {} +``` + +```java +public class ItemQueue { + + private final BlockingQueue queue; + + public ItemQueue() { + queue = new LinkedBlockingQueue<>(5); + } + + public void put(Item item) throws InterruptedException { + queue.put(item); + } + + public Item take() throws InterruptedException { + return queue.take(); + } +} +``` + +`Producer` produces items to the `ItemQueue`. ```java public class Producer { private static final SecureRandom RANDOM = new SecureRandom(); - private final ItemQueue queue; - private final String name; - private int itemId; public Producer(String name, ItemQueue queue) { @@ -51,9 +82,6 @@ public class Producer { this.queue = queue; } - /** - * Put item in the queue. - */ public void produce() throws InterruptedException { var item = new Item(name, itemId++); @@ -61,18 +89,15 @@ public class Producer { Thread.sleep(RANDOM.nextInt(2000)); } } - ``` -Then, we have the `Consumer` class that takes the item from the item queue. +Then, we have the `Consumer` class that takes items from the `ItemQueue`. ```java - @Slf4j public class Consumer { private final ItemQueue queue; - private final String name; public Consumer(String name, ItemQueue queue) { @@ -80,9 +105,6 @@ public class Consumer { this.queue = queue; } - /** - * Consume item from the queue. - */ public void consume() throws InterruptedException { var item = queue.take(); LOGGER.info("Consumer [{}] consume item [{}] produced by [{}]", name, @@ -92,38 +114,105 @@ public class Consumer { } ``` -Now, during the manufacturing pipeline, we can instantiate objects from both the `Producer` and `Consumer` clasess as they produce and consumer items from the queue. +Now, during the manufacturing pipeline, we can instantiate objects from both the `Producer` and `Consumer` classes as they produce and consume items from the queue. ```java -var queue = new ItemQueue(); -var executorService = Executors.newFixedThreadPool(5); -for (var i = 0; i < 2; i++) { - - final var producer = new Producer("Producer_" + i, queue); - executorService.submit(() -> { - while (true) { - producer.produce(); - } - }); + public static void main(String[] args) { + + var queue = new ItemQueue(); + + var executorService = Executors.newFixedThreadPool(5); + for (var i = 0; i < 2; i++) { + + final var producer = new Producer("Producer_" + i, queue); + executorService.submit(() -> { + while (true) { + producer.produce(); + } + }); + } + + for (var i = 0; i < 3; i++) { + final var consumer = new Consumer("Consumer_" + i, queue); + executorService.submit(() -> { + while (true) { + consumer.consume(); + } + }); + } + + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + executorService.shutdownNow(); + } catch (InterruptedException e) { + LOGGER.error("Error waiting for ExecutorService shutdown"); + } } +``` -for (var i = 0; i < 3; i++) { - final var consumer = new Consumer("Consumer_" + i, queue); - executorService.submit(() -> { - while (true) { - consumer.consume(); - } - }); -} +Program output: ``` +08:10:08.008 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [0] produced by [Producer_1] +08:10:08.008 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [0] produced by [Producer_0] +08:10:08.517 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [1] produced by [Producer_0] +08:10:08.952 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [1] produced by [Producer_1] +08:10:09.208 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [2] produced by [Producer_0] +08:10:09.354 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [2] produced by [Producer_1] +08:10:10.214 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [3] produced by [Producer_1] +08:10:10.585 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [3] produced by [Producer_0] +08:10:11.530 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [4] produced by [Producer_1] +08:10:11.682 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [4] produced by [Producer_0] +08:10:11.781 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [5] produced by [Producer_0] +08:10:12.209 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [5] produced by [Producer_1] +08:10:13.045 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [6] produced by [Producer_0] +08:10:13.861 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [6] produced by [Producer_1] +08:10:14.739 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [7] produced by [Producer_1] +08:10:14.842 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [7] produced by [Producer_0] +08:10:15.975 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [8] produced by [Producer_0] +08:10:16.378 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [8] produced by [Producer_1] +08:10:16.967 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [9] produced by [Producer_0] +08:10:17.417 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [9] produced by [Producer_1] +08:10:17.483 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [10] produced by [Producer_1] +``` + +## Detailed Explanation of Producer-Consumer Pattern with Real-World Examples + +![Producer-Consumer](./etc/producer-consumer.png "Producer-Consumer") + +## When to Use the Producer-Consumer Pattern in Java + +* When you need to manage a buffer or queue where producers add data and consumers take data, often in a multithreaded environment. +* When decoupling the production and consumption of data is beneficial for the application's design, performance, or maintainability. +* Suitable for scenarios requiring synchronized access to a shared resource or data structure. + +## Real-World Applications of Producer-Consumer Pattern in Java + +* Thread pools where worker threads act as consumers processing tasks produced by another thread. +* Logging frameworks where log messages are produced by various parts of an application and consumed by a logging service. +* Message queues in distributed systems for asynchronous communication between services. + +## Benefits and Trade-offs of Producer-Consumer Pattern + +Benefits: + +* Decoupling: Producers and consumers can operate independently, simplifying the system design. +* Improved Performance: Allows multiple producer and consumer threads to work concurrently, improving throughput. +* Flexibility: Easily extendable to add more producers or consumers without significant changes to the existing system. + +Trade-offs: + +* Complexity: Requires careful handling of synchronization and potential deadlocks. +* Resource Management: Properly managing the buffer size to avoid overflow or underflow conditions. +## Related Java Design Patterns -## Class diagram -![alt text](./etc/producer-consumer.png "Producer Consumer") +* [Observer](https://java-design-patterns.com/patterns/observer/): While both deal with notifying or handling events, the Observer pattern is more about event subscription and notification, whereas Producer-Consumer focuses on decoupled data production and consumption. +* [Thread Pool](https://java-design-patterns.com/patterns/thread-pool/): Uses a similar decoupling approach where tasks are produced and consumed by a pool of worker threads. -## Applicability -Use the Producer Consumer idiom when +## References and Credits -* Decouple system by separate work in two process produce and consume. -* Addresses the issue of different timing require to produce work or consuming work +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) diff --git a/producer-consumer/pom.xml b/producer-consumer/pom.xml index f82049cd621f..41eda2a36e98 100644 --- a/producer-consumer/pom.xml +++ b/producer-consumer/pom.xml @@ -34,6 +34,14 @@ producer-consumer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java index 81ed325baff7..22aad7002488 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java @@ -54,20 +54,22 @@ public static void main(String[] args) { for (var i = 0; i < 2; i++) { final var producer = new Producer("Producer_" + i, queue); - executorService.submit(() -> { - while (true) { - producer.produce(); - } - }); + executorService.submit( + () -> { + while (true) { + producer.produce(); + } + }); } for (var i = 0; i < 3; i++) { final var consumer = new Consumer("Consumer_" + i, queue); - executorService.submit(() -> { - while (true) { - consumer.consume(); - } - }); + executorService.submit( + () -> { + while (true) { + consumer.consume(); + } + }); } executorService.shutdown(); diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java index 1d26f2b4a5fa..4574473dbab8 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Class responsible for consume the {@link Item} produced by {@link Producer}. - */ +/** Class responsible for consume the {@link Item} produced by {@link Producer}. */ @Slf4j public class Consumer { @@ -41,13 +39,10 @@ public Consumer(String name, ItemQueue queue) { this.queue = queue; } - /** - * Consume item from the queue. - */ + /** Consume item from the queue. */ public void consume() throws InterruptedException { var item = queue.take(); - LOGGER.info("Consumer [{}] consume item [{}] produced by [{}]", name, - item.id(), item.producer()); - + LOGGER.info( + "Consumer [{}] consume item [{}] produced by [{}]", name, item.id(), item.producer()); } } diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java index 51353ee7a785..b670086f6f20 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java @@ -24,8 +24,5 @@ */ package com.iluwatar.producer.consumer; -/** - * Class take part of an {@link Producer}-{@link Consumer} exchange. - */ -public record Item(String producer, int id) { -} +/** Class take part of an {@link Producer}-{@link Consumer} exchange. */ +public record Item(String producer, int id) {} diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java index 7fd3debad9fc..cdfff3f996f4 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java @@ -27,9 +27,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -/** - * Class as a channel for {@link Producer}-{@link Consumer} exchange. - */ +/** Class as a channel for {@link Producer}-{@link Consumer} exchange. */ public class ItemQueue { private final BlockingQueue queue; @@ -48,5 +46,4 @@ public Item take() throws InterruptedException { return queue.take(); } - } diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java index b0d4bd3845ef..a28a9e78ddca 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java @@ -45,9 +45,7 @@ public Producer(String name, ItemQueue queue) { this.queue = queue; } - /** - * Put item in the queue. - */ + /** Put item in the queue. */ public void produce() throws InterruptedException { var item = new Item(name, itemId++); diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java index 5dbf70ade854..aeb12f59b71b 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.producer.consumer; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java index 553307523d6e..2f65d8e7540f 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java @@ -31,11 +31,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/27/15 - 11:01 PM - * - * @author Jeroen Meulemeester - */ +/** ConsumerTest */ class ConsumerTest { private static final int ITEM_COUNT = 5; @@ -56,5 +52,4 @@ void testConsume() throws Exception { verify(queue, times(ITEM_COUNT)).take(); } - } diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java index 73d4424a6f89..3aef8bfe4991 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java @@ -33,24 +33,21 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 12:12 AM - * - * @author Jeroen Meulemeester - */ +/** ProducerTest */ class ProducerTest { @Test void testProduce() { - assertTimeout(ofMillis(6000), () -> { - final var queue = mock(ItemQueue.class); - final var producer = new Producer("producer", queue); + assertTimeout( + ofMillis(6000), + () -> { + final var queue = mock(ItemQueue.class); + final var producer = new Producer("producer", queue); - producer.produce(); - verify(queue).put(any(Item.class)); + producer.produce(); + verify(queue).put(any(Item.class)); - verifyNoMoreInteractions(queue); - }); + verifyNoMoreInteractions(queue); + }); } - -} \ No newline at end of file +} diff --git a/promise/README.md b/promise/README.md index 82442dc6cb28..76b67e2fb58e 100644 --- a/promise/README.md +++ b/promise/README.md @@ -1,36 +1,33 @@ --- -title: Promise +title: "Promise Pattern in Java: Streamlining Async Tasks for Better Performance" +shortTitle: Promise +description: "Explore the Promise design pattern in Java, ideal for managing asynchronous operations efficiently. Learn how it enhances code readability and maintainability with practical examples and detailed explanations." category: Concurrency language: en tag: - - Reactive + - Asynchronous + - Decoupling + - Messaging + - Synchronization + - Thread management --- ## Also known as -CompletableFuture +* Deferred +* Future -## Intent +## Intent of Promise Design Pattern -A Promise represents a proxy for a value not necessarily known when the promise is created. It -allows you to associate dependent promises to an asynchronous action's eventual success value or -failure reason. Promises are a way to write async code that still appears as though it is executing -in a synchronous way. +The Promise design pattern is used to handle asynchronous operations by providing a placeholder for a result that is initially unknown but will be resolved in the future. -## Explanation +## Detailed Explanation of Promise Pattern with Real-World Examples -The Promise object is used for asynchronous computations. A Promise represents an operation that -hasn't completed yet, but is expected in the future. +Real-world example -Promises provide a few advantages over callback objects: - * Functional composition and error handling. - * Prevents callback hell and provides callback aggregation. - -Real world example - -> We are developing a software solution that downloads files and calculates the number of lines and -> character frequencies in those files. Promise is an ideal solution to make the code concise and -> easy to understand. +> In an online pizza ordering system, when a customer places an order, the system immediately acknowledges the order and provides a tracking number (the promise). The pizza preparation and delivery process happens asynchronously in the background. The customer can check the status of their order at any time using the tracking number. Once the pizza is prepared and out for delivery, the customer receives a notification (promise resolved) about the delivery status. If there are any issues, such as an unavailable ingredient or delivery delay, the customer is notified about the error (promise rejected). +> +> This analogy illustrates how the Promise design pattern manages asynchronous tasks, decoupling the initial request from the eventual outcome, and handling both results and errors efficiently. In plain words @@ -38,274 +35,138 @@ In plain words Wikipedia says -> In computer science, future, promise, delay, and deferred refer to constructs used for -> synchronizing program execution in some concurrent programming languages. They describe an object -> that acts as a proxy for a result that is initially unknown, usually because the computation of -> its value is not yet complete. +> In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is not yet complete. -**Programmatic Example** +## Programmatic Example of Promise Pattern in Java -In the example a file is downloaded and its line count is calculated. The calculated line count is -then consumed and printed on console. +The Promise design pattern is a software design pattern that's often used in concurrent programming to handle asynchronous operations. It represents a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. -Let's first introduce a support class we need for implementation. Here's `PromiseSupport`. +In the provided example, a Promise is used to download files and perform operations like line counting and character frequency analysis asynchronously, showcasing the pattern's utility in practical applications. ```java -class PromiseSupport implements Future { - - private static final Logger LOGGER = LoggerFactory.getLogger(PromiseSupport.class); - - private static final int RUNNING = 1; - private static final int FAILED = 2; - private static final int COMPLETED = 3; +@Slf4j +public class App { - private final Object lock; - - private volatile int state = RUNNING; - private T value; - private Exception exception; - - PromiseSupport() { - this.lock = new Object(); - } - - void fulfill(T value) { - this.value = value; - this.state = COMPLETED; - synchronized (lock) { - lock.notifyAll(); - } - } - - void fulfillExceptionally(Exception exception) { - this.exception = exception; - this.state = FAILED; - synchronized (lock) { - lock.notifyAll(); - } - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return false; - } + private static final String DEFAULT_URL = + "https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md"; + private final ExecutorService executor; - @Override - public boolean isCancelled() { - return false; + private App() { + // Create a thread pool with 2 threads + executor = Executors.newFixedThreadPool(2); } - @Override - public boolean isDone() { - return state > RUNNING; + public static void main(String[] args) { + var app = new App(); + app.promiseUsage(); } - @Override - public T get() throws InterruptedException, ExecutionException { - synchronized (lock) { - while (state == RUNNING) { - lock.wait(); - } - } - if (state == COMPLETED) { - return value; - } - throw new ExecutionException(exception); + private void promiseUsage() { + calculateLineCount(); + calculateLowestFrequencyChar(); } - @Override - public T get(long timeout, TimeUnit unit) throws ExecutionException { - synchronized (lock) { - while (state == RUNNING) { - try { - lock.wait(unit.toMillis(timeout)); - } catch (InterruptedException e) { - LOGGER.warn("Interrupted!", e); - Thread.currentThread().interrupt(); + private void calculateLowestFrequencyChar() { + // Create a promise to calculate the lowest frequency character + lowestFrequencyChar().thenAccept( + charFrequency -> { + LOGGER.info("Char with lowest frequency is: {}", charFrequency); } - } - } - - if (state == COMPLETED) { - return value; - } - throw new ExecutionException(exception); - } -} -``` - -With `PromiseSupport` in place we can implement the actual `Promise`. - -```java -public class Promise extends PromiseSupport { - - private Runnable fulfillmentAction; - private Consumer exceptionHandler; - - public Promise() { + ); } - @Override - public void fulfill(T value) { - super.fulfill(value); - postFulfillment(); - } - - @Override - public void fulfillExceptionally(Exception exception) { - super.fulfillExceptionally(exception); - handleException(exception); - postFulfillment(); - } - - private void handleException(Exception exception) { - if (exceptionHandler == null) { - return; - } - exceptionHandler.accept(exception); - } - - private void postFulfillment() { - if (fulfillmentAction == null) { - return; - } - fulfillmentAction.run(); - } - - public Promise fulfillInAsync(final Callable task, Executor executor) { - executor.execute(() -> { - try { - fulfill(task.call()); - } catch (Exception ex) { - fulfillExceptionally(ex); - } - }); - return this; - } - - public Promise thenAccept(Consumer action) { - var dest = new Promise(); - fulfillmentAction = new ConsumeAction(this, dest, action); - return dest; - } - - public Promise onError(Consumer exceptionHandler) { - this.exceptionHandler = exceptionHandler; - return this; - } - - public Promise thenApply(Function func) { - Promise dest = new Promise<>(); - fulfillmentAction = new TransformAction<>(this, dest, func); - return dest; + private void calculateLineCount() { + // Create a promise to calculate the line count + countLines().thenAccept( + count -> { + LOGGER.info("Line count is: {}", count); + } + ); } - private class ConsumeAction implements Runnable { - - private final Promise src; - private final Promise dest; - private final Consumer action; - - private ConsumeAction(Promise src, Promise dest, Consumer action) { - this.src = src; - this.dest = dest; - this.action = action; - } - - @Override - public void run() { - try { - action.accept(src.get()); - dest.fulfill(null); - } catch (Throwable throwable) { - dest.fulfillExceptionally((Exception) throwable.getCause()); - } - } + private Promise lowestFrequencyChar() { + // Create a promise to calculate the character frequency and then find the lowest frequency character + return characterFrequency().thenApply(Utility::lowestFrequencyChar); } - private class TransformAction implements Runnable { - - private final Promise src; - private final Promise dest; - private final Function func; - - private TransformAction(Promise src, Promise dest, Function func) { - this.src = src; - this.dest = dest; - this.func = func; - } - - @Override - public void run() { - try { - dest.fulfill(func.apply(src.get())); - } catch (Throwable throwable) { - dest.fulfillExceptionally((Exception) throwable.getCause()); - } - } + private Promise> characterFrequency() { + // Create a promise to download a file and then calculate the character frequency + return download(DEFAULT_URL).thenApply(Utility::characterFrequency); } -} -``` - -Now we can show the full example in action. Here's how to download and count the number of lines in -a file using `Promise`. - -```java - countLines().thenAccept( - count -> { - LOGGER.info("Line count is: {}", count); - taskCompleted(); - } - ); private Promise countLines() { + // Create a promise to download a file and then count the lines return download(DEFAULT_URL).thenApply(Utility::countLines); } private Promise download(String urlString) { + // Create a promise to download a file return new Promise() .fulfillInAsync( () -> Utility.downloadFile(urlString), executor) .onError( throwable -> { - throwable.printStackTrace(); - taskCompleted(); + LOGGER.error("An error occurred: ", throwable); } ); } +} ``` -## Class diagram +In this code, the `Promise` class is used to create promises for various operations. The `thenApply` method is used to chain promises, meaning that the result of one promise is used as the input for the next promise. The `thenAccept` method is used to handle the result of a promise. The `fulfillInAsync` method is used to fulfill a promise asynchronously, and the `onError` method is used to handle any errors that occur while fulfilling the promise. -![alt text](./etc/promise.png "Promise") +Program output: -## Applicability +``` +08:19:33.036 [pool-1-thread-2] INFO com.iluwatar.promise.Utility -- Downloading contents from url: https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md +08:19:33.036 [pool-1-thread-1] INFO com.iluwatar.promise.Utility -- Downloading contents from url: https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/promise/README.md +08:19:33.419 [pool-1-thread-2] INFO com.iluwatar.promise.Utility -- File downloaded at: /var/folders/sg/9_st37nn5hq_bfhp8hw2dcrw0000gp/T/promise_pattern12403918065536844551.tmp +08:19:33.419 [pool-1-thread-1] INFO com.iluwatar.promise.Utility -- File downloaded at: /var/folders/sg/9_st37nn5hq_bfhp8hw2dcrw0000gp/T/promise_pattern11215446820862558571.tmp +08:19:33.419 [pool-1-thread-1] INFO com.iluwatar.promise.App -- Line count is: 164 +08:19:33.426 [pool-1-thread-2] INFO com.iluwatar.promise.App -- Char with lowest frequency is: ’ +``` + +## When to Use the Promise Pattern in Java -Promise pattern is applicable in concurrent programming when some work needs to be done -asynchronously and: +* When you need to perform asynchronous tasks and handle their results or errors at a later point. +* In scenarios where tasks can be executed in parallel and their outcomes need to be handled once they are completed. +* Suitable for improving the readability and maintainability of asynchronous code. -* Code maintainability and readability suffers due to callback hell. -* You need to compose promises and need better error handling for asynchronous tasks. -* You want to use functional style of programming. +## Promise Pattern Java Tutorials +* [Functional-Style Callbacks Using Java 8's CompletableFuture (InfoQ)](https://www.infoq.com/articles/Functional-Style-Callbacks-Using-CompletableFuture) +* [Guide To CompletableFuture (Baeldung)](https://www.baeldung.com/java-completablefuture) +* [You are missing the point to Promises (Domenic Denicola)](https://gist.github.com/domenic/3889970) -## Real world examples +## Real-World Applications of Promise Pattern in Java -* [java.util.concurrent.CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) +* Java's CompletableFuture and Future classes. +* JavaScript’s Promise object for managing asynchronous operations. +* Many asynchronous frameworks and libraries such as RxJava and Vert.x. * [Guava ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained) -## Related Patterns +## Benefits and Trade-offs of Promise Pattern + +Benefits: + +* Improved Readability: Simplifies complex asynchronous code, making it easier to understand and maintain. +* Decoupling: Decouples the code that initiates the asynchronous operation from the code that processes the result. +* Error Handling: Provides a unified way to handle both results and errors from asynchronous operations. + +Trade-offs: - * [Async Method Invocation](https://java-design-patterns.com/patterns/async-method-invocation/) - * [Callback](https://java-design-patterns.com/patterns/callback/) +* Complexity: Can add complexity to the codebase if overused or misused. +* Debugging: Asynchronous code can be harder to debug compared to synchronous code due to the non-linear flow of execution. -## Tutorials +## Related Java Design Patterns -* [Guide To CompletableFuture](https://www.baeldung.com/java-completablefuture) +* [Observer](https://java-design-patterns.com/patterns/observer/): Promises can be used in conjunction with the Observer pattern to notify subscribers about the completion of asynchronous operations. +* [Callback](https://java-design-patterns.com/patterns/callback/): Promises often replace callback mechanisms by providing a more structured and readable way to handle asynchronous results. +* [Async Method Invocation](https://java-design-patterns.com/patterns/async-method-invocation/): Promises are often used to handle the results of asynchronous method invocations, allowing for non-blocking execution and result handling. -## Credits +## References and Credits -* [You are missing the point to Promises](https://gist.github.com/domenic/3889970) -* [Functional style callbacks using CompletableFuture](https://www.infoq.com/articles/Functional-Style-Callbacks-Using-CompletableFuture) -* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://www.amazon.com/gp/product/1617291994/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617291994&linkId=995af46887bb7b65e6c788a23eaf7146) -* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://www.amazon.com/gp/product/1617293563/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617293563&linkId=f70fe0d3e1efaff89554a6479c53759c) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3QCmGXs) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://amzn.to/3VhwetF) diff --git a/promise/pom.xml b/promise/pom.xml index 7a104ee3bf14..b43d3f57a950 100644 --- a/promise/pom.xml +++ b/promise/pom.xml @@ -34,6 +34,14 @@ promise + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/promise/src/main/java/com/iluwatar/promise/App.java b/promise/src/main/java/com/iluwatar/promise/App.java index 3ac7b8363467..7bfed9dc9aab 100644 --- a/promise/src/main/java/com/iluwatar/promise/App.java +++ b/promise/src/main/java/com/iluwatar/promise/App.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import lombok.extern.slf4j.Slf4j; @@ -36,26 +35,28 @@ * The Promise object is used for asynchronous computations. A Promise represents an operation that * hasn't completed yet, but is expected in the future. * - *

A Promise represents a proxy for a value not necessarily known when the promise is created. - * It allows you to associate dependent promises to an asynchronous action's eventual success value - * or failure reason. This lets asynchronous methods return values like synchronous methods: instead - * of the final value, the asynchronous method returns a promise of having a value at some point in - * the future. + *

A Promise represents a proxy for a value not necessarily known when the promise is created. It + * allows you to associate dependent promises to an asynchronous action's eventual success value or + * failure reason. This lets asynchronous methods return values like synchronous methods: instead of + * the final value, the asynchronous method returns a promise of having a value at some point in the + * future. * *

Promises provide a few advantages over callback objects: + * *

    - *
  • Functional composition and error handling - *
  • Prevents callback hell and provides callback aggregation + *
  • Functional composition and error handling + *
  • Prevents callback hell and provides callback aggregation *
* *

In this application the usage of promise is demonstrated with two examples: + * *

    - *
  • Count Lines: In this example a file is downloaded and its line count is calculated. - * The calculated line count is then consumed and printed on console. - *
  • Lowest Character Frequency: In this example a file is downloaded and its lowest frequency - * character is found and printed on console. This happens via a chain of promises, we start with - * a file download promise, then a promise of character frequency, then a promise of lowest - * frequency character which is finally consumed and result is printed on console. + *
  • Count Lines: In this example a file is downloaded and its line count is calculated. The + * calculated line count is then consumed and printed on console. + *
  • Lowest Character Frequency: In this example a file is downloaded and its lowest frequency + * character is found and printed on console. This happens via a chain of promises, we start + * with a file download promise, then a promise of character frequency, then a promise of + * lowest frequency character which is finally consumed and result is printed on console. *
* * @see CompletableFuture @@ -78,7 +79,6 @@ private App() { * * @param args arguments * @throws InterruptedException if main thread is interrupted. - * @throws ExecutionException if an execution error occurs. */ public static void main(String[] args) throws InterruptedException { var app = new App(); @@ -100,12 +100,12 @@ private void promiseUsage() { * consume the result in a Consumer */ private void calculateLowestFrequencyChar() { - lowestFrequencyChar().thenAccept( - charFrequency -> { - LOGGER.info("Char with lowest frequency is: {}", charFrequency); - taskCompleted(); - } - ); + lowestFrequencyChar() + .thenAccept( + charFrequency -> { + LOGGER.info("Char with lowest frequency is: {}", charFrequency); + taskCompleted(); + }); } /* @@ -113,17 +113,17 @@ private void calculateLowestFrequencyChar() { * in a Consumer */ private void calculateLineCount() { - countLines().thenAccept( - count -> { - LOGGER.info("Line count is: {}", count); - taskCompleted(); - } - ); + countLines() + .thenAccept( + count -> { + LOGGER.info("Line count is: {}", count); + taskCompleted(); + }); } /* * Calculate the character frequency of a file and when that promise is fulfilled, - * then promise to apply function to calculate lowest character frequency. + * then promise to apply function to calculate the lowest character frequency. */ private Promise lowestFrequencyChar() { return characterFrequency().thenApply(Utility::lowestFrequencyChar); @@ -151,14 +151,12 @@ private Promise countLines() { */ private Promise download(String urlString) { return new Promise() - .fulfillInAsync( - () -> Utility.downloadFile(urlString), executor) + .fulfillInAsync(() -> Utility.downloadFile(urlString), executor) .onError( throwable -> { - throwable.printStackTrace(); + LOGGER.error("An error occurred: ", throwable); taskCompleted(); - } - ); + }); } private void stop() throws InterruptedException { diff --git a/promise/src/main/java/com/iluwatar/promise/Promise.java b/promise/src/main/java/com/iluwatar/promise/Promise.java index 27ce5899f12d..546e82a9bffd 100644 --- a/promise/src/main/java/com/iluwatar/promise/Promise.java +++ b/promise/src/main/java/com/iluwatar/promise/Promise.java @@ -44,9 +44,7 @@ public class Promise extends PromiseSupport { private Runnable fulfillmentAction; private Consumer exceptionHandler; - /** - * Creates a promise that will be fulfilled in future. - */ + /** Creates a promise that will be fulfilled in the future. */ public Promise() { // Empty constructor } @@ -66,7 +64,7 @@ public void fulfill(T value) { * Fulfills the promise with exception due to error in execution. * * @param exception the exception will be wrapped in {@link ExecutionException} when accessing the - * value using {@link #get()}. + * value using {@link #get()}. */ @Override public void fulfillExceptionally(Exception exception) { @@ -93,18 +91,19 @@ private void postFulfillment() { * Executes the task using the executor in other thread and fulfills the promise returned once the * task completes either successfully or with an exception. * - * @param task the task that will provide the value to fulfill the promise. + * @param task the task that will provide the value to fulfill the promise. * @param executor the executor in which the task should be run. * @return a promise that represents the result of running the task provided. */ public Promise fulfillInAsync(final Callable task, Executor executor) { - executor.execute(() -> { - try { - fulfill(task.call()); - } catch (Exception ex) { - fulfillExceptionally(ex); - } - }); + executor.execute( + () -> { + try { + fulfill(task.call()); + } catch (Exception ex) { + fulfillExceptionally(ex); + } + }); return this; } @@ -125,7 +124,7 @@ public Promise thenAccept(Consumer action) { * Set the exception handler on this promise. * * @param exceptionHandler a consumer that will handle the exception occurred while fulfilling the - * promise. + * promise. * @return this */ public Promise onError(Consumer exceptionHandler) { @@ -198,4 +197,4 @@ public void run() { } } } -} \ No newline at end of file +} diff --git a/promise/src/main/java/com/iluwatar/promise/Utility.java b/promise/src/main/java/com/iluwatar/promise/Utility.java index 0976c8c751bc..903dcfe8e11b 100644 --- a/promise/src/main/java/com/iluwatar/promise/Utility.java +++ b/promise/src/main/java/com/iluwatar/promise/Utility.java @@ -39,9 +39,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -/** - * Utility to perform various operations. - */ +/** Utility to perform various operations. */ @Slf4j public class Utility { @@ -53,12 +51,13 @@ public class Utility { */ public static Map characterFrequency(String fileLocation) { try (var bufferedReader = new BufferedReader(new FileReader(fileLocation))) { - return bufferedReader.lines() + return bufferedReader + .lines() .flatMapToInt(String::chars) .mapToObj(x -> (char) x) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); } catch (IOException ex) { - ex.printStackTrace(); + LOGGER.error("An error occurred: ", ex); } return Collections.emptyMap(); } @@ -69,9 +68,7 @@ public static Map characterFrequency(String fileLocation) { * @return the character, {@code Optional.empty()} otherwise. */ public static Character lowestFrequencyChar(Map charFrequency) { - return charFrequency - .entrySet() - .stream() + return charFrequency.entrySet().stream() .min(Comparator.comparingLong(Entry::getValue)) .map(Entry::getKey) .orElseThrow(); @@ -86,7 +83,7 @@ public static Integer countLines(String fileLocation) { try (var bufferedReader = new BufferedReader(new FileReader(fileLocation))) { return (int) bufferedReader.lines().count(); } catch (IOException ex) { - ex.printStackTrace(); + LOGGER.error("An error occurred: ", ex); } return 0; } @@ -101,7 +98,7 @@ public static String downloadFile(String urlString) throws IOException { var url = new URL(urlString); var file = File.createTempFile("promise_pattern", null); try (var bufferedReader = new BufferedReader(new InputStreamReader(url.openStream())); - var writer = new FileWriter(file)) { + var writer = new FileWriter(file)) { String line; while ((line = bufferedReader.readLine()) != null) { writer.write(line); diff --git a/promise/src/test/java/com/iluwatar/promise/AppTest.java b/promise/src/test/java/com/iluwatar/promise/AppTest.java index a3eb8718fe60..ecc2ad88d036 100644 --- a/promise/src/test/java/com/iluwatar/promise/AppTest.java +++ b/promise/src/test/java/com/iluwatar/promise/AppTest.java @@ -24,14 +24,11 @@ */ package com.iluwatar.promise; -import java.util.concurrent.ExecutionException; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test. - */ +import org.junit.jupiter.api.Test; + +/** Application test. */ class AppTest { @Test diff --git a/promise/src/test/java/com/iluwatar/promise/PromiseTest.java b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java index 99bf9aeed9e2..13c01eafd1f1 100644 --- a/promise/src/test/java/com/iluwatar/promise/PromiseTest.java +++ b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java @@ -41,9 +41,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests Promise class. - */ +/** Tests Promise class. */ class PromiseTest { private Executor executor; @@ -66,17 +64,18 @@ void promiseIsFulfilledWithTheResultantValueOfExecutingTheTask() } @Test - void promiseIsFulfilledWithAnExceptionIfTaskThrowsAnException() - throws InterruptedException { + void promiseIsFulfilledWithAnExceptionIfTaskThrowsAnException() throws InterruptedException { testWaitingForeverForPromiseToBeFulfilled(); testWaitingSomeTimeForPromiseToBeFulfilled(); } private void testWaitingForeverForPromiseToBeFulfilled() throws InterruptedException { var promise = new Promise(); - promise.fulfillInAsync(() -> { - throw new RuntimeException("Barf!"); - }, executor); + promise.fulfillInAsync( + () -> { + throw new RuntimeException("Barf!"); + }, + executor); try { promise.get(); @@ -97,9 +96,11 @@ private void testWaitingForeverForPromiseToBeFulfilled() throws InterruptedExcep private void testWaitingSomeTimeForPromiseToBeFulfilled() throws InterruptedException { var promise = new Promise(); - promise.fulfillInAsync(() -> { - throw new RuntimeException("Barf!"); - }, executor); + promise.fulfillInAsync( + () -> { + throw new RuntimeException("Barf!"); + }, + executor); try { promise.get(1000, TimeUnit.SECONDS); @@ -116,15 +117,15 @@ private void testWaitingSomeTimeForPromiseToBeFulfilled() throws InterruptedExce assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); } - } @Test void dependentPromiseIsFulfilledAfterTheConsumerConsumesTheResultOfThisPromise() throws InterruptedException, ExecutionException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenAccept(value -> assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value)); + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenAccept(value -> assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value)); dependentPromise.get(); assertTrue(dependentPromise.isDone()); @@ -134,16 +135,19 @@ void dependentPromiseIsFulfilledAfterTheConsumerConsumesTheResultOfThisPromise() @Test void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() throws InterruptedException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenAccept(value -> { - throw new RuntimeException("Barf!"); - }); + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenAccept( + value -> { + throw new RuntimeException("Barf!"); + }); try { dependentPromise.get(); - fail("Fetching dependent promise should result in exception " - + "if the action threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the action threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); @@ -151,8 +155,9 @@ void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() try { dependentPromise.get(1000, TimeUnit.SECONDS); - fail("Fetching dependent promise should result in exception " - + "if the action threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the action threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); @@ -162,13 +167,14 @@ void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() @Test void dependentPromiseIsFulfilledAfterTheFunctionTransformsTheResultOfThisPromise() throws InterruptedException, ExecutionException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenApply(value -> { - assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value); - return String.valueOf(value); - }); - + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenApply( + value -> { + assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value); + return String.valueOf(value); + }); assertEquals(String.valueOf(NumberCrunchingTask.CRUNCHED_NUMBER), dependentPromise.get()); assertTrue(dependentPromise.isDone()); @@ -178,16 +184,19 @@ void dependentPromiseIsFulfilledAfterTheFunctionTransformsTheResultOfThisPromise @Test void dependentPromiseIsFulfilledWithAnExceptionIfTheFunctionThrowsException() throws InterruptedException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenApply(value -> { - throw new RuntimeException("Barf!"); - }); + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenApply( + value -> { + throw new RuntimeException("Barf!"); + }); try { dependentPromise.get(); - fail("Fetching dependent promise should result in exception " - + "if the function threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the function threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); @@ -195,8 +204,9 @@ void dependentPromiseIsFulfilledWithAnExceptionIfTheFunctionThrowsException() try { dependentPromise.get(1000, TimeUnit.SECONDS); - fail("Fetching dependent promise should result in exception " - + "if the function threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the function threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); diff --git a/property/README.md b/property/README.md index 19fe9c90ea69..0bfd89183531 100644 --- a/property/README.md +++ b/property/README.md @@ -1,131 +1,200 @@ --- -title: Property -category: Creational +title: "Property Pattern in Java: Enhancing Flexibility with Dynamic Attribute Management" +shortTitle: Property +description: "Explore how the Property design pattern facilitates dynamic property management in Java objects, enabling runtime modifications without altering class structure. Ideal for developers looking to enhance flexibility and maintainability in their code." +category: Behavioral language: en tag: - - Instantiation + - Abstraction + - Encapsulation + - Interface + - Object composition + - Polymorphism --- -## Intent -Create hierarchy of objects and new objects using already existing -objects as parents. +## Also known as -## Explanation +* Dynamic Properties +* Property Bag -Real-world example +## Intent of Property Design Pattern + +The Property design pattern in Java allows dynamic addition, removal, or modification of object properties, offering a flexible solution for developers to customize object attributes at runtime. + +## Detailed Explanation of Property Pattern with Real-World Examples -> In the mystical land of "Elandria", adventurers can harness the power of ancient relics to customize their abilities. Each relic represents a unique property or skill. As adventurers explore, they discover and integrate new relics, dynamically enhancing their skills based on the relics they possess. +Real-world example -> Consider a modern software used in designing and customizing smartphones. Designers can choose from a variety of components such as processor type, camera specs, battery capacity, and more. Each component represents a property of the smartphone. As technology evolves and new components become available, designers can seamlessly add or replace properties to create a unique smartphone configuration without redefining the core design structure. +> Imagine a custom burger ordering system at a restaurant. Each burger starts with a basic configuration (bun, patty), but customers can add or remove various ingredients (cheese, lettuce, tomato, sauces) as they wish. The restaurant's ordering system uses the Property design pattern to handle this flexibility. Each burger object dynamically updates its list of properties (ingredients) based on customer choices, allowing for a wide variety of custom burgers without needing a fixed class structure for every possible combination. This ensures the system can adapt to any new ingredient without altering the core burger class. In plain words > Define and manage a dynamic set of properties for an object, allowing customization without altering its structure. -**Programmatic Example** -```java -import java.util.HashMap; -import java.util.Map; +## Programmatic Example of Property Pattern in Java -// Enumeration for possible properties or statistics a character can have -enum Stats { - AGILITY, ATTACK_POWER, ARMOR, INTELLECT, SPIRIT, FURY, RAGE; -} +The Property design pattern, also known as Prototype inheritance, is a pattern that allows objects to be created from other objects, forming object hierarchies. This pattern is particularly useful when you want to create a new object that is a slight variation of an existing object. -// Enumeration for different types or classes of characters -enum Type { - WARRIOR, MAGE, ROGUE; +In the given code, the Property pattern is used to create different types of characters, each with their own unique properties. + +```java +// The Character class represents a character in a game. It has a type and a set of properties. +public class Character { + private Type type; + private Map properties; + + // The Character can be created with a type and a prototype. The new character will have the same properties as the prototype. + public Character(Type type, Character prototype) { + this.type = type; + this.properties = new HashMap<>(prototype.properties); + } + + // Properties can be added or modified using the set method. + public void set(Stats stat, int value) { + properties.put(stat, value); + } + + // Properties can be removed using the remove method. + public void remove(Stats stat) { + properties.remove(stat); + } + + // The has method checks if a property is present. + public boolean has(Stats stat) { + return properties.containsKey(stat); + } + + // The get method retrieves the value of a property. + public Integer get(Stats stat) { + return properties.get(stat); + } } -// Interface defining prototype operations on a character -interface Prototype { - Integer get(Stats stat); - boolean has(Stats stat); - void set(Stats stat, Integer value); - void remove(Stats stat); +// The Stats enum represents the different properties a character can have. +public enum Stats { + STRENGTH, AGILITY, ARMOR, ATTACK_POWER, INTELLECT, SPIRIT, RAGE, ENERGY } -// Implementation of the Character class -class Character implements Prototype { - private String name; - private Type type; - private Map properties = new HashMap<>(); - - public Character() {} - - public Character(Type type, Prototype prototype) { - this.type = type; - for (Stats stat : Stats.values()) { - if (prototype.has(stat)) { - this.set(stat, prototype.get(stat)); - } - } - } - - public Character(String name, Type type) { - this.name = name; - this.type = type; - } - - @Override - public Integer get(Stats stat) { - return properties.get(stat); - } - - @Override - public boolean has(Stats stat) { - return properties.containsKey(stat); - } - - @Override - public void set(Stats stat, Integer value) { - properties.put(stat, value); - } - - @Override - public void remove(Stats stat) { - properties.remove(stat); - } - - @Override - public String toString() { - return "Character{name='" + name + "', type=" + type + ", properties=" + properties + '}'; - } +// The Type enum represents the different types of characters. +public enum Type { + MAGE, WARRIOR, ROGUE } +``` -// Main class to demonstrate the pattern -public class PropertyPatternDemo { - public static void main(String[] args) { - // Create a prototype character - Character prototypeWarrior = new Character("Proto Warrior", Type.WARRIOR); - prototypeWarrior.set(Stats.ATTACK_POWER, 10); - prototypeWarrior.set(Stats.ARMOR, 15); - - // Create a new character using the prototype - Character newWarrior = new Character(Type.WARRIOR, prototypeWarrior); - newWarrior.set(Stats.AGILITY, 5); - - System.out.println(prototypeWarrior); - System.out.println(newWarrior); - } +In the `main` method, we create a prototype character and then create different types of characters based on the prototype: + +```java +public static void main(String[] args) { + // Create a prototype character with default properties + var charProto = new Character(); + charProto.set(Stats.STRENGTH, 10); + charProto.set(Stats.AGILITY, 10); + charProto.set(Stats.ARMOR, 10); + charProto.set(Stats.ATTACK_POWER, 10); + + // Create a mage character based on the prototype and add mage-specific properties + var mageProto = new Character(Type.MAGE, charProto); + mageProto.set(Stats.INTELLECT, 15); + mageProto.set(Stats.SPIRIT, 10); + + // Create a warrior character based on the prototype and add warrior-specific properties + var warProto = new Character(Type.WARRIOR, charProto); + warProto.set(Stats.RAGE, 15); + warProto.set(Stats.ARMOR, 15); // boost default armor for warrior + + // Create a rogue character based on the prototype and add rogue-specific properties + var rogueProto = new Character(Type.ROGUE, charProto); + rogueProto.set(Stats.ENERGY, 15); + rogueProto.set(Stats.AGILITY, 15); // boost default agility for rogue + + // Create specific characters based on the prototypes + var mag = new Character("Player_1", mageProto); + var warrior = new Character("Player_2", warProto); + var rogue = new Character("Player_3", rogueProto); } ``` Program output: ``` -Character{name='Proto Warrior', type=WARRIOR, properties={ARMOR=15, ATTACK_POWER=10}} -Character{name='null', type=WARRIOR, properties={ARMOR=15, AGILITY=5, ATTACK_POWER=10}} +08:27:52.567 [main] INFO com.iluwatar.property.App -- Player: Player_1 +Character type: MAGE +Stats: + - AGILITY:10 + - STRENGTH:10 + - ATTACK_POWER:10 + - ARMOR:8 + - INTELLECT:15 + - SPIRIT:10 + +08:27:52.569 [main] INFO com.iluwatar.property.App -- Player: Player_2 +Character type: WARRIOR +Stats: + - AGILITY:10 + - STRENGTH:10 + - ATTACK_POWER:10 + - ARMOR:15 + - RAGE:15 + +08:27:52.569 [main] INFO com.iluwatar.property.App -- Player: Player_3 +Character type: ROGUE +Stats: + - AGILITY:15 + - STRENGTH:10 + - ATTACK_POWER:10 + - ARMOR:10 + - ENERGY:15 + +08:27:52.569 [main] INFO com.iluwatar.property.App -- Player: Player_4 +Character type: ROGUE +Stats: + - AGILITY:15 + - STRENGTH:10 + - ATTACK_POWER:12 + - ARMOR:10 + - ENERGY:15 ``` -## Class diagram -![alt text](./etc/property.png "Property") +This way, we can easily create new characters with different properties without having to create a new class for each type of character. + +## When to Use the Property Pattern in Java -## Applicability Use the Property pattern when -* When you like to have objects with dynamic set of fields and prototype inheritance +* When you need to manage a flexible set of properties without altering the class structure. +* When properties need to be added or removed dynamically at runtime. +* When different instances of a class need different properties. + +## Real-World Applications of Property Pattern in Java + +* Configurations in applications where different entities require different sets of configurable parameters. +* Game development where game entities (like characters or objects) need various attributes that can change during gameplay. +* User profile management systems where user profiles can have dynamic attributes. + +## Benefits and Trade-offs of Property Pattern + +Benefits: + +Employing the Property design pattern enhances + +* Flexibility: Allows for the dynamic addition, removal, and modification of properties. +* Decoupling: Reduces dependencies between classes and their properties. +* Ease of Use: Simplifies the management of properties in large systems. + +Trade-offs: + +* Performance Overhead: Dynamic property management can introduce runtime overhead. +* Complexity: May increase the complexity of the code, making it harder to maintain and understand. +* Type Safety: Reduces type safety since properties are often managed as generic key-value pairs. + +## Related Java Design Patterns + +* [Composite](https://java-design-patterns.com/patterns/composite/): Composite allows a tree structure of objects where each node can be a complex or simple object. Property pattern can be seen as a flattened version, managing properties without hierarchy. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Both patterns enhance an object's behavior, but the Property pattern focuses on adding properties dynamically, while the Decorator adds responsibilities. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Like the Property pattern, the Strategy pattern allows dynamic behavior changes, but Strategy is about changing the algorithm used by an object. -## Real world examples +## References and Credits -* [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) prototype inheritance +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) diff --git a/property/pom.xml b/property/pom.xml index 17d8e35a0bf6..a2d894e2bb04 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -34,6 +34,14 @@ property + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/property/src/main/java/com/iluwatar/property/Character.java b/property/src/main/java/com/iluwatar/property/Character.java index 83506abe28a8..0738260f070b 100644 --- a/property/src/main/java/com/iluwatar/property/Character.java +++ b/property/src/main/java/com/iluwatar/property/Character.java @@ -27,16 +27,14 @@ import java.util.HashMap; import java.util.Map; -/** - * Represents Character in game and his abilities (base stats). - */ +/** Represents Character in game and his abilities (base stats). */ public class Character implements Prototype { - /** - * Enumeration of Character types. - */ + /** Enumeration of Character types. */ public enum Type { - WARRIOR, MAGE, ROGUE + WARRIOR, + MAGE, + ROGUE } private final Prototype prototype; @@ -45,31 +43,30 @@ public enum Type { private String name; private Type type; - /** - * Constructor. - */ + /** Constructor. */ public Character() { - this.prototype = new Prototype() { // Null-value object - @Override - public Integer get(Stats stat) { - return null; - } - - @Override - public boolean has(Stats stat) { - return false; - } - - @Override - public void set(Stats stat, Integer val) { - // Does Nothing - } - - @Override - public void remove(Stats stat) { - // Does Nothing. - } - }; + this.prototype = + new Prototype() { // Null-value object + @Override + public Integer get(Stats stat) { + return null; + } + + @Override + public boolean has(Stats stat) { + return false; + } + + @Override + public void set(Stats stat, Integer val) { + // Does Nothing + } + + @Override + public void remove(Stats stat) { + // Does Nothing. + } + }; } public Character(Type type, Prototype prototype) { @@ -77,9 +74,7 @@ public Character(Type type, Prototype prototype) { this.prototype = prototype; } - /** - * Constructor. - */ + /** Constructor. */ public Character(String name, Character prototype) { this.name = name; this.type = prototype.type; @@ -140,5 +135,4 @@ public String toString() { } return builder.toString(); } - } diff --git a/property/src/main/java/com/iluwatar/property/Prototype.java b/property/src/main/java/com/iluwatar/property/Prototype.java index 9c92095640c6..a353f8a57794 100644 --- a/property/src/main/java/com/iluwatar/property/Prototype.java +++ b/property/src/main/java/com/iluwatar/property/Prototype.java @@ -24,9 +24,7 @@ */ package com.iluwatar.property; -/** - * Interface for prototype inheritance. - */ +/** Interface for prototype inheritance. */ public interface Prototype { Integer get(Stats stat); diff --git a/property/src/main/java/com/iluwatar/property/Stats.java b/property/src/main/java/com/iluwatar/property/Stats.java index 2605f3e4fe58..ab83b80ce23f 100644 --- a/property/src/main/java/com/iluwatar/property/Stats.java +++ b/property/src/main/java/com/iluwatar/property/Stats.java @@ -24,10 +24,14 @@ */ package com.iluwatar.property; -/** - * All possible attributes that Character can have. - */ +/** All possible attributes that Character can have. */ public enum Stats { - - AGILITY, STRENGTH, ATTACK_POWER, ARMOR, INTELLECT, SPIRIT, ENERGY, RAGE + AGILITY, + STRENGTH, + ATTACK_POWER, + ARMOR, + INTELLECT, + SPIRIT, + ENERGY, + RAGE } diff --git a/property/src/test/java/com/iluwatar/property/AppTest.java b/property/src/test/java/com/iluwatar/property/AppTest.java index 761f092089c6..86dd774a4f77 100644 --- a/property/src/test/java/com/iluwatar/property/AppTest.java +++ b/property/src/test/java/com/iluwatar/property/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.property; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/property/src/test/java/com/iluwatar/property/CharacterTest.java b/property/src/test/java/com/iluwatar/property/CharacterTest.java index bbfe5e879d5c..0d3147b11bf9 100644 --- a/property/src/test/java/com/iluwatar/property/CharacterTest.java +++ b/property/src/test/java/com/iluwatar/property/CharacterTest.java @@ -33,15 +33,11 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 7:46 PM - * - * @author Jeroen Meulemeester - */ +/** CharacterTest */ class CharacterTest { @Test - void testPrototypeStats() throws Exception { + void testPrototypeStats() { final var prototype = new Character(); for (final var stat : Stats.values()) { @@ -57,7 +53,6 @@ void testPrototypeStats() throws Exception { assertFalse(prototype.has(stat)); assertNull(prototype.get(stat)); } - } @Test @@ -79,7 +74,8 @@ void testToString() { prototype.set(Stats.ARMOR, 1); prototype.set(Stats.AGILITY, 2); prototype.set(Stats.INTELLECT, 3); - var message = """ + var message = + """ Stats: - AGILITY:2 - ARMOR:1 @@ -89,7 +85,8 @@ void testToString() { final var stupid = new Character(Type.ROGUE, prototype); stupid.remove(Stats.INTELLECT); - String expectedStupidString = """ + String expectedStupidString = + """ Character type: ROGUE Stats: - AGILITY:2 @@ -99,14 +96,14 @@ void testToString() { final var weak = new Character("weak", prototype); weak.remove(Stats.ARMOR); - String expectedWeakString = """ + String expectedWeakString = + """ Player: weak Stats: - AGILITY:2 - INTELLECT:3 """; assertEquals(expectedWeakString, weak.toString()); - } @Test @@ -140,5 +137,4 @@ void testType() { weak.remove(Stats.ARMOR); assertNull(weak.type()); } - -} \ No newline at end of file +} diff --git a/prototype/README.md b/prototype/README.md index 180dd7fd2ba5..c68c5fbc001f 100644 --- a/prototype/README.md +++ b/prototype/README.md @@ -1,26 +1,31 @@ --- -title: Prototype +title: "Prototype Pattern in Java: Mastering Object Cloning for Efficient Instantiation" +shortTitle: Prototype +description: "Explore the Prototype design pattern in Java with a comprehensive guide on its implementation, advantages, and real-world applications. Learn how to efficiently clone objects and manage object creation in your Java applications." category: Creational language: en tag: - - Gang Of Four - - Instantiation + - Gang Of Four + - Instantiation + - Object composition + - Polymorphism --- -## Intent +## Also known as -Specify the kinds of objects to create using a prototypical instance, and create new objects by -copying this prototype. +* Clone -## Explanation +## Intent of Prototype Design Pattern -First, it should be noted that the Prototype pattern is not used to gain performance benefits. It's only -used for creating new objects from prototype instances. +The Prototype pattern is used to specify the kinds of objects to create using a prototypical instance, and create new instances through object cloning. + +## Detailed Explanation of Prototype Pattern with Real-World Examples Real-world example -> Remember Dolly? The sheep that was cloned! Let's not get into the details but the key point here is -> that it is all about cloning. +> Imagine a company that manufactures custom-designed furniture. Instead of creating each piece from scratch every time an order is placed, they keep prototypes of their most popular designs. When a customer places an order for a specific design, the company simply clones the prototype of that design and makes the necessary customizations. This approach saves time and resources as the basic structure and design details are already in place, allowing the company to quickly fulfill orders with consistent quality. +> +> In this scenario, the furniture prototypes act like object prototypes in software, enabling efficient creation of new, customized pieces based on existing models. In plain words @@ -28,18 +33,11 @@ In plain words Wikipedia says -> The prototype pattern is a creational design pattern in software development. It is used when the -> type of objects to create is determined by a prototypical instance, which is cloned to produce new -> objects. - -In short, it allows you to create a copy of an existing object and modify it to your needs, instead -of going through the trouble of creating an object from scratch and setting it up. +> The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects. -**Programmatic Example** +## Programmatic Example of Prototype Pattern in Java -In Java, the prototype pattern is recommended to be implemented as follows. First, create an -interface with a method for cloning objects. In this example, `Prototype` interface accomplishes -this with its `copy` method. +In Java, the prototype pattern is recommended to be implemented as follows. First, create an interface with a method for cloning objects. In this example, `Prototype` interface accomplishes this with its `copy` method. ```java public abstract class Prototype implements Cloneable { @@ -50,19 +48,17 @@ public abstract class Prototype implements Cloneable { } ``` -Our example contains a hierarchy of different creatures. For example, let's look at `Beast` and -`OrcBeast` classes. +Our example contains a hierarchy of different creatures. For example, let's look at `Beast` and `OrcBeast` classes. ```java @EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Beast extends Prototype { - - public Beast(Beast source) { - } - + public Beast(Beast source) {} } +``` +```java @EqualsAndHashCode(callSuper = false) @RequiredArgsConstructor public class OrcBeast extends Beast { @@ -78,24 +74,22 @@ public class OrcBeast extends Beast { public String toString() { return "Orcish wolf attacks with " + weapon; } - } ``` -We don't want to go into too many details, but the full example contains also base classes `Mage` -and `Warlord` and there are specialized implementations for those for elves in addition to orcs. +We don't want to go into too many details, but the full example contains also base classes `Mage` and `Warlord` and there are specialized implementations of those for elves and orcs. -To take full advantage of the prototype pattern, we create `HeroFactory` and `HeroFactoryImpl` -classes to produce different kinds of creatures from prototypes. +To take full advantage of the prototype pattern, we create `HeroFactory` and `HeroFactoryImpl` classes to produce different kinds of creatures from prototypes. ```java public interface HeroFactory { - Mage createMage(); Warlord createWarlord(); Beast createBeast(); } +``` +```java @RequiredArgsConstructor public class HeroFactoryImpl implements HeroFactory { @@ -117,14 +111,14 @@ public class HeroFactoryImpl implements HeroFactory { } ``` -Now, we are able to show the full prototype pattern in action producing new creatures by cloning -existing instances. +Now, we are able to show the full prototype pattern in action producing new creatures by cloning existing instances. ```java +public static void main(String[] args) { var factory = new HeroFactoryImpl( - new ElfMage("cooking"), - new ElfWarlord("cleaning"), - new ElfBeast("protecting") + new ElfMage("cooking"), + new ElfWarlord("cleaning"), + new ElfBeast("protecting") ); var mage = factory.createMage(); var warlord = factory.createWarlord(); @@ -134,9 +128,9 @@ existing instances. LOGGER.info(beast.toString()); factory = new HeroFactoryImpl( - new OrcMage("axe"), - new OrcWarlord("sword"), - new OrcBeast("laser") + new OrcMage("axe"), + new OrcWarlord("sword"), + new OrcBeast("laser") ); mage = factory.createMage(); warlord = factory.createWarlord(); @@ -144,40 +138,62 @@ existing instances. LOGGER.info(mage.toString()); LOGGER.info(warlord.toString()); LOGGER.info(beast.toString()); +} ``` Here's the console output from running the example. ``` -Elven mage helps in cooking -Elven warlord helps in cleaning -Elven eagle helps in protecting -Orcish mage attacks with axe -Orcish warlord attacks with sword -Orcish wolf attacks with laser +08:36:19.012 [main] INFO com.iluwatar.prototype.App -- Elven mage helps in cooking +08:36:19.013 [main] INFO com.iluwatar.prototype.App -- Elven warlord helps in cleaning +08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Elven eagle helps in protecting +08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish mage attacks with axe +08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish warlord attacks with sword +08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish wolf attacks with laser ``` -## Class diagram +## Detailed Explanation of Prototype Pattern with Real-World Examples ![alt text](./etc/prototype.urm.png "Prototype pattern class diagram") -## Applicability - -Use the Prototype pattern when a system should be independent of how its products are created, -composed, represented and +## When to Use the Prototype Pattern in Java * When the classes to instantiate are specified at run-time, for example, by dynamic loading. * To avoid building a class hierarchy of factories that parallels the class hierarchy of products. -* When instances of a class can have one of only a few different combinations of state. It may be -more convenient to install a corresponding number of prototypes and clone them rather than -instantiating the class manually, each time with the appropriate state. +* When instances of a class can have one of only a few different combinations of state. It may be more convenient to install a corresponding number of prototypes and clone them rather than instantiating the class manually, each time with the appropriate state. * When object creation is expensive compared to cloning. +* When the concrete classes to instantiate are unknown until runtime. + +## Real-World Applications of Prototype Pattern in Java + +* In Java, the `Object.clone()` method is a classic implementation of the Prototype pattern. +* GUI libraries often use prototypes for creating buttons, windows, and other widgets. +* In game development, creating multiple objects (like enemy characters) with similar attributes. + +## Benefits and Trade-offs of Prototype Pattern + +Benefits: + +Leveraging the Prototype pattern in Java applications + +* Hides the complexities of instantiating new objects. +* Reduces the number of classes. +* Allows adding and removing objects at runtime. + +Trade-offs: + +* Requires implementing a cloning mechanism which might be complex. +* Deep cloning can be difficult to implement correctly, especially if the classes have complex object graphs with circular references. -## Known uses +## Related Java Design Patterns -* [java.lang.Object#clone()](http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#clone%28%29) +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Both involve creating objects, but Prototype uses cloning of a prototype instance whereas Abstract Factory creates objects using factory methods. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Singleton can use a prototype for creating instances if it allows cloning of its single instance. +* [Composite](https://java-design-patterns.com/patterns/composite/): Prototypes are often used within composites to allow for dynamic creation of component trees. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) diff --git a/prototype/pom.xml b/prototype/pom.xml index 6ab8640f3226..4e6a44b18733 100644 --- a/prototype/pom.xml +++ b/prototype/pom.xml @@ -34,6 +34,14 @@ prototype + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/prototype/src/main/java/com/iluwatar/prototype/App.java b/prototype/src/main/java/com/iluwatar/prototype/App.java index f29747353440..fdcad907720c 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/App.java +++ b/prototype/src/main/java/com/iluwatar/prototype/App.java @@ -33,8 +33,8 @@ * application, like the abstract factory pattern, does. - avoid the inherent cost of creating a new * object in the standard way (e.g., using the 'new' keyword) * - *

In this example we have a factory class ({@link HeroFactoryImpl}) producing objects by - * cloning the existing ones. The factory's prototype objects are given as constructor parameters. + *

In this example we have a factory class ({@link HeroFactoryImpl}) producing objects by cloning + * the existing ones. The factory's prototype objects are given as constructor parameters. */ @Slf4j public class App { @@ -45,11 +45,9 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var factory = new HeroFactoryImpl( - new ElfMage("cooking"), - new ElfWarlord("cleaning"), - new ElfBeast("protecting") - ); + var factory = + new HeroFactoryImpl( + new ElfMage("cooking"), new ElfWarlord("cleaning"), new ElfBeast("protecting")); var mage = factory.createMage(); var warlord = factory.createWarlord(); var beast = factory.createBeast(); @@ -57,11 +55,8 @@ public static void main(String[] args) { LOGGER.info(warlord.toString()); LOGGER.info(beast.toString()); - factory = new HeroFactoryImpl( - new OrcMage("axe"), - new OrcWarlord("sword"), - new OrcBeast("laser") - ); + factory = + new HeroFactoryImpl(new OrcMage("axe"), new OrcWarlord("sword"), new OrcBeast("laser")); mage = factory.createMage(); warlord = factory.createWarlord(); beast = factory.createBeast(); diff --git a/prototype/src/main/java/com/iluwatar/prototype/Beast.java b/prototype/src/main/java/com/iluwatar/prototype/Beast.java index 1888133a765d..027ad2b18ad4 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Beast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Beast.java @@ -27,14 +27,10 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/** - * Beast. - */ +/** Beast. */ @EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Beast extends Prototype { - public Beast(Beast source) { - } - + public Beast(Beast source) {} } diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java index 0dbfd789fc5b..0fa9822bee36 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * ElfBeast. - */ +/** ElfBeast. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class ElfBeast extends Beast { @@ -45,5 +43,4 @@ public ElfBeast(ElfBeast elfBeast) { public String toString() { return "Elven eagle helps in " + helpType; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java index f1cd27c841eb..5b00275e46a4 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * ElfMage. - */ +/** ElfMage. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class ElfMage extends Mage { @@ -45,5 +43,4 @@ public ElfMage(ElfMage elfMage) { public String toString() { return "Elven mage helps in " + helpType; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java index a876cdcb3b55..6a53e7309deb 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * ElfWarlord. - */ +/** ElfWarlord. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class ElfWarlord extends Warlord { diff --git a/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java b/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java index 91aa37229b2f..8295e0bdb8c1 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java +++ b/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java @@ -1,38 +1,35 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.prototype; - -/** - * Interface for the factory class. - */ -public interface HeroFactory { - - Mage createMage(); - - Warlord createWarlord(); - - Beast createBeast(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.prototype; + +/** Interface for the factory class. */ +public interface HeroFactory { + + Mage createMage(); + + Warlord createWarlord(); + + Beast createBeast(); +} diff --git a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java index 21c20a5fd146..c959aa89d1ad 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java +++ b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java @@ -26,9 +26,7 @@ import lombok.RequiredArgsConstructor; -/** - * Concrete factory class. - */ +/** Concrete factory class. */ @RequiredArgsConstructor public class HeroFactoryImpl implements HeroFactory { @@ -36,25 +34,18 @@ public class HeroFactoryImpl implements HeroFactory { private final Warlord warlord; private final Beast beast; - /** - * Create mage. - */ + /** Create mage. */ public Mage createMage() { return mage.copy(); } - /** - * Create warlord. - */ + /** Create warlord. */ public Warlord createWarlord() { return warlord.copy(); } - /** - * Create beast. - */ + /** Create beast. */ public Beast createBeast() { return beast.copy(); } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/Mage.java b/prototype/src/main/java/com/iluwatar/prototype/Mage.java index 8f90e53f00da..70a7770515f1 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Mage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Mage.java @@ -27,14 +27,10 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/** - * Mage. - */ +/** Mage. */ @EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Mage extends Prototype { - public Mage(Mage source) { - } - + public Mage(Mage source) {} } diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java index 4cd24ed7e8fe..0ab3c3b9cfc6 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * OrcBeast. - */ +/** OrcBeast. */ @EqualsAndHashCode(callSuper = false) @RequiredArgsConstructor public class OrcBeast extends Beast { @@ -45,5 +43,4 @@ public OrcBeast(OrcBeast orcBeast) { public String toString() { return "Orcish wolf attacks with " + weapon; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java index 7a4aa0a4db31..33c0cac9f42a 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * OrcMage. - */ +/** OrcMage. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class OrcMage extends Mage { @@ -45,5 +43,4 @@ public OrcMage(OrcMage orcMage) { public String toString() { return "Orcish mage attacks with " + weapon; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java index 5ee7da40ba39..54964bd333f7 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * OrcWarlord. - */ +/** OrcWarlord. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class OrcWarlord extends Warlord { @@ -45,5 +43,4 @@ public OrcWarlord(OrcWarlord orcWarlord) { public String toString() { return "Orcish warlord attacks with " + weapon; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/Prototype.java b/prototype/src/main/java/com/iluwatar/prototype/Prototype.java index b5cd10188066..ead3d4e860eb 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Prototype.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Prototype.java @@ -27,15 +27,11 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -/** - * Prototype. - */ +/** Prototype. */ @Slf4j public abstract class Prototype implements Cloneable { - /** - * Object a shallow copy of this object or null if this object is not Cloneable. - */ + /** Object a shallow copy of this object or null if this object is not Cloneable. */ @SuppressWarnings("unchecked") @SneakyThrows public T copy() { diff --git a/prototype/src/main/java/com/iluwatar/prototype/Warlord.java b/prototype/src/main/java/com/iluwatar/prototype/Warlord.java index f407673d133a..a1cf5562b22f 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Warlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Warlord.java @@ -27,14 +27,10 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/** - * Warlord. - */ +/** Warlord. */ @EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Warlord extends Prototype { - public Warlord(Warlord source) { - } - + public Warlord(Warlord source) {} } diff --git a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java index c9c55e0c6776..2407c1c8d8b3 100644 --- a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java +++ b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java index 2f7f76f7a405..b8cdb73b8a43 100644 --- a/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java +++ b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java @@ -35,21 +35,19 @@ import org.junit.jupiter.params.provider.MethodSource; /** - * Date: 12/28/15 - 8:45 PM + * PrototypeTest * * @param

Prototype - * @author Jeroen Meulemeester */ class PrototypeTest

> { static Collection dataProvider() { return List.of( - new Object[]{new OrcBeast("axe"), "Orcish wolf attacks with axe"}, - new Object[]{new OrcMage("sword"), "Orcish mage attacks with sword"}, - new Object[]{new OrcWarlord("laser"), "Orcish warlord attacks with laser"}, - new Object[]{new ElfBeast("cooking"), "Elven eagle helps in cooking"}, - new Object[]{new ElfMage("cleaning"), "Elven mage helps in cleaning"}, - new Object[]{new ElfWarlord("protecting"), "Elven warlord helps in protecting"} - ); + new Object[] {new OrcBeast("axe"), "Orcish wolf attacks with axe"}, + new Object[] {new OrcMage("sword"), "Orcish mage attacks with sword"}, + new Object[] {new OrcWarlord("laser"), "Orcish warlord attacks with laser"}, + new Object[] {new ElfBeast("cooking"), "Elven eagle helps in cooking"}, + new Object[] {new ElfMage("cleaning"), "Elven mage helps in cleaning"}, + new Object[] {new ElfWarlord("protecting"), "Elven warlord helps in protecting"}); } @ParameterizedTest @@ -63,5 +61,4 @@ void testPrototype(P testedPrototype, String expectedToString) { assertSame(testedPrototype.getClass(), clone.getClass()); assertEquals(clone, testedPrototype); } - } diff --git a/proxy/README.md b/proxy/README.md index a0f91240b160..f237fb47e9e5 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -1,59 +1,59 @@ --- -title: Proxy +title: "Proxy Pattern in Java: Enhancing Security and Control with Smart Proxies" +shortTitle: Proxy +description: "Explore the Proxy design pattern in Java with detailed examples. Learn how it provides controlled access, facilitates lazy initialization, and ensures security. Ideal for developers looking to implement advanced Java techniques." category: Structural language: en tag: - - Gang Of Four - - Decoupling + - Decoupling + - Encapsulation + - Gang Of Four + - Lazy initialization + - Proxy + - Security + - Wrapping --- ## Also known as -Surrogate +* Surrogate -## Intent +## Intent of Proxy Design Pattern -Provide a surrogate or placeholder for another object to control access to it. +The Proxy pattern in Java provides a surrogate or placeholder to effectively control access to an object, enhancing security and resource management. -## Explanation +## Detailed Explanation of Proxy Pattern with Real-World Examples Real-world example -> Imagine a tower where the local wizards go to study their spells. The ivory tower can only be -> accessed through a proxy which ensures that only the first three wizards can enter. Here the proxy -> represents the functionality of the tower and adds access control to it. +> In a real-world scenario, consider a security guard at a gated community. The security guard acts as a proxy for the residents. When a visitor arrives, the guard checks the visitor's credentials and permissions before allowing them access to the community. If the visitor is authorized, the guard grants entry; if not, entry is denied. This ensures that only authorized individuals can access the community, much like a Proxy design pattern controls access to a specific object. In plain words -> Using the proxy pattern, a class represents the functionality of another class. +> Utilizing the Java Proxy pattern, a class encapsulates the functionality of another, streamlining access control and operation efficiency. Wikipedia says -> A proxy, in its most general form, is a class functioning as an interface to something else. -> A proxy is a wrapper or agent object that is being called by the client to access the real serving -> object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can -> provide additional logic. In the proxy extra functionality can be provided, for example caching -> when operations on the real object are resource intensive, or checking preconditions before -> operations on the real object are invoked. +> A proxy, in its most general form, is a class functioning as an interface to something else. A proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. -**Programmatic Example** +## Programmatic Example of Proxy Pattern in Java -Taking our wizard tower example from above. Firstly we have the `WizardTower` interface and the -`IvoryTower` class. +Imagine a tower where the local wizards go to study their spells. The ivory tower can only be accessed through a proxy which ensures that only the first three wizards can enter. Here the proxy represents the functionality of the tower and adds access control to it. + +First, we have the `WizardTower` interface and the `IvoryTower` class. ```java public interface WizardTower { - void enter(Wizard wizard); } +``` +```java @Slf4j public class IvoryTower implements WizardTower { - public void enter(Wizard wizard) { LOGGER.info("{} enters the tower.", wizard); } - } ``` @@ -82,9 +82,7 @@ Then we have the `WizardTowerProxy` to add access control to `WizardTower`. public class WizardTowerProxy implements WizardTower { private static final int NUM_WIZARDS_ALLOWED = 3; - private int numWizards; - private final WizardTower tower; public WizardTowerProxy(WizardTower tower) { @@ -106,64 +104,70 @@ public class WizardTowerProxy implements WizardTower { And here is the tower entering scenario. ```java -var proxy = new WizardTowerProxy(new IvoryTower()); -proxy.enter(new Wizard("Red wizard")); -proxy.enter(new Wizard("White wizard")); -proxy.enter(new Wizard("Black wizard")); -proxy.enter(new Wizard("Green wizard")); -proxy.enter(new Wizard("Brown wizard")); + public static void main(String[] args) { + + var proxy = new WizardTowerProxy(new IvoryTower()); + proxy.enter(new Wizard("Red wizard")); + proxy.enter(new Wizard("White wizard")); + proxy.enter(new Wizard("Black wizard")); + proxy.enter(new Wizard("Green wizard")); + proxy.enter(new Wizard("Brown wizard")); +} ``` Program output: ``` -Red wizard enters the tower. -White wizard enters the tower. -Black wizard enters the tower. -Green wizard is not allowed to enter! -Brown wizard is not allowed to enter! +08:42:06.183 [main] INFO com.iluwatar.proxy.IvoryTower -- Red wizard enters the tower. +08:42:06.186 [main] INFO com.iluwatar.proxy.IvoryTower -- White wizard enters the tower. +08:42:06.186 [main] INFO com.iluwatar.proxy.IvoryTower -- Black wizard enters the tower. +08:42:06.186 [main] INFO com.iluwatar.proxy.WizardTowerProxy -- Green wizard is not allowed to enter! +08:42:06.186 [main] INFO com.iluwatar.proxy.WizardTowerProxy -- Brown wizard is not allowed to enter! ``` -## Class diagram - -![alt text](./etc/proxy.urm.png "Proxy pattern class diagram") +## When to Use the Proxy Pattern in Java -## Applicability - -Proxy is applicable whenever there is a need for a more versatile or sophisticated reference to an -object than a simple pointer. Here are several common situations in which the Proxy pattern is -applicable. - -* Remote proxy provides a local representative for an object in a different address space. -* Virtual proxy creates expensive objects on demand. -* Protection proxy controls access to the original object. Protection proxies are useful when -objects should have different access rights. - -Typically, the proxy pattern is used to +Proxy is applicable whenever there is a need for a more versatile or sophisticated reference to an object than a simple pointer. Here are several common situations in which the Proxy pattern is applicable. Typically, the proxy pattern is used to * Control access to another object * Lazy initialization * Implement logging * Facilitate network connection * Count references to an object +* Provide a local representation for an object that is in a different address space. -## Tutorials - -* [Controlling Access With Proxy Pattern](http://java-design-patterns.com/blog/controlling-access-with-proxy-pattern/) - -## Known uses +## Real-World Applications of Proxy Pattern in Java +* Virtual Proxies: In applications that need heavy resources like large images or complex calculations, virtual proxies can be used to instantiate objects only when needed. +* Remote Proxies: Used in remote method invocation (RMI) to manage interactions with remote objects. +* Protection Proxies: Control access to the original object to ensure proper authorization. * [java.lang.reflect.Proxy](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html) * [Apache Commons Proxy](https://commons.apache.org/proper/commons-proxy/) -* Mocking frameworks [Mockito](https://site.mockito.org/), -[Powermock](https://powermock.github.io/), [EasyMock](https://easymock.org/) +* Mocking frameworks [Mockito](https://site.mockito.org/),[Powermock](https://powermock.github.io/), [EasyMock](https://easymock.org/) * [UIAppearance](https://developer.apple.com/documentation/uikit/uiappearance) -## Related patterns +## Benefits and Trade-offs of Proxy Pattern + +Benefits: + +* Controlled Access: Proxies control access to the real object, allowing for checks, logging, or other operations. +* Lazy Initialization: Proxies can delay the creation and initialization of resource-intensive objects until they are needed. +* Remote Proxy Handling: Simplifies interaction with remote objects by handling the network communication. + +Trade-offs: + +* Overhead: Adding a proxy introduces additional layers that might add overhead. +* Complexity: Increases the complexity of the system by adding more classes. + +## Related Java Design Patterns -* [Ambassador](https://java-design-patterns.com/patterns/ambassador/) +* [Adapter](https://java-design-patterns.com/patterns/adapter/): The Adapter pattern changes the interface of an existing object, whereas Proxy provides the same interface as the original object. +* [Ambassador](https://java-design-patterns.com/patterns/ambassador/): Ambassador is similar to Proxy as it acts as an intermediary, especially in remote communications, enhancing access control and monitoring. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Both Decorator and Proxy patterns provide a level of indirection, but the Decorator pattern adds responsibilities to objects dynamically, while Proxy controls access. +* [Facade](https://java-design-patterns.com/patterns/facade/): Facade provides a simplified interface to a complex subsystem, while Proxy controls access to a particular object. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) diff --git a/proxy/pom.xml b/proxy/pom.xml index f700c2fbb4ee..79a9f2c58e72 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -34,6 +34,14 @@ proxy + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/proxy/src/main/java/com/iluwatar/proxy/App.java b/proxy/src/main/java/com/iluwatar/proxy/App.java index dfd8550c6584..9b4cd6b5a497 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/App.java +++ b/proxy/src/main/java/com/iluwatar/proxy/App.java @@ -40,9 +40,7 @@ */ public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var proxy = new WizardTowerProxy(new IvoryTower()); @@ -51,6 +49,5 @@ public static void main(String[] args) { proxy.enter(new Wizard("Black wizard")); proxy.enter(new Wizard("Green wizard")); proxy.enter(new Wizard("Brown wizard")); - } } diff --git a/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java b/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java index 64f69f1e6659..2f815f511ecf 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java +++ b/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java @@ -26,14 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * The object to be proxied. - */ +/** The object to be proxied. */ @Slf4j public class IvoryTower implements WizardTower { public void enter(Wizard wizard) { LOGGER.info("{} enters the tower.", wizard); } - } diff --git a/proxy/src/main/java/com/iluwatar/proxy/Wizard.java b/proxy/src/main/java/com/iluwatar/proxy/Wizard.java index e37d33805e1f..ebc3307cbd8a 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/Wizard.java +++ b/proxy/src/main/java/com/iluwatar/proxy/Wizard.java @@ -26,9 +26,7 @@ import lombok.RequiredArgsConstructor; -/** - * Wizard. - */ +/** Wizard. */ @RequiredArgsConstructor public class Wizard { @@ -38,5 +36,4 @@ public class Wizard { public String toString() { return name; } - } diff --git a/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java b/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java index 8dcbdb2d372c..5e89539d0333 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java +++ b/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java @@ -24,9 +24,7 @@ */ package com.iluwatar.proxy; -/** - * WizardTower interface. - */ +/** WizardTower interface. */ public interface WizardTower { void enter(Wizard wizard); diff --git a/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java b/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java index 9f27fad34d29..11f4effd58f0 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java +++ b/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * The proxy controlling access to the {@link IvoryTower}. - */ +/** The proxy controlling access to the {@link IvoryTower}. */ @Slf4j public class WizardTowerProxy implements WizardTower { diff --git a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java index 066eb6fe06eb..bf96c6c00a40 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.proxy; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java b/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java index 4531a67fcaaa..550814e2644e 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests for {@link IvoryTower} - */ +/** Tests for {@link IvoryTower} */ class IvoryTowerTest { private InMemoryAppender appender; @@ -52,12 +50,12 @@ void tearDown() { @Test void testEnter() { - final var wizards = List.of( - new Wizard("Gandalf"), - new Wizard("Dumbledore"), - new Wizard("Oz"), - new Wizard("Merlin") - ); + final var wizards = + List.of( + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin")); var tower = new IvoryTower(); wizards.forEach(tower::enter); diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java index 68ec25192969..73ff41ac87dd 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java @@ -29,9 +29,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * Tests for {@link Wizard} - */ +/** Tests for {@link Wizard} */ class WizardTest { @Test @@ -39,4 +37,4 @@ void testToString() { List.of("Gandalf", "Dumbledore", "Oz", "Merlin") .forEach(name -> assertEquals(name, new Wizard(name).toString())); } -} \ No newline at end of file +} diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java index 1b3305296734..1e73e690edd1 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests for {@link WizardTowerProxy} - */ +/** Tests for {@link WizardTowerProxy} */ class WizardTowerProxyTest { private InMemoryAppender appender; @@ -52,12 +50,12 @@ void tearDown() { @Test void testEnter() { - final var wizards = List.of( - new Wizard("Gandalf"), - new Wizard("Dumbledore"), - new Wizard("Oz"), - new Wizard("Merlin") - ); + final var wizards = + List.of( + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin")); final var proxy = new WizardTowerProxy(new IvoryTower()); wizards.forEach(proxy::enter); diff --git a/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java b/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java index fb982b96df07..b3341c19f358 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java +++ b/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java @@ -31,10 +31,7 @@ import java.util.List; import org.slf4j.LoggerFactory; - -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/publish-subscribe/README.md b/publish-subscribe/README.md new file mode 100644 index 000000000000..4e88cf2e8d9b --- /dev/null +++ b/publish-subscribe/README.md @@ -0,0 +1,302 @@ +--- +title: "Publish-Subscribe Pattern in Java: Decoupling the solution with asynchronous communication" +shortTitle: Publish-Subscribe +description: "Explore the Publish-Subscribe design pattern in Java with detailed examples. Learn how it helps to create loosely coupled, scalable, and flexible systems by allowing components to communicate asynchronously without knowing each other directly." +category: Behavioral +language: en +tag: + - Decoupling + - Event-driven + - Gang Of Four + - Publish/subscribe +--- + +## Intent of the Publish-Subscribe Design Pattern + +The Publish-Subscribe design pattern is widely used in software architecture to transmit data between various components in a system. +It is a behavioral design pattern aimed at achieving loosely coupled communication between objects. +The primary intent is to allow a one-to-many dependency relationship where one object (the Publisher) notifies multiple other objects (the Subscribers) +about changes or events, without needing to know who or what the subscribers are. + +## Detailed Explanation of Publish-Subscribe Pattern with Real-World Examples + +### Real-world example + +- Messaging systems like Kafka, RabbitMQ, AWS SNS, JMS + - **Kafka** : publishes messages to topics and subscribers consumes them in real time for analytics, logs or other purposes. + - **RabbitMQ** : Uses exchanges as publisher and queues as subscribers to route messages + - **AWS SNS** : Simple Notification Service (SNS) received the messages from publishers with topic and the subscribers on that topic will receive the messages. (SQS, Lambda functions, emails, SMS) + + +- Event driven microservices + - **Publisher** : Point of Sale(PoS) system records the sale of an item and publish the event + - **Subscribers** : Inventory management service updates stock, Billing service sends e-bill to customer + + +- Newsletter subscriptions + - **Publisher** : Writes a new blog post and publish to subscribers + - **Subscribers** : All the subscribers to the newsletter receive the email + +### In plain words + +The Publish-Subscribe design pattern allows senders (publishers) to broadcast messages to multiple receivers (subscribers) without knowing who they are, +enabling loose coupling and asynchronous communication in a system + +### Wikipedia says + +In software architecture, publish–subscribe or pub/sub is a messaging pattern where publishers categorize messages into classes that are received by subscribers. +This is contrasted to the typical messaging pattern model where publishers send messages directly to subscribers. + +Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. + +Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system. +Most messaging systems support both the pub/sub and message queue models in their API; e.g., Java Message Service (JMS). + +### Architectural Diagram +![pub-sub](./etc/pub-sub.png) + +## Programmatic Example of Publish-Subscribe Pattern in Java + +First we need to identify the Event on which we need the pub-sub methods to trigger. +For example: + +- Sending alerts based on the weather events such as earthquakes, floods and tornadoes +- Sending alerts based on the temperature +- Sending an email to different customer support emails when a support ticket is created. + +The Message class below will hold the content of the message we need to pass between the publisher and the subscribers. + +```java +public record Message(Object content) { +} + +``` + +The Topic class will have the topic **name** based on the event + +- Weather events TopicName WEATHER +- Weather events TopicName TEMPERATURE +- Support ticket created TopicName CUSTOMER_SUPPORT +- Any other custom topic depending on use case +- Also, the Topic contains a list of subscribers that will listen to that topic + +We can add or remove subscribers from the subscription to the topic + +```java +public class Topic { + + private final TopicName name; + private final Set subscribers = new CopyOnWriteArraySet<>(); + //...// +} +``` + +Then we can create the publisher. The publisher class has a set of topics. + +- Each new topic has to be registered in the publisher. +- Publish method will publish the _Message_ to the corresponding _Topic_. + +```java +public class PublisherImpl implements Publisher { + + private static final Logger logger = LoggerFactory.getLogger(PublisherImpl.class); + private final Set topics = new HashSet<>(); + + @Override + public void registerTopic(Topic topic) { + topics.add(topic); + } + + @Override + public void publish(Topic topic, Message message) { + if (!topics.contains(topic)) { + logger.error("This topic is not registered: {}", topic.getName()); + return; + } + topic.publish(message); + } +} +``` + +Finally, we can Subscribers to the Topics we want to listen to. + +- For WEATHER topic we will create _WeatherSubscriber_ +- _WeatherSubscriber_ can also subscribe to TEMPERATURE topic +- For CUSTOMER_SUPPORT topic we will create _CustomerSupportSubscribe_ +- Also to demonstrate the async behavior we will create a _DelayedWeatherSubscriber_ who has a 0.2 sec processing deplay + +All classes will have a _onMessage_ method which will take a Message input. + +- On message method will verify the content of the message is as expected +- After content is verified it will perform the operation based on the message + - _WeatherSubscriber_ will send a weather or temperature alert based on the _Message_ + - _CustomerSupportSubscribe_will send an email based on the _Message_ + - _DelayedWeatherSubscriber_ will send a weather alert based on the _Message_ after a delay + +```java +public interface Subscriber { + void onMessage(Message message); +} +``` + +And here is the invocation of the publisher and subscribers. + +```java +public static void main(String[] args) throws InterruptedException { + + final String topicWeather = "WEATHER"; + final String topicTemperature = "TEMPERATURE"; + final String topicCustomerSupport = "CUSTOMER_SUPPORT"; + + // 1. create the publisher. + Publisher publisher = new PublisherImpl(); + + // 2. define the topics and register on publisher + Topic weatherTopic = new Topic(topicWeather); + publisher.registerTopic(weatherTopic); + + Topic temperatureTopic = new Topic(topicTemperature); + publisher.registerTopic(temperatureTopic); + + Topic supportTopic = new Topic(topicCustomerSupport); + publisher.registerTopic(supportTopic); + + // 3. Create the subscribers and subscribe to the relevant topics + // weatherSub1 will subscribe to two topics WEATHER and TEMPERATURE. + Subscriber weatherSub1 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub1); + temperatureTopic.addSubscriber(weatherSub1); + + // weatherSub2 will subscribe to WEATHER topic + Subscriber weatherSub2 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub2); + + // delayedWeatherSub will subscribe to WEATHER topic + // NOTE :: DelayedWeatherSubscriber has a 0.2 sec delay of processing message. + Subscriber delayedWeatherSub = new DelayedWeatherSubscriber(); + weatherTopic.addSubscriber(delayedWeatherSub); + + // subscribe the customer support subscribers to the CUSTOMER_SUPPORT topic. + Subscriber supportSub1 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub1); + Subscriber supportSub2 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub2); + + // 4. publish message from each topic + publisher.publish(weatherTopic, new Message("earthquake")); + publisher.publish(temperatureTopic, new Message("23C")); + publisher.publish(supportTopic, new Message("support@test.de")); + + // 5. unregister subscriber from TEMPERATURE topic + temperatureTopic.removeSubscriber(weatherSub1); + + // 6. publish message under TEMPERATURE topic + publisher.publish(temperatureTopic, new Message("0C")); + + /* + * Finally, we wait for the subscribers to consume messages to check the output. + * The output can change on each run, depending on how long the execution on each + * subscriber would take + * Expected behavior: + * - weatherSub1 will consume earthquake and 23C + * - weatherSub2 will consume earthquake + * - delayedWeatherSub will take longer and consume earthquake + * - supportSub1, supportSub2 will consume support@test.de + * - the message 0C will not be consumed because weatherSub1 unsubscribed from TEMPERATURE topic + */ + TimeUnit.SECONDS.sleep(2); +} +``` + +Program output: + +Note that the order of output could change everytime you run the program. +The subscribers could take different time to consume the message. + +``` +14:01:45.599 [ForkJoinPool.commonPool-worker-6] INFO com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber -- Customer Support Subscriber: 1416331388 sent the email to: support@test.de +14:01:45.599 [ForkJoinPool.commonPool-worker-4] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 1949521124 issued message: 23C +14:01:45.599 [ForkJoinPool.commonPool-worker-2] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 60629172 issued message: earthquake +14:01:45.599 [ForkJoinPool.commonPool-worker-5] INFO com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber -- Customer Support Subscriber: 1807508804 sent the email to: support@test.de +14:01:45.599 [ForkJoinPool.commonPool-worker-1] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 1949521124 issued message: earthquake +14:01:47.600 [ForkJoinPool.commonPool-worker-3] INFO com.iluwatar.publish.subscribe.subscriber.DelayedWeatherSubscriber -- Delayed Weather Subscriber: 2085808749 issued message: earthquake +``` + +## When to Use the Publish-Subscribe Pattern + +- Event-Driven Systems + - Use Pub/Sub when your system relies on events (e.g., user registration, payment completion). + - Example: After a user registers, send a welcome email and log the action simultaneously. + +- Asynchronous Communication + - When tasks can be performed without waiting for immediate responses. + - Example: In an e-commerce app, notify the warehouse and the user after a successful order. + +- Decoupling Components + - Ideal for systems where producers and consumers should not depend on each other. + - Example: A logging service listens for logs from multiple microservices. + +- Scaling Systems + - Useful when you need to scale services without changing the core application logic. + - Example: Broadcasting messages to thousands of clients (chat applications, IoT). + +- Broadcasting Notifications + - When a message should be delivered to multiple receivers. + - Example: Sending promotional offers to multiple user devices. + +- Microservices Communication + - Allow independent services to communicate without direct coupling. + - Example: An order service publishes an event, and both the billing and shipping services process it. + +## When to avoid the Publish-Subscribe Pattern + +- Simple applications where direct calls suffice. +- Strong consistency requirements (e.g., banking transactions). +- Low-latency synchronous communication needed. + +## Benefits and Trade-offs of Publish-Subscribe Pattern + +### Benefits: + +- Decoupling + - Publishers and subscribers are independent of each other. + - Publishers don’t need to know who the subscribers are, and vice versa. + - Changes in one component don’t affect the other. +- Scalability + - New subscribers can be added without modifying publishers. + - Supports distributed systems where multiple services consume the same events. +- Dynamic Subscription + - Subscribers can subscribe/unsubscribe at runtime. + - Enables flexible event-driven architectures. +- Asynchronous Communication + - Publishers and subscribers operate independently, improving performance. + - Useful for background processing (e.g., notifications, logging). +- Broadcast Communication + - A single event can be consumed by multiple subscribers. + - Useful for fan-out scenarios (e.g., notifications, analytics). +- Resilience & Fault Tolerance + - If a subscriber fails, others can still process messages. + - Message brokers (e.g., Kafka, RabbitMQ) can retry or persist undelivered messages. + +### Trade-offs: + +- Complexity in Debugging + - Since publishers and subscribers are decoupled, tracing event flow can be difficult. + - Requires proper logging and monitoring tools. +- Message Ordering & Consistency + - Ensuring message order across subscribers can be challenging (e.g., Kafka vs. RabbitMQ). + - Some systems may process events out of order. +- Potential Latency + - Asynchronous processing introduces delays compared to direct calls. + - Not ideal for real-time synchronous requirements. + +## Related Java Design Patterns + +* [Observer Pattern](https://github.com/sanurah/java-design-patterns/blob/master/observer/): Both involve a producer (subject/publisher) notifying consumers (observers/subscribers). Observer is synchronous & tightly coupled (observers know the subject). Pub-Sub is asynchronous & decoupled (via a message broker). +* [Mediator Pattern](https://github.com/sanurah/java-design-patterns/blob/master/mediator/): A mediator centralizes communication between components (like a message broker in Pub-Sub). Mediator focuses on reducing direct dependencies between objects. Pub-Sub focuses on broadcasting events to unknown subscribers. + +## References and Credits + +* [Apache Kafka – Pub-Sub Model](https://kafka.apache.org/documentation/#design_pubsub) +* [Microsoft – Publish-Subscribe Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber) +* [Martin Fowler – Event-Driven Architecture](https://martinfowler.com/articles/201701-event-driven.html) diff --git a/publish-subscribe/etc/pub-sub.png b/publish-subscribe/etc/pub-sub.png new file mode 100644 index 000000000000..9783fdffeab3 Binary files /dev/null and b/publish-subscribe/etc/pub-sub.png differ diff --git a/thread-local-storage/pom.xml b/publish-subscribe/pom.xml similarity index 80% rename from thread-local-storage/pom.xml rename to publish-subscribe/pom.xml index 49726e281269..2dce76d5e019 100644 --- a/thread-local-storage/pom.xml +++ b/publish-subscribe/pom.xml @@ -25,16 +25,17 @@ THE SOFTWARE. --> - + 4.0.0 - com.iluwatar java-design-patterns 1.26.0-SNAPSHOT - thread-local-storage + publish-subscribe @@ -42,5 +43,10 @@ junit-jupiter-engine test + + ch.qos.logback + logback-classic + - + + \ No newline at end of file diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java new file mode 100644 index 000000000000..19e12b9ad1f0 --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java @@ -0,0 +1,139 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe; + +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import com.iluwatar.publish.subscribe.publisher.Publisher; +import com.iluwatar.publish.subscribe.publisher.PublisherImpl; +import com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber; +import com.iluwatar.publish.subscribe.subscriber.DelayedWeatherSubscriber; +import com.iluwatar.publish.subscribe.subscriber.Subscriber; +import com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber; +import java.util.concurrent.TimeUnit; + +/** + * The Publish and Subscribe pattern is a messaging paradigm used in software architecture with + * several key points: + *

  • Decoupling of publishers and subscribers: Publishers and subscribers operate independently, + * and there's no direct link between them. This enhances the scalability and * modularity of + * applications. + *
  • Event-driven communication: The pattern facilitates event-driven architectures by allowing + * publishers to broadcast events without concerning themselves with who receives the events. + *
  • Dynamic subscription: Subscribers can dynamically choose to listen for specific events or + * messages they are interested in, often by subscribing to a particular topic or channel. + *
  • Asynchronous processing: The pattern inherently supports asynchronous message processing, + * enabling efficient handling of events and improving application responsiveness. + *
  • Scalability: By decoupling senders and receivers, the pattern can support a large number of + * publishers and subscribers, making it suitable for scalable systems. + *
  • Flexibility and adaptability: New subscribers or publishers can be added to the system + * without significant changes to the existing components, making the system highly adaptable to + * evolving requirements. + * + *

    In this example we will create three topics WEATHER, TEMPERATURE and CUSTOMER_SUPPORT. + * Then we will register those topics in the {@link Publisher}. After that we will create two + * {@link WeatherSubscriber}s, one {@link DelayedWeatherSubscriber} and two {@link + * CustomerSupportSubscriber}.The subscribers will subscribe to the relevant topics. One {@link + * WeatherSubscriber} will subscribe to two topics (WEATHER, TEMPERATURE). {@link + * DelayedWeatherSubscriber} has a delay in message processing. Now we can publish the three + * {@link Topic}s with different content in the {@link Message}s. And we can observe the output + * in the log where, one {@link WeatherSubscriber} will output the message with weather and the + * other {@link WeatherSubscriber} will output weather and temperature. {@link + * CustomerSupportSubscriber}s will output the message with customer support email. {@link + * DelayedWeatherSubscriber} has a delay in processing and will output the message at last. Each + * subscriber is only listening to the subscribed topics. + */ +public class App { + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) throws InterruptedException { + + final String topicWeather = "WEATHER"; + final String topicTemperature = "TEMPERATURE"; + final String topicCustomerSupport = "CUSTOMER_SUPPORT"; + + // 1. create the publisher. + Publisher publisher = new PublisherImpl(); + + // 2. define the topics and register on publisher + Topic weatherTopic = new Topic(topicWeather); + publisher.registerTopic(weatherTopic); + + Topic temperatureTopic = new Topic(topicTemperature); + publisher.registerTopic(temperatureTopic); + + Topic supportTopic = new Topic(topicCustomerSupport); + publisher.registerTopic(supportTopic); + + // 3. Create the subscribers and subscribe to the relevant topics + // weatherSub1 will subscribe to two topics WEATHER and TEMPERATURE. + Subscriber weatherSub1 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub1); + temperatureTopic.addSubscriber(weatherSub1); + + // weatherSub2 will subscribe to WEATHER topic + Subscriber weatherSub2 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub2); + + // delayedWeatherSub will subscribe to WEATHER topic + // NOTE :: DelayedWeatherSubscriber has a 0.2 sec delay of processing message. + Subscriber delayedWeatherSub = new DelayedWeatherSubscriber(); + weatherTopic.addSubscriber(delayedWeatherSub); + + // subscribe the customer support subscribers to the CUSTOMER_SUPPORT topic. + Subscriber supportSub1 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub1); + Subscriber supportSub2 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub2); + + // 4. publish message from each topic + publisher.publish(weatherTopic, new Message("earthquake")); + publisher.publish(temperatureTopic, new Message("23C")); + publisher.publish(supportTopic, new Message("support@test.de")); + + // 5. unregister subscriber from TEMPERATURE topic + temperatureTopic.removeSubscriber(weatherSub1); + + // 6. publish message under TEMPERATURE topic + publisher.publish(temperatureTopic, new Message("0C")); + + /* + * Finally, we wait for the subscribers to consume messages to check the output. + * The output can change on each run, depending on how long the execution on each + * subscriber would take + * Expected behavior: + * - weatherSub1 will consume earthquake and 23C + * - weatherSub2 will consume earthquake + * - delayedWeatherSub will take longer and consume earthquake + * - supportSub1, supportSub2 will consume support@test.de + * - the message 0C will not be consumed because weatherSub1 unsubscribed from TEMPERATURE topic + */ + TimeUnit.SECONDS.sleep(2); + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Message.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Message.java new file mode 100644 index 000000000000..0d6017900a0c --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Message.java @@ -0,0 +1,28 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +/** This class represents a Message that holds the published content. */ +public record Message(Object content) {} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java new file mode 100644 index 000000000000..4c421afe735a --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java @@ -0,0 +1,72 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +import com.iluwatar.publish.subscribe.subscriber.Subscriber; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArraySet; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** This class represents a Topic that topic name and subscribers. */ +@Getter +@Setter +@RequiredArgsConstructor +public class Topic { + + private final String topicName; + private final Set subscribers = new CopyOnWriteArraySet<>(); + + /** + * Add a subscriber to the list of subscribers. + * + * @param subscriber subscriber to add + */ + public void addSubscriber(Subscriber subscriber) { + subscribers.add(subscriber); + } + + /** + * Remove a subscriber to the list of subscribers. + * + * @param subscriber subscriber to remove + */ + public void removeSubscriber(Subscriber subscriber) { + subscribers.remove(subscriber); + } + + /** + * Publish a message to subscribers. + * + * @param message message with content to publish + */ + public void publish(Message message) { + for (Subscriber subscriber : subscribers) { + CompletableFuture.runAsync(() -> subscriber.onMessage(message)); + } + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/Publisher.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/Publisher.java new file mode 100644 index 000000000000..3f58d3719bca --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/Publisher.java @@ -0,0 +1,47 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.publisher; + +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; + +/** This class represents a Publisher. */ +public interface Publisher { + + /** + * Register a topic in the publisher. + * + * @param topic the topic to be registered + */ + void registerTopic(Topic topic); + + /** + * Register a topic in the publisher. + * + * @param topic the topic to publish the message under + * @param message message with content to be published + */ + void publish(Topic topic, Message message); +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/PublisherImpl.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/PublisherImpl.java new file mode 100644 index 000000000000..eb895adaa3cf --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/PublisherImpl.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.publisher; + +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import java.util.HashSet; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is an implementation of the Publisher. */ +public class PublisherImpl implements Publisher { + + private static final Logger logger = LoggerFactory.getLogger(PublisherImpl.class); + private final Set topics = new HashSet<>(); + + @Override + public void registerTopic(Topic topic) { + topics.add(topic); + } + + @Override + public void publish(Topic topic, Message message) { + if (!topics.contains(topic)) { + logger.error("This topic is not registered: {}", topic.getTopicName()); + return; + } + topic.publish(message); + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/CustomerSupportSubscriber.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/CustomerSupportSubscriber.java new file mode 100644 index 000000000000..385862d8dd44 --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/CustomerSupportSubscriber.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import com.iluwatar.publish.subscribe.model.Message; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class subscribes to CUSTOMER_SUPPORT topic. */ +@Slf4j +public class CustomerSupportSubscriber implements Subscriber { + + private static final Logger logger = LoggerFactory.getLogger(CustomerSupportSubscriber.class); + + @Override + public void onMessage(Message message) { + if (message.content() instanceof String content) { + logger.info( + "Customer Support Subscriber: {} sent the email to: {}", this.hashCode(), content); + } else { + logger.error( + "Unknown content type: {} expected: {}", + message.content().getClass().getSimpleName(), + String.class.getSimpleName()); + } + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/DelayedWeatherSubscriber.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/DelayedWeatherSubscriber.java new file mode 100644 index 000000000000..1f69cf0c57a7 --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/DelayedWeatherSubscriber.java @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import com.iluwatar.publish.subscribe.model.Message; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class subscribes to WEATHER topic. */ +@Slf4j +public class DelayedWeatherSubscriber implements Subscriber { + + private static final Logger logger = LoggerFactory.getLogger(DelayedWeatherSubscriber.class); + + @Override + public void onMessage(Message message) { + if (message.content() instanceof String content) { + processData(); + logger.info("Delayed Weather Subscriber: {} issued message: {}", this.hashCode(), content); + } else { + logger.error( + "Unknown content type: {} expected: {}", + message.content().getClass().getSimpleName(), + String.class.getSimpleName()); + } + } + + /** create an artificial delay to mimic the persistence and timeouts in real world. */ + private void processData() { + try { + TimeUnit.MILLISECONDS.sleep(2000); + } catch (InterruptedException e) { + logger.error("Interrupted!", e); + Thread.currentThread().interrupt(); + } + } +} diff --git a/embedded-value/src/test/java/com/iluwatar/embedded/value/AppTest.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/Subscriber.java similarity index 77% rename from embedded-value/src/test/java/com/iluwatar/embedded/value/AppTest.java rename to publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/Subscriber.java index 1e97bfabc36f..5deb441c6a52 100644 --- a/embedded-value/src/test/java/com/iluwatar/embedded/value/AppTest.java +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/Subscriber.java @@ -22,20 +22,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.embedded.value; +package com.iluwatar.publish.subscribe.subscriber; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import com.iluwatar.publish.subscribe.model.Message; -import org.junit.jupiter.api.Test; +/** This class represents a Subscriber. */ +public interface Subscriber { -/** - * Check whether the execution of the main method in {@link App} - * throws an exception. -*/ -public class AppTest { - - @Test - public void doesNotThrowException() { - assertDoesNotThrow(() -> App.main(new String[] {})); - } + /** + * On message method will trigger when the subscribed event is published. + * + * @param message the message contains the content of the published event + */ + void onMessage(Message message); } diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/Worker.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/WeatherSubscriber.java similarity index 64% rename from thread-pool/src/main/java/com/iluwatar/threadpool/Worker.java rename to publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/WeatherSubscriber.java index ad5002b96569..eda7e6d6cf9b 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/Worker.java +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/WeatherSubscriber.java @@ -22,30 +22,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; +package com.iluwatar.publish.subscribe.subscriber; +import com.iluwatar.publish.subscribe.model.Message; import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * Worker implements {@link Runnable} and thus can be executed by {@link - * java.util.concurrent.ExecutorService}. - */ +/** This class subscribes to WEATHER or TEMPERATURE topic. */ @Slf4j -public class Worker implements Runnable { - - private final Task task; +public class WeatherSubscriber implements Subscriber { - public Worker(final Task task) { - this.task = task; - } + private static final Logger logger = LoggerFactory.getLogger(WeatherSubscriber.class); @Override - public void run() { - LOGGER.info("{} processing {}", Thread.currentThread().getName(), task.toString()); - try { - Thread.sleep(task.getTimeMs()); - } catch (InterruptedException e) { - e.printStackTrace(); + public void onMessage(Message message) { + if (message.content() instanceof String content) { + logger.info("Weather Subscriber: {} issued message: {}", this.hashCode(), content); + } else { + logger.error( + "Unknown content type: {} expected: {}", + message.content().getClass().getSimpleName(), + String.class.getSimpleName()); } } } diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java new file mode 100644 index 000000000000..8080553078f2 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +public class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java new file mode 100644 index 000000000000..05e16e5a1110 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java @@ -0,0 +1,64 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.LoggerFactory; + +public class LoggerExtension implements BeforeEachCallback, AfterEachCallback { + + private final ListAppender listAppender = new ListAppender<>(); + private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + listAppender.stop(); + listAppender.list.clear(); + logger.detachAppender(listAppender); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + logger.addAppender(listAppender); + listAppender.start(); + } + + public List getMessages() { + return listAppender.list.stream().map(e -> e.getMessage()).collect(Collectors.toList()); + } + + public List getFormattedMessages() { + return listAppender.list.stream() + .map(e -> e.getFormattedMessage()) + .collect(Collectors.toList()); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java new file mode 100644 index 000000000000..a08624a3fab2 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java @@ -0,0 +1,41 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.Test; + +public class MessageTest { + + @Test + public void testMessage() { + final String content = "some content"; + Message message = new Message(content); + assertInstanceOf(String.class, message.content()); + assertEquals(content, String.valueOf(message.content())); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java new file mode 100644 index 000000000000..eb2d87c8c127 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java @@ -0,0 +1,64 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import com.iluwatar.publish.subscribe.subscriber.Subscriber; +import com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber; +import java.lang.reflect.Field; +import java.util.Set; +import org.junit.jupiter.api.Test; + +public class TopicTest { + + private static final String TOPIC_WEATHER = "WEATHER"; + + @Test + void testTopic() { + Topic topic = new Topic(TOPIC_WEATHER); + assertEquals(TOPIC_WEATHER, topic.getTopicName()); + } + + @Test + void testSubscribing() throws NoSuchFieldException, IllegalAccessException { + + Topic topic = new Topic(TOPIC_WEATHER); + Subscriber sub = new WeatherSubscriber(); + topic.addSubscriber(sub); + + Field field = topic.getClass().getDeclaredField("subscribers"); + field.setAccessible(true); + Object value = field.get(topic); + assertInstanceOf(Set.class, value); + + Set subscribers = (Set) field.get(topic); + assertEquals(1, subscribers.size()); + + topic.removeSubscriber(sub); + assertEquals(0, subscribers.size()); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java new file mode 100644 index 000000000000..d3db88c421bf --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java @@ -0,0 +1,84 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.publisher; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import com.iluwatar.publish.subscribe.LoggerExtension; +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import java.lang.reflect.Field; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class PublisherTest { + + @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension(); + + private static final String TOPIC_WEATHER = "WEATHER"; + private static final String TOPIC_CUSTOMER_SUPPORT = "CUSTOMER_SUPPORT"; + + @Test + void testRegisterTopic() throws NoSuchFieldException, IllegalAccessException { + Topic topic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topic); + + Field field = publisher.getClass().getDeclaredField("topics"); + field.setAccessible(true); + Object value = field.get(publisher); + assertInstanceOf(Set.class, value); + + Set topics = (Set) field.get(publisher); + assertEquals(1, topics.size()); + } + + @Test + void testPublish() { + Topic topic = new Topic(TOPIC_WEATHER); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topic); + + Message message = new Message("weather"); + assertDoesNotThrow(() -> publisher.publish(topic, message)); + } + + @Test + void testPublishUnregisteredTopic() { + Topic topic = new Topic(TOPIC_WEATHER); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topic); + + Topic topicUnregistered = new Topic(TOPIC_CUSTOMER_SUPPORT); + Message message = new Message("support"); + publisher.publish(topicUnregistered, message); + assertEquals( + "This topic is not registered: CUSTOMER_SUPPORT", + loggerExtension.getFormattedMessages().getFirst()); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/subscriber/SubscriberTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/subscriber/SubscriberTest.java new file mode 100644 index 000000000000..8fdc66ab7225 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/subscriber/SubscriberTest.java @@ -0,0 +1,207 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.publish.subscribe.LoggerExtension; +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import com.iluwatar.publish.subscribe.publisher.Publisher; +import com.iluwatar.publish.subscribe.publisher.PublisherImpl; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SubscriberTest { + + private static final Logger logger = LoggerFactory.getLogger(SubscriberTest.class); + @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension(); + + private static final String TOPIC_WEATHER = "WEATHER"; + private static final String TOPIC_TEMPERATURE = "TEMPERATURE"; + private static final String TOPIC_CUSTOMER_SUPPORT = "CUSTOMER_SUPPORT"; + + @Test + void testSubscribeToMultipleTopics() { + + Topic topicWeather = new Topic(TOPIC_WEATHER); + Topic topicTemperature = new Topic(TOPIC_TEMPERATURE); + Subscriber weatherSubscriber = new WeatherSubscriber(); + + topicWeather.addSubscriber(weatherSubscriber); + topicTemperature.addSubscriber(weatherSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topicWeather); + publisher.registerTopic(topicTemperature); + + publisher.publish(topicWeather, new Message("earthquake")); + publisher.publish(topicTemperature, new Message("-2C")); + + waitForOutput(); + assertEquals(2, loggerExtension.getFormattedMessages().size()); + } + + @Test + void testOnlyReceiveSubscribedTopic() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Topic customerSupportTopic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + publisher.registerTopic(customerSupportTopic); + + publisher.publish(customerSupportTopic, new Message("support@test.de")); + + waitForOutput(); + assertEquals(0, loggerExtension.getFormattedMessages().size()); + } + + @Test + void testMultipleSubscribersOnSameTopic() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber1 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber1); + + Subscriber weatherSubscriber2 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber2); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + + publisher.publish(weatherTopic, new Message("tornado")); + + waitForOutput(); + assertEquals(2, loggerExtension.getFormattedMessages().size()); + assertEquals( + "Weather Subscriber: " + weatherSubscriber1.hashCode() + " issued message: tornado", + getMessage(weatherSubscriber1.hashCode())); + assertEquals( + "Weather Subscriber: " + weatherSubscriber2.hashCode() + " issued message: tornado", + getMessage(weatherSubscriber2.hashCode())); + } + + @Test + void testMultipleSubscribersOnDifferentTopics() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Topic customerSupportTopic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Subscriber customerSupportSubscriber = new CustomerSupportSubscriber(); + customerSupportTopic.addSubscriber(customerSupportSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + publisher.registerTopic(customerSupportTopic); + + publisher.publish(weatherTopic, new Message("flood")); + publisher.publish(customerSupportTopic, new Message("support@test.at")); + + waitForOutput(); + assertEquals(2, loggerExtension.getFormattedMessages().size()); + assertEquals( + "Weather Subscriber: " + weatherSubscriber.hashCode() + " issued message: flood", + getMessage(weatherSubscriber.hashCode())); + assertEquals( + "Customer Support Subscriber: " + + customerSupportSubscriber.hashCode() + + " sent the email to: support@test.at", + getMessage(customerSupportSubscriber.hashCode())); + } + + @Test + void testInvalidContentOnTopics() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Topic customerSupportTopic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Subscriber customerSupportSubscriber = new CustomerSupportSubscriber(); + customerSupportTopic.addSubscriber(customerSupportSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + publisher.registerTopic(customerSupportTopic); + + publisher.publish(weatherTopic, new Message(123)); + publisher.publish(customerSupportTopic, new Message(34.56)); + + waitForOutput(); + assertTrue(loggerExtension.getFormattedMessages().getFirst().contains("Unknown content type")); + assertTrue(loggerExtension.getFormattedMessages().get(1).contains("Unknown content type")); + } + + @Test + void testUnsubscribe() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + + publisher.publish(weatherTopic, new Message("earthquake")); + + weatherTopic.removeSubscriber(weatherSubscriber); + publisher.publish(weatherTopic, new Message("tornado")); + + waitForOutput(); + assertEquals(1, loggerExtension.getFormattedMessages().size()); + assertTrue(loggerExtension.getFormattedMessages().getFirst().contains("earthquake")); + assertFalse(loggerExtension.getFormattedMessages().getFirst().contains("tornado")); + } + + private String getMessage(int subscriberHash) { + Optional message = + loggerExtension.getFormattedMessages().stream() + .filter(str -> str.contains(String.valueOf(subscriberHash))) + .findFirst(); + assertTrue(message.isPresent()); + return message.get(); + } + + private void waitForOutput() { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + logger.error("Interrupted!", e); + Thread.currentThread().interrupt(); + } + } +} diff --git a/queue-based-load-leveling/README.md b/queue-based-load-leveling/README.md new file mode 100644 index 000000000000..a72d39ea3c43 --- /dev/null +++ b/queue-based-load-leveling/README.md @@ -0,0 +1,217 @@ +--- +title: "Queue-Based Load Leveling Pattern in Java: Balancing Workloads for Scalable Performance" +shortTitle: Queue-Based Load Leveling +description: "Master the Queue-Based Load Leveling pattern in Java with our comprehensive guide. Learn how to enhance system resilience, manage workload efficiently, and prevent overload with effective asynchronous buffering techniques." +category: Resilience +language: en +tag: + - Asynchronous + - Buffering + - Decoupling + - Fault tolerance + - Messaging + - Scalability + - Synchronization + - Thread management +--- + +## Also known as + +* Load Leveling +* Message Queuing + +## Intent of Queue-Based Load Leveling Design Pattern + +The Queue-Based Load Leveling pattern expertly manages system load balancing in Java by utilizing a queue to efficiently distribute the workload among producers and consumers, enhancing system performance and scalability. + +## Detailed Explanation of Queue-Based Load Leveling Pattern with Real-World Examples + +Real-world example + +> In a practical scenario, akin to a bustling restaurant, Queue-Based Load Leveling serves as a system optimization strategy, where orders are systematically queued to maintain high service quality and efficiency. During peak hours, if all customers were served immediately, the kitchen would be overwhelmed, leading to long wait times and potential mistakes in orders. To manage this, the restaurant implements a queue-based load leveling system using a ticketing machine. +> +> When customers place orders, they receive a ticket number and their order is placed in a queue. The kitchen staff then processes orders one at a time in the order they were received. This ensures that the kitchen can handle the workload at a manageable pace, preventing overload and maintaining service quality. Customers wait comfortably knowing their order is in line and will be handled efficiently, even during the busiest times. + +In plain words + +> Queue-Based Load Leveling is a design pattern that uses a queue to manage and balance the workload between producers and consumers, preventing system overload and ensuring smooth processing. + +Wikipedia says + +> Message Queues are essential components for inter-process communication (IPC) and inter-thread communication, using queues to manage the passing of messages. They help in decoupling producers and consumers, allowing asynchronous processing, which is a key aspect of the Queue-Based Load Leveling pattern. + +## Programmatic Example of Queue-Based Load Leveling Pattern in Java + +The Queue-Based Load Leveling pattern helps to manage high-volume, sporadic bursts of tasks that can overwhelm a system. It uses a queue as a buffer to hold tasks, decoupling the task generation from task processing. Tasks are processed at a controlled rate, ensuring optimal load management and fault tolerance, crucial for maintaining robust system architecture. + +First, let's look at the `MessageQueue` and `Message` classes. The `MessageQueue` acts as a buffer, storing messages until they are retrieved by the `ServiceExecutor`. The `Message` represents the tasks to be processed. + +```java +public class Message { + // Message details +} +``` + +```java +public class MessageQueue { + private Queue queue; + + public MessageQueue() { + queue = new LinkedList<>(); + } + + // Method to add a message to the queue + public void addMessage(Message message) { + queue.add(message); + } + + // Method to retrieve a message from the queue + public Message getMessage() { + return queue.poll(); + } +} +``` + +Next, we have the `TaskGenerator` class. This class represents the task producers. It generates tasks and submits them to the `MessageQueue`. + +```java +public class TaskGenerator implements Runnable { + + private MessageQueue msgQueue; + private int taskCount; + + public TaskGenerator(MessageQueue msgQueue, int taskCount) { + this.msgQueue = msgQueue; + this.taskCount = taskCount; + } + + @Override + public void run() { + for (int i = 0; i < taskCount; i++) { + Message message = new Message(); // Create a new message + msgQueue.addMessage(message); // Add the message to the queue + } + } +} +``` + +The `ServiceExecutor` class represents the task consumer. It retrieves tasks from the `MessageQueue` and processes them. + +```java +public class ServiceExecutor implements Runnable { + + private MessageQueue msgQueue; + + public ServiceExecutor(MessageQueue msgQueue) { + this.msgQueue = msgQueue; + } + + @Override + public void run() { + while (true) { + Message message = msgQueue.getMessage(); // Retrieve a message from the queue + if (message != null) { + // Process the message + } else { + // No more messages to process + break; + } + } + } +} +``` + +Finally, we have the `App` class which sets up the `TaskGenerator` and `ServiceExecutor` threads and submits them to an `ExecutorService`. + +```java +public class App { + public static void main(String[] args) { + var msgQueue = new MessageQueue(); + + final var taskRunnable1 = new TaskGenerator(msgQueue, 5); + final var taskRunnable2 = new TaskGenerator(msgQueue, 1); + final var taskRunnable3 = new TaskGenerator(msgQueue, 2); + + final var srvRunnable = new ServiceExecutor(msgQueue); + + ExecutorService executor = Executors.newFixedThreadPool(2); + executor.submit(taskRunnable1); + executor.submit(taskRunnable2); + executor.submit(taskRunnable3); + executor.submit(srvRunnable); + + executor.shutdown(); + } +} +``` + +In this example, the `TaskGenerator` threads generate tasks at a variable rate and submit them to the `MessageQueue`. The `ServiceExecutor` retrieves the tasks from the queue and processes them at its own pace, preventing the system from being overwhelmed by peak loads. + +Running the application produces the following console output: + +``` +[main] INFO App - Submitting TaskGenerators and ServiceExecutor threads. +[main] INFO App - Initiating shutdown. Executor will shutdown only after all the Threads are completed. +[pool-1-thread-2] INFO TaskGenerator - Message-1 submitted by pool-1-thread-2 +[pool-1-thread-1] INFO TaskGenerator - Message-5 submitted by pool-1-thread-1 +[pool-1-thread-1] INFO TaskGenerator - Message-4 submitted by pool-1-thread-1 +[pool-1-thread-2] INFO TaskGenerator - Message-2 submitted by pool-1-thread-2 +[pool-1-thread-1] INFO TaskGenerator - Message-3 submitted by pool-1-thread-1 +[pool-1-thread-2] INFO TaskGenerator - Message-1 submitted by pool-1-thread-2 +[pool-1-thread-1] INFO TaskGenerator - Message-2 submitted by pool-1-thread-1 +[pool-1-thread-2] INFO ServiceExecutor - Message-1 submitted by pool-1-thread-2 is served. +[pool-1-thread-1] INFO TaskGenerator - Message-1 submitted by pool-1-thread-1 +[pool-1-thread-2] INFO ServiceExecutor - Message-5 submitted by pool-1-thread-1 is served. +[pool-1-thread-2] INFO ServiceExecutor - Message-4 submitted by pool-1-thread-1 is served. +[pool-1-thread-2] INFO ServiceExecutor - Message-2 submitted by pool-1-thread-2 is served. +[pool-1-thread-2] INFO ServiceExecutor - Message-3 submitted by pool-1-thread-1 is served. +[pool-1-thread-2] INFO ServiceExecutor - Message-1 submitted by pool-1-thread-2 is served. +[pool-1-thread-2] INFO ServiceExecutor - Message-2 submitted by pool-1-thread-1 is served. +[pool-1-thread-2] INFO ServiceExecutor - Message-1 submitted by pool-1-thread-1 is served. +[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. +[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. +[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. +[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. +[main] INFO App - Executor was shut down and Exiting. +[pool-1-thread-2] ERROR ServiceExecutor - sleep interrupted +``` + +## When to Use the Queue-Based Load Leveling Pattern in Java + +* When there are variable workloads, and you need to ensure that peak loads do not overwhelm the system +* In distributed systems where tasks are produced at a different rate than they are consumed +* For decoupling producers and consumers in an asynchronous messaging system + +## Real-World Applications of Queue-Based Load Leveling Pattern in Java + +* Amazon Web Services (AWS) Simple Queue Service (SQS) +* RabbitMQ +* Java Message Service (JMS) in enterprise Java applications + +## Benefits and Trade-offs of Queue-Based Load Leveling Pattern + +Benefits: + +* Decouples the producers and consumers, allowing each to operate at its own pace +* Increases system resilience and fault tolerance by preventing overload conditions +* Enhances scalability by allowing more consumers to be added to handle increased load + +Trade-offs: + +* Adds complexity to the system architecture +* May introduce latency as messages need to be queued and dequeued +* Requires additional components (queues) to be managed and monitored + +## Related Java Design Patterns + +* Asynchronous Messaging: Queue-Based Load Leveling uses asynchronous messaging to decouple producers and consumers +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Often used in conjunction with Queue-Based Load Leveling to prevent system overloads by temporarily halting message processing +* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Queue-Based Load Leveling is a specific application of the Producer-Consumer pattern where the queue serves as the intermediary +* [Retry](https://java-design-patterns.com/patterns/retry/): Works with Queue-Based Load Leveling to handle transient failures by retrying failed operations + +## References and Credits + +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3y6yv1z) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Queue-Based Load Leveling - Microsoft](https://docs.microsoft.com/en-us/azure/architecture/patterns/queue-based-load-leveling) diff --git a/queue-load-leveling/etc/queue-load-leveling.urm.puml b/queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml similarity index 100% rename from queue-load-leveling/etc/queue-load-leveling.urm.puml rename to queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml diff --git a/queue-load-leveling/etc/queue-load-leveling.gif b/queue-based-load-leveling/etc/queue-load-leveling.gif similarity index 100% rename from queue-load-leveling/etc/queue-load-leveling.gif rename to queue-based-load-leveling/etc/queue-load-leveling.gif diff --git a/queue-load-leveling/etc/queue-load-leveling.ucls b/queue-based-load-leveling/etc/queue-load-leveling.ucls similarity index 100% rename from queue-load-leveling/etc/queue-load-leveling.ucls rename to queue-based-load-leveling/etc/queue-load-leveling.ucls diff --git a/queue-based-load-leveling/etc/queue-load-leveling.urm.puml b/queue-based-load-leveling/etc/queue-load-leveling.urm.puml new file mode 100644 index 000000000000..ca90842d92dd --- /dev/null +++ b/queue-based-load-leveling/etc/queue-load-leveling.urm.puml @@ -0,0 +1,44 @@ +@startuml +package com.iluwatar.queue.load.leveling { + class App { + - LOGGER : Logger {static} + - SHUTDOWN_TIME : int {static} + + App() + + main(args : String[]) {static} + } + class Message { + - msg : String + + Message(msg : String) + + getMsg() : String + + toString() : String + } + class MessageQueue { + - LOGGER : Logger {static} + - blkQueue : BlockingQueue + + MessageQueue() + + retrieveMsg() : Message + + submitMsg(msg : Message) + } + class ServiceExecutor { + - LOGGER : Logger {static} + - msgQueue : MessageQueue + + ServiceExecutor(msgQueue : MessageQueue) + + run() + } + interface Task { + + submit(Message) {abstract} + } + class TaskGenerator { + - LOGGER : Logger {static} + - msgCount : int + - msgQueue : MessageQueue + + TaskGenerator(msgQueue : MessageQueue, msgCount : int) + + run() + + submit(msg : Message) + } +} +MessageQueue --> "-blkQueue" Message +ServiceExecutor --> "-msgQueue" MessageQueue +TaskGenerator --> "-msgQueue" MessageQueue +TaskGenerator ..|> Task +@enduml \ No newline at end of file diff --git a/queue-load-leveling/pom.xml b/queue-based-load-leveling/pom.xml similarity index 89% rename from queue-load-leveling/pom.xml rename to queue-based-load-leveling/pom.xml index dd376f30888a..701123f60bab 100644 --- a/queue-load-leveling/pom.xml +++ b/queue-based-load-leveling/pom.xml @@ -32,8 +32,16 @@ java-design-patterns 1.26.0-SNAPSHOT - queue-load-leveling + queue-based-load-leveling + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java similarity index 91% rename from queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java rename to queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java index 1f6f84cded4b..368346630db0 100644 --- a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java @@ -31,11 +31,10 @@ /** * Many solutions in the cloud involve running tasks that invoke services. In this environment, if a - * service is subjected to intermittent heavy loads, it can cause performance or reliability - * issues. + * service is subjected to intermittent heavy loads, it can cause performance or reliability issues. * - *

    A service could be a component that is part of the same solution as the tasks that utilize - * it, or it could be a third-party service providing access to frequently used resources such as a + *

    A service could be a component that is part of the same solution as the tasks that utilize it, + * or it could be a third-party service providing access to frequently used resources such as a * cache or a storage service. If the same service is utilized by a number of tasks running * concurrently, it can be difficult to predict the volume of requests to which the service might be * subjected at any given point in time. @@ -45,8 +44,7 @@ * The task posts a message containing the data required by the service to a queue. The queue acts * as a buffer, storing the message until it is retrieved by the service. The service retrieves the * messages from the queue and processes them. Requests from a number of tasks, which can be - * generated at a highly variable rate, can be passed to the service through the same message - * queue. + * generated at a highly variable rate, can be passed to the service through the same message queue. * *

    The queue effectively decouples the tasks from the service, and the service can handle the * messages at its own pace irrespective of the volume of requests from concurrent tasks. @@ -61,7 +59,7 @@ @Slf4j public class App { - //Executor shut down time limit. + // Executor shut down time limit. private static final int SHUTDOWN_TIME = 15; /** @@ -71,7 +69,7 @@ public class App { */ public static void main(String[] args) { - // An Executor that provides methods to manage termination and methods that can + // An Executor that provides methods to manage termination and methods that can // produce a Future for tracking progress of one or more asynchronous tasks. ExecutorService executor = null; @@ -90,7 +88,7 @@ public static void main(String[] args) { final var srvRunnable = new ServiceExecutor(msgQueue); // Create a ThreadPool of 2 threads and - // submit all Runnable task for execution to executor.. + // submit all Runnable task for execution to executor executor = Executors.newFixedThreadPool(2); executor.submit(taskRunnable1); executor.submit(taskRunnable2); @@ -100,12 +98,13 @@ public static void main(String[] args) { executor.submit(srvRunnable); // Initiates an orderly shutdown. - LOGGER.info("Initiating shutdown." - + " Executor will shutdown only after all the Threads are completed."); + LOGGER.info( + "Initiating shutdown." + + " Executor will shutdown only after all the Threads are completed."); executor.shutdown(); - // Wait for SHUTDOWN_TIME seconds for all the threads to complete - // their tasks and then shut down the executor and then exit. + // Wait for SHUTDOWN_TIME seconds for all the threads to complete + // their tasks and then shut down the executor and then exit. if (!executor.awaitTermination(SHUTDOWN_TIME, TimeUnit.SECONDS)) { LOGGER.info("Executor was shut down and Exiting."); executor.shutdownNow(); @@ -114,4 +113,4 @@ public static void main(String[] args) { LOGGER.error(e.getMessage()); } } -} \ No newline at end of file +} diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java similarity index 96% rename from queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java rename to queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java index c57c171a1c89..3b1eb84b0a41 100644 --- a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Message class with only one parameter. - */ +/** Message class with only one parameter. */ @Getter @RequiredArgsConstructor public class Message { @@ -39,4 +37,4 @@ public class Message { public String toString() { return msg; } -} \ No newline at end of file +} diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java similarity index 99% rename from queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java rename to queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java index 1d28faa54ca5..f027937f08ae 100644 --- a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java @@ -37,7 +37,7 @@ public class MessageQueue { private final BlockingQueue blkQueue; - // Default constructor when called creates Blocking Queue object. + // Default constructor when called creates Blocking Queue object. public MessageQueue() { this.blkQueue = new ArrayBlockingQueue<>(1024); } diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java similarity index 93% rename from queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java rename to queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java index a9e1e0271b58..0a8f1b5a7642 100644 --- a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java @@ -39,16 +39,14 @@ public ServiceExecutor(MessageQueue msgQueue) { this.msgQueue = msgQueue; } - /** - * The ServiceExecutor thread will retrieve each message and process it. - */ + /** The ServiceExecutor thread will retrieve each message and process it. */ public void run() { try { while (!Thread.currentThread().isInterrupted()) { var msg = msgQueue.retrieveMsg(); if (null != msg) { - LOGGER.info(msg.toString() + " is served."); + LOGGER.info(msg + " is served."); } else { LOGGER.info("Service Executor: Waiting for Messages to serve .. "); } @@ -59,4 +57,4 @@ public void run() { LOGGER.error(e.getMessage()); } } -} \ No newline at end of file +} diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java similarity index 98% rename from queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java rename to queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java index ab2b79e28acb..dfdb4454489f 100644 --- a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java @@ -24,9 +24,7 @@ */ package com.iluwatar.queue.load.leveling; -/** - * Task Interface. - */ +/** Task Interface. */ public interface Task { void submit(Message msg); } diff --git a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java similarity index 96% rename from queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java rename to queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java index 73babb9d0e06..904bb410182d 100644 --- a/queue-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java @@ -27,7 +27,7 @@ import lombok.extern.slf4j.Slf4j; /** - * TaskGenerator class. Each TaskGenerator thread will be a Worker which submit's messages to the + * TaskGenerator class. Each TaskGenerator thread will be a Worker which submits messages to the * queue. We need to mention the message count for each of the TaskGenerator threads. */ @Slf4j @@ -45,9 +45,7 @@ public TaskGenerator(MessageQueue msgQueue, int msgCount) { this.msgCount = msgCount; } - /** - * Submit messages to the Blocking Queue. - */ + /** Submit messages to the Blocking Queue. */ public void submit(Message msg) { try { this.msgQueue.submitMsg(msg); @@ -80,4 +78,4 @@ public void run() { LOGGER.error(e.getMessage()); } } -} \ No newline at end of file +} diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java similarity index 94% rename from queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java rename to queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java index 6ba3d87b43e0..06da02738391 100644 --- a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.queue.load.leveling; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Test - */ +import org.junit.jupiter.api.Test; + +/** Application Test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java similarity index 95% rename from queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java rename to queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java index 4ddcc9937f3b..fa7d6f3b156a 100644 --- a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test case for submitting and retrieving messages from Blocking Queue. - */ +/** Test case for submitting and retrieving messages from Blocking Queue. */ class MessageQueueTest { @Test @@ -44,5 +42,4 @@ void messageQueueTest() { // retrieve message assertEquals("MessageQueue Test", msgQueue.retrieveMsg().getMsg()); } - } diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java similarity index 96% rename from queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java rename to queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java index a0314ed04495..8af65cc1e947 100644 --- a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test case for creating and checking the Message. - */ +/** Test case for creating and checking the Message. */ class MessageTest { @Test diff --git a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java similarity index 97% rename from queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java rename to queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java index f8667a528180..395f5ec40bda 100644 --- a/queue-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java @@ -24,10 +24,9 @@ */ package com.iluwatar.queue.load.leveling; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; /** * Test case for submitting Message to Blocking Queue by TaskGenerator and retrieve the message by @@ -53,5 +52,4 @@ void taskGeneratorTest() { assertNotNull(srvExeThr); } - } diff --git a/queue-load-leveling/README.md b/queue-load-leveling/README.md deleted file mode 100644 index 49c37e6238dc..000000000000 --- a/queue-load-leveling/README.md +++ /dev/null @@ -1,307 +0,0 @@ ---- -title: Queue based load leveling -category: Concurrency -language: en -tag: - - Decoupling - - Performance - - Cloud distributed ---- - -## Intent -Use a queue that acts as a buffer between a task and a service that it invokes in order to smooth -intermittent heavy loads that may otherwise cause the service to fail or the task to time out. -This pattern can help to minimize the impact of peaks in demand on availability and responsiveness -for both the task and the service. - -## Explanation - -Real world example -> A Microsoft Azure web role stores data by using a separate storage service. If a large number of instances of the web -> role run concurrently, it is possible that the storage service could be overwhelmed and be unable to respond to requests -> quickly enough to prevent these requests from timing out or failing. - -In plain words -> Makes resource-load balanced by ensuring an intermediate data structure like queue that makes bridge -> between service-takers and service-givers. Where both takers and givers are running asynchronously and -> service-takers can tolerate some amount of delay to get feedback. -> - -Wikipedia says - -> In computing, load balancing is the process of distributing a set of tasks over a set of resources -> (computing units), with the aim of making their overall processing more efficient. Load balancing can -> optimize the response time and avoid unevenly overloading some compute nodes while other compute nodes -> are left idle. - -**Programmatic Example** - -TaskGenerator implements Task, runnable interfaces. Hence, It runs asynchronously. - -```java -/** - * Task Interface. - */ -public interface Task { - void submit(Message msg); -} -``` -It submits tasks to ServiceExecutor to serve tasks. -```java -/** - * TaskGenerator class. Each TaskGenerator thread will be a Worker which submit's messages to the - * queue. We need to mention the message count for each of the TaskGenerator threads. - */ -@Slf4j -public class TaskGenerator implements Task, Runnable { - - // MessageQueue reference using which we will submit our messages. - private final MessageQueue msgQueue; - - // Total message count that a TaskGenerator will submit. - private final int msgCount; - - // Parameterized constructor. - public TaskGenerator(MessageQueue msgQueue, int msgCount) { - this.msgQueue = msgQueue; - this.msgCount = msgCount; - } - - /** - * Submit messages to the Blocking Queue. - */ - public void submit(Message msg) { - try { - this.msgQueue.submitMsg(msg); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - /** - * Each TaskGenerator thread will submit all the messages to the Queue. After every message - * submission TaskGenerator thread will sleep for 1 second. - */ - public void run() { - var count = this.msgCount; - - try { - while (count > 0) { - var statusMsg = "Message-" + count + " submitted by " + Thread.currentThread().getName(); - this.submit(new Message(statusMsg)); - - LOGGER.info(statusMsg); - - // reduce the message count. - count--; - - // Make the current thread to sleep after every Message submission. - Thread.sleep(1000); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } -} -``` -It also implements runnable interface and run asynchronously. It retrieves tasks one by one -from blockingQueue to serve. -```java -/** - * ServiceExecuotr class. This class will pick up Messages one by one from the Blocking Queue and - * process them. - */ -@Slf4j -public class ServiceExecutor implements Runnable { - - private final MessageQueue msgQueue; - - public ServiceExecutor(MessageQueue msgQueue) { - this.msgQueue = msgQueue; - } - - /** - * The ServiceExecutor thread will retrieve each message and process it. - */ - public void run() { - try { - while (!Thread.currentThread().isInterrupted()) { - var msg = msgQueue.retrieveMsg(); - - if (null != msg) { - LOGGER.info(msg.toString() + " is served."); - } else { - LOGGER.info("Service Executor: Waiting for Messages to serve .. "); - } - - Thread.sleep(1000); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } -} -``` - -BlockingQueue data-structure is used in MessageQueue class for acting buffer -between TaskGenerator to ServiceExecutor. - -```java -public class MessageQueue { - - private final BlockingQueue blkQueue; - - // Default constructor when called creates Blocking Queue object. - public MessageQueue() { - this.blkQueue = new ArrayBlockingQueue<>(1024); - } - - /** - * All the TaskGenerator threads will call this method to insert the Messages in to the Blocking - * Queue. - */ - public void submitMsg(Message msg) { - try { - if (null != msg) { - blkQueue.add(msg); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - /** - * All the messages will be retrieved by the ServiceExecutor by calling this method and process - * them. Retrieves and removes the head of this queue, or returns null if this queue is empty. - */ - public Message retrieveMsg() { - try { - return blkQueue.poll(); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - return null; - } -} -``` -TaskGenerator submit message object to ServiceExecutor for serving. -```java -/** - * Message class with only one parameter. - */ -@Getter -@RequiredArgsConstructor -public class Message { - private final String msg; - - @Override - public String toString() { - return msg; - } -} -``` -To simulate the situation ExecutorService is used here. ExecutorService automatically provides a pool of threads and -an API for assigning tasks to it. -```java -public class App { - - //Executor shut down time limit. - private static final int SHUTDOWN_TIME = 15; - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - - // An Executor that provides methods to manage termination and methods that can - // produce a Future for tracking progress of one or more asynchronous tasks. - ExecutorService executor = null; - - try { - // Create a MessageQueue object. - var msgQueue = new MessageQueue(); - - LOGGER.info("Submitting TaskGenerators and ServiceExecutor threads."); - - // Create three TaskGenerator threads. Each of them will submit different number of jobs. - final var taskRunnable1 = new TaskGenerator(msgQueue, 5); - final var taskRunnable2 = new TaskGenerator(msgQueue, 1); - final var taskRunnable3 = new TaskGenerator(msgQueue, 2); - - // Create e service which should process the submitted jobs. - final var srvRunnable = new ServiceExecutor(msgQueue); - - // Create a ThreadPool of 2 threads and - // submit all Runnable task for execution to executor.. - executor = Executors.newFixedThreadPool(2); - executor.submit(taskRunnable1); - executor.submit(taskRunnable2); - executor.submit(taskRunnable3); - - // submitting serviceExecutor thread to the Executor service. - executor.submit(srvRunnable); - - // Initiates an orderly shutdown. - LOGGER.info("Initiating shutdown." - + " Executor will shutdown only after all the Threads are completed."); - executor.shutdown(); - - // Wait for SHUTDOWN_TIME seconds for all the threads to complete - // their tasks and then shut down the executor and then exit. - if (!executor.awaitTermination(SHUTDOWN_TIME, TimeUnit.SECONDS)) { - LOGGER.info("Executor was shut down and Exiting."); - executor.shutdownNow(); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } -} -``` - -The console output -``` -[main] INFO App - Submitting TaskGenerators and ServiceExecutor threads. -[main] INFO App - Initiating shutdown. Executor will shutdown only after all the Threads are completed. -[pool-1-thread-2] INFO TaskGenerator - Message-1 submitted by pool-1-thread-2 -[pool-1-thread-1] INFO TaskGenerator - Message-5 submitted by pool-1-thread-1 -[pool-1-thread-1] INFO TaskGenerator - Message-4 submitted by pool-1-thread-1 -[pool-1-thread-2] INFO TaskGenerator - Message-2 submitted by pool-1-thread-2 -[pool-1-thread-1] INFO TaskGenerator - Message-3 submitted by pool-1-thread-1 -[pool-1-thread-2] INFO TaskGenerator - Message-1 submitted by pool-1-thread-2 -[pool-1-thread-1] INFO TaskGenerator - Message-2 submitted by pool-1-thread-1 -[pool-1-thread-2] INFO ServiceExecutor - Message-1 submitted by pool-1-thread-2 is served. -[pool-1-thread-1] INFO TaskGenerator - Message-1 submitted by pool-1-thread-1 -[pool-1-thread-2] INFO ServiceExecutor - Message-5 submitted by pool-1-thread-1 is served. -[pool-1-thread-2] INFO ServiceExecutor - Message-4 submitted by pool-1-thread-1 is served. -[pool-1-thread-2] INFO ServiceExecutor - Message-2 submitted by pool-1-thread-2 is served. -[pool-1-thread-2] INFO ServiceExecutor - Message-3 submitted by pool-1-thread-1 is served. -[pool-1-thread-2] INFO ServiceExecutor - Message-1 submitted by pool-1-thread-2 is served. -[pool-1-thread-2] INFO ServiceExecutor - Message-2 submitted by pool-1-thread-1 is served. -[pool-1-thread-2] INFO ServiceExecutor - Message-1 submitted by pool-1-thread-1 is served. -[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. -[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. -[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. -[pool-1-thread-2] INFO ServiceExecutor - Service Executor: Waiting for Messages to serve .. -[main] INFO App - Executor was shut down and Exiting. -[pool-1-thread-2] ERROR ServiceExecutor - sleep interrupted -``` - -## Class diagram -![alt text](./etc/queue-load-leveling.gif "queue-load-leveling") - -## Applicability - -* This pattern is ideally suited to any type of application that uses services that may be subject to overloading. -* This pattern might not be suitable if the application expects a response from the service with minimal latency. - -## Tutorials -* [Queue-Based Load Leveling Pattern](http://java-design-patterns.com/blog/queue-load-leveling/) - - -## Credits - -* [Queue-Based Load Leveling pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/queue-based-load-leveling) -* [Load-Balancing](https://www.wikiwand.com/en/Load_balancing_(computing)) diff --git a/reactor/README.md b/reactor/README.md index d20b8946d40c..50ee482cb15a 100644 --- a/reactor/README.md +++ b/reactor/README.md @@ -1,28 +1,200 @@ --- -title: Reactor +title: "Reactor Pattern in Java: Mastering Non-blocking Event-Driven Architectures" +shortTitle: Reactor +description: "Explore the Reactor pattern in Java: Learn how this concurrency model handles multiple simultaneous I/O operations using a single thread for improved performance and scalability. Ideal for developers building high-performance network applications." category: Concurrency language: en tag: - - Performance - - Reactive + - Asynchronous + - Event-driven + - Fault tolerance + - Messaging + - Reactive + - Scalability + - Synchronization + - Thread management --- -## Intent -The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. The application can register specific handlers for processing which are called by reactor on specific events. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. +## Also known as + +* Dispatcher +* Notifier + +## Intent of Reactor Design Pattern + +The Reactor pattern is designed to handle concurrent service requests efficiently, using a single or limited number of threads, making it a cornerstone for asynchronous, event-driven systems. + +## Detailed Explanation of Reactor Pattern with Real-World Examples + +Real-world example + +> This design pattern is analogous to a head chef in a busy kitchen, demonstrating its ability to manage high-scalability demands and maintain efficient task distribution in multithreaded environments. Instead of each chef handling one order at a time, there is a head chef who acts as the dispatcher. The head chef receives all the orders and decides which chef will handle which part of each order, ensuring that all chefs are utilized efficiently. This way, the kitchen can handle many orders simultaneously, ensuring that dishes are prepared quickly and efficiently without any one chef becoming a bottleneck. This setup is analogous to the Reactor pattern, where the head chef dispatches tasks (events) to various chefs (event handlers) to process multiple tasks concurrently. + +In plain words + +> The Reactor pattern efficiently handles multiple concurrent service requests by dispatching them to appropriate event handlers using a single or a limited number of threads. + +Wikipedia says + +> The reactor software design pattern is an event handling strategy that can respond to many potential service requests concurrently. The pattern's key component is an event loop, running in a single thread or process, which demultiplexes incoming requests and dispatches them to the correct request handler. + +## Programmatic Example of Reactor Pattern in Java + +The Reactor design pattern is a concurrency model that efficiently handles multiple simultaneous I/O operations using a single or a limited number of threads. It is particularly useful in scenarios where an application needs to handle multiple clients sending service requests concurrently. + +In the given code, the Reactor pattern is implemented using Java's NIO (Non-blocking I/O) framework. The key components of this pattern in the code are: + +1. **Reactor**: This is the event loop that demultiplexes the incoming requests and dispatches them to the appropriate handlers. In our example, `NioReactor` is the reactor. + +2. **Dispatcher**: This is responsible for managing the execution of the tasks that are triggered by the events. In our example, `Dispatcher` is the dispatcher and `ThreadPoolDispatcher` is a concrete implementation of it. + +3. **Handles**: These are resources that are managed by the reactor. They are associated with specific events and are used by the reactor to identify the event handlers to which the events should be dispatched. In our example, `AbstractNioChannel` represents a handle. + +4. **Event Handlers**: These are associated with specific handles and are responsible for handling the events that occur on those handles. In our example, `ChannelHandler` is an event handler and `LoggingHandler` is a concrete implementation of it. + +5. **Synchronous Event Demultiplexer**: This is a system-level component (not shown in the code) that provides a blocking call that waits for events to occur on any of the handles. In our example, this is part of the Java NIO framework. + +6. **Concrete Event Handlers**: These are application-specific implementations of the event handlers. In our example, `LoggingHandler` is a concrete event handler. + +7. **Initiation Dispatcher**: This is a component that initializes the association between event handlers and handles. In our example, this is done by the `registerChannel` method in the `NioReactor` class. + +**Part 1: Creating the Dispatcher** + +The first part of our example involves creating a dispatcher. The dispatcher is responsible for managing the execution of the tasks that are triggered by the events. + +```java +// Create a dispatcher +Dispatcher dispatcher = new ThreadPoolDispatcher(2); +``` + +In this snippet, we're creating a `ThreadPoolDispatcher` with 2 threads. This dispatcher will use a thread pool to execute the tasks. + +**Part 2: Creating the Reactor** + +Next, we create a reactor with the dispatcher. The reactor is the core component of the Reactor pattern. It waits for events on multiple channels registered to it in an event loop and dispatches them to the appropriate handlers. + +```java +// Create a reactor with the dispatcher +NioReactor reactor = new NioReactor(dispatcher); +``` + +Here, we're creating a `NioReactor` and passing the dispatcher we created earlier to its constructor. + +**Part 3: Creating the Handler** + +Now, we create a handler for handling events. The handler is responsible for processing the events that occur on the channels. + +```java +// Create a handler for handling events +ChannelHandler loggingHandler = new LoggingHandler(); +``` + +In this snippet, we're creating a `LoggingHandler`. This handler will log the events that occur on the channels. + +**Part 4: Registering Channels with the Reactor** + +Next, we register channels with the reactor. These channels are the sources of the events that the reactor will handle. + +```java +// Register channels with the reactor +reactor.registerChannel(new NioServerSocketChannel(16666, loggingHandler)); +reactor.registerChannel(new NioDatagramChannel(16668, loggingHandler)); +``` + +Here, we're registering a `NioServerSocketChannel` and a `NioDatagramChannel` with the reactor. These channels are associated with the `LoggingHandler` we created earlier. + +**Part 5: Starting the Reactor** + +Finally, we start the reactor. Once started, the reactor begins to listen for events on the registered channels. + +```java +// Start the reactor +reactor.start(); +``` + +In this snippet, we're starting the reactor. From this point on, the reactor will start handling events from the registered channels. + +**Part 6: Creating the App Class** + +The `App` class is the entry point of our application. It creates the reactor, registers the channels, and starts the reactor. + +```java +public class App { + + public static void main(String[] args) { + // Create a dispatcher + Dispatcher dispatcher = new ThreadPoolDispatcher(2); + + // Create a reactor with the dispatcher + NioReactor reactor = new NioReactor(dispatcher); + + // Create a handler for handling events + ChannelHandler loggingHandler = new LoggingHandler(); + + // Register channels with the reactor + reactor.registerChannel(new NioServerSocketChannel(16666, loggingHandler)); + reactor.registerChannel(new NioDatagramChannel(16668, loggingHandler)); + + // Start the reactor + reactor.start(); + } +} +``` + +In this snippet, we're creating an instance of the `App` class. Inside the `main` method, we're following the same steps as before: creating a dispatcher, creating a reactor with the dispatcher, creating a handler, registering channels with the reactor, and finally starting the reactor. + +This `App` class demonstrates how an application interacts with the reactor. It sets up the necessary components (dispatcher, reactor, handler, channels) and starts the reactor. Once the reactor is started, it will handle events from the registered channels using the specified handler. + +Running the code produces the following output: + +``` +09:50:08.317 [main] INFO com.iluwatar.reactor.framework.NioServerSocketChannel -- Bound TCP socket at port: 16666 +09:50:08.320 [main] INFO com.iluwatar.reactor.framework.NioServerSocketChannel -- Bound TCP socket at port: 16667 +09:50:08.323 [main] INFO com.iluwatar.reactor.framework.NioDatagramChannel -- Bound UDP socket at port: 16668 +09:50:08.324 [main] INFO com.iluwatar.reactor.framework.NioDatagramChannel -- Bound UDP socket at port: 16669 +09:50:08.324 [pool-2-thread-1] INFO com.iluwatar.reactor.framework.NioReactor -- Reactor started, waiting for events... +``` + +This concludes our detailed explanation of the Reactor design pattern. The Reactor pattern allows us to handle multiple simultaneous I/O operations efficiently using a single or a limited number of threads. + +## Detailed Explanation of Reactor Pattern with Real-World Examples -## Class diagram ![Reactor](./etc/reactor.png "Reactor") -## Applicability -Use Reactor pattern when +## When to Use the Reactor Pattern in Java + +Employ the Reactor pattern in scenarios requiring low-latency and high-throughput in server-side applications, making it an essential strategy for modern networking frameworks and web servers. + +## Real-World Applications of Reactor Pattern in Java + +* Netty: An asynchronous event-driven network application framework for rapid development of maintainable high-performance protocol servers and clients. +* Akka: A toolkit and runtime for building concurrent, distributed, and fault-tolerant applications on the JVM. +* Java NIO (New I/O): Provides non-blocking I/O operations, allowing a single thread to manage multiple channels. + +## Benefits and Trade-offs of Reactor Pattern + +Benefits: + +* Improves application performance by efficiently handling multiple simultaneous connections. +* Reduces resource consumption by using a small number of threads to handle many I/O operations. +* Enhances scalability by allowing applications to serve many clients with minimal threads. + +Trade-offs: + +* Increased complexity in managing state and event handling. +* Debugging and maintaining asynchronous code can be challenging. +* Potential difficulty in ensuring thread safety and avoiding race conditions. -* A server application needs to handle concurrent service requests from multiple clients. -* A server application needs to be available for receiving requests from new clients even when handling older client requests. -* A server must maximize throughput, minimize latency and use CPU efficiently without blocking. +## Related Java Design Patterns -## Credits +* [Observer](https://java-design-patterns.com/patterns/observer/): Reactor uses the Observer pattern for handling events where event handlers are notified of changes. +* Proactor: Similar to Reactor but handles asynchronous I/O completion rather than readiness. +* [Command](https://java-design-patterns.com/patterns/command/): Encapsulates a request as an object, allowing parameterization and queuing of requests. + +## References and Credits -* [Douglas C. Schmidt - Reactor](https://www.dre.vanderbilt.edu/~schmidt/PDF/Reactor.pdf) -* [Pattern Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://www.amazon.com/gp/product/0471606952/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0471606952&linkCode=as2&tag=javadesignpat-20&linkId=889e4af72dca8261129bf14935e0f8dc) -* [Doug Lea - Scalable IO in Java](http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf) -* [Netty](http://netty.io/) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) +* [Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications](https://amzn.to/4dNTLJC) +* [Scalable IO in Java - Doug Lea](http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf) +* [Reactor - An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events (Douglas C. Schmidt)](https://www.dre.vanderbilt.edu/~schmidt/PDF/Reactor.pdf) diff --git a/reactor/pom.xml b/reactor/pom.xml index ff884ab586b1..738959d7ccc5 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -34,6 +34,14 @@ reactor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/App.java b/reactor/src/main/java/com/iluwatar/reactor/app/App.java index cb10d5edb3c0..a01738d2e64b 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/App.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/App.java @@ -47,45 +47,35 @@ *

    PROBLEM
    * Server applications in a distributed system must handle multiple clients that send them service * requests. Following forces need to be resolved: + * *

      - *
    • Availability
    • - *
    • Efficiency
    • - *
    • Programming Simplicity
    • - *
    • Adaptability
    • + *
    • Availability + *
    • Efficiency + *
    • Programming Simplicity + *
    • Adaptability *
    * *

    PARTICIPANTS
    + * *

      - *
    • Synchronous Event De-multiplexer - *

      - * {@link NioReactor} plays the role of synchronous event de-multiplexer. - * It waits for events on multiple channels registered to it in an event loop. - *

      - *
    • - *
    • Initiation Dispatcher - *

      - * {@link NioReactor} plays this role as the application specific {@link ChannelHandler}s - * are registered to the reactor. - *

      - *
    • - *
    • Handle - *

      - * {@link AbstractNioChannel} acts as a handle that is registered to the reactor. - * When any events occur on a handle, reactor calls the appropriate handler. - *

      - *
    • - *
    • Event Handler - *

      - * {@link ChannelHandler} acts as an event handler, which is bound to a - * channel and is called back when any event occurs on any of its associated handles. Application - * logic resides in event handlers. - *

      - *
    • + *
    • Synchronous Event De-multiplexer + *

      {@link NioReactor} plays the role of synchronous event de-multiplexer. It waits for + * events on multiple channels registered to it in an event loop. + *

    • Initiation Dispatcher + *

      {@link NioReactor} plays this role as the application specific {@link ChannelHandler}s + * are registered to the reactor. + *

    • Handle + *

      {@link AbstractNioChannel} acts as a handle that is registered to the reactor. When any + * events occur on a handle, reactor calls the appropriate handler. + *

    • Event Handler + *

      {@link ChannelHandler} acts as an event handler, which is bound to a channel and is + * called back when any event occurs on any of its associated handles. Application logic + * resides in event handlers. *

    + * * The application utilizes single thread to listen for requests on all ports. It does not create a * separate thread for each client, which provides better scalability under load (number of clients - * increase). - * The example uses Java NIO framework to implement the Reactor. + * increase). The example uses Java NIO framework to implement the Reactor. */ public class App { @@ -103,9 +93,7 @@ public App(Dispatcher dispatcher) { this.dispatcher = dispatcher; } - /** - * App entry. - */ + /** App entry. */ public static void main(String[] args) throws IOException { new App(new ThreadPoolDispatcher(2)).start(); } @@ -143,7 +131,7 @@ public void start() throws IOException { * Stops the NIO reactor. This is a blocking call. * * @throws InterruptedException if interrupted while stopping the reactor. - * @throws IOException if any I/O error occurs + * @throws IOException if any I/O error occurs */ public void stop() throws InterruptedException, IOException { reactor.stop(); diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java index 53f67199158d..8636150bd1bf 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java @@ -70,9 +70,7 @@ public void start() throws IOException { service.execute(new UdpLoggingClient("Client 4", 16669)); } - /** - * Stops logging clients. This is a blocking call. - */ + /** Stops logging clients. This is a blocking call. */ public void stop() { service.shutdown(); if (!service.isTerminated()) { @@ -94,9 +92,7 @@ private static void artificialDelayOf(long millis) { } } - /** - * A logging client that sends requests to Reactor on TCP socket. - */ + /** A logging client that sends requests to Reactor on TCP socket. */ static class TcpLoggingClient implements Runnable { private final int serverPort; @@ -141,12 +137,9 @@ private void sendLogRequests(PrintWriter writer, InputStream inputStream) throws artificialDelayOf(100); } } - } - /** - * A logging client that sends requests to Reactor on UDP socket. - */ + /** A logging client that sends requests to Reactor on UDP socket. */ static class UdpLoggingClient implements Runnable { private final String clientName; private final InetSocketAddress remoteAddress; @@ -155,7 +148,7 @@ static class UdpLoggingClient implements Runnable { * Creates a new UDP logging client. * * @param clientName the name of the client to be sent in logging requests. - * @param port the port on which client will send logging requests. + * @param port the port on which client will send logging requests. * @throws UnknownHostException if localhost is unknown */ public UdpLoggingClient(String clientName, int port) throws UnknownHostException { diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java b/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java index 404cecbf2377..bc71b0353bf2 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java @@ -40,9 +40,7 @@ public class LoggingHandler implements ChannelHandler { private static final byte[] ACK = "Data logged successfully".getBytes(); - /** - * Decodes the received data and logs it on standard console. - */ + /** Decodes the received data and logs it on standard console. */ @Override public void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key) { /* @@ -52,8 +50,7 @@ public void handleChannelRead(AbstractNioChannel channel, Object readObject, Sel if (readObject instanceof ByteBuffer) { doLogging((ByteBuffer) readObject); sendReply(channel, key); - } else if (readObject instanceof DatagramPacket) { - var datagram = (DatagramPacket) readObject; + } else if (readObject instanceof DatagramPacket datagram) { doLogging(datagram.getData()); sendReply(channel, datagram, key); } else { @@ -62,10 +59,7 @@ public void handleChannelRead(AbstractNioChannel channel, Object readObject, Sel } private static void sendReply( - AbstractNioChannel channel, - DatagramPacket incomingPacket, - SelectionKey key - ) { + AbstractNioChannel channel, DatagramPacket incomingPacket, SelectionKey key) { /* * Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming * message. diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java index 6d98230668dd..df7ec1c827d8 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java @@ -32,6 +32,7 @@ import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import lombok.Getter; /** * This represents the Handle of Reactor pattern. These are resources managed by OS which can @@ -46,7 +47,7 @@ public abstract class AbstractNioChannel { private final SelectableChannel channel; - private final ChannelHandler handler; + @Getter private final ChannelHandler handler; private final Map> channelToPendingWrites; private NioReactor reactor; @@ -62,9 +63,7 @@ public AbstractNioChannel(ChannelHandler handler, SelectableChannel channel) { this.channelToPendingWrites = new ConcurrentHashMap<>(); } - /** - * Injects the reactor in this channel. - */ + /** Injects the reactor in this channel. */ void setReactor(NioReactor reactor) { this.reactor = reactor; } @@ -104,15 +103,6 @@ public SelectableChannel getJavaChannel() { */ public abstract Object read(SelectionKey key) throws IOException; - /** - * Get handler. - * - * @return the handler associated with this channel. - */ - public ChannelHandler getHandler() { - return handler; - } - /* * Called from the context of reactor thread when the key becomes writable. The channel writes the * whole pending block of data at once. @@ -132,7 +122,7 @@ void flush(SelectionKey key) throws IOException { * Writes the data to the channel. * * @param pendingWrite the data to be written on channel. - * @param key the key which is writable. + * @param key the key which is writable. * @throws IOException if any I/O error occurs. */ protected abstract void doWrite(Object pendingWrite, SelectionKey key) throws IOException; @@ -156,13 +146,15 @@ void flush(SelectionKey key) throws IOException { * * * @param data the data to be written on underlying channel. - * @param key the key which is writable. + * @param key the key which is writable. */ public void write(Object data, SelectionKey key) { var pendingWrites = this.channelToPendingWrites.get(key.channel()); if (pendingWrites == null) { synchronized (this.channelToPendingWrites) { - pendingWrites = this.channelToPendingWrites.computeIfAbsent(key.channel(), k -> new ConcurrentLinkedQueue<>()); + pendingWrites = + this.channelToPendingWrites.computeIfAbsent( + key.channel(), k -> new ConcurrentLinkedQueue<>()); } } pendingWrites.add(data); diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java b/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java index 46cb2fc54cf6..1a37f2b5984c 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java @@ -31,17 +31,16 @@ * to it by the {@link Dispatcher}. This is where the application logic resides. * *

    A {@link ChannelHandler} can be associated with one or many {@link AbstractNioChannel}s, and - * whenever an event occurs on any of the associated channels, the handler is notified of the - * event. + * whenever an event occurs on any of the associated channels, the handler is notified of the event. */ public interface ChannelHandler { /** * Called when the {@code channel} receives some data from remote peer. * - * @param channel the channel from which the data was received. + * @param channel the channel from which the data was received. * @param readObject the data read. - * @param key the key on which read event occurred. + * @param key the key on which read event occurred. */ void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key); } diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java b/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java index 583c1241bcf9..403b58c90627 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java @@ -30,8 +30,9 @@ * Represents the event dispatching strategy. When {@link NioReactor} senses any event on the * registered {@link AbstractNioChannel}s then it de-multiplexes the event type, read or write or * connect, and then calls the {@link Dispatcher} to dispatch the read events. This decouples the - * I/O processing from application specific processing.
    Dispatcher should call the {@link - * ChannelHandler} associated with the channel on which event occurred. + * I/O processing from application specific processing.
    + * Dispatcher should call the {@link ChannelHandler} associated with the channel on which event + * occurred. * *

    The application can customize the way in which event is dispatched such as using the reactor * thread to dispatch event to channels or use a worker pool to do the non I/O processing. @@ -47,9 +48,9 @@ public interface Dispatcher { * *

    The type of readObject depends on the channel on which data was received. * - * @param channel on which read event occurred + * @param channel on which read event occurred * @param readObject object read by channel - * @param key on which event occurred + * @param key on which event occurred */ void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key); diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java index 2c31a3c9e993..0272d3c90f59 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java @@ -31,11 +31,11 @@ import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; -/** - * A wrapper over {@link DatagramChannel} which can read and write data on a DatagramChannel. - */ +/** A wrapper over {@link DatagramChannel} which can read and write data on a DatagramChannel. */ @Slf4j public class NioDatagramChannel extends AbstractNioChannel { @@ -48,7 +48,7 @@ public class NioDatagramChannel extends AbstractNioChannel { *

    Note the constructor does not bind the socket, {@link #bind()} method should be called for * binding the socket. * - * @param port the port to be bound to listen for incoming datagram requests. + * @param port the port to be bound to listen for incoming datagram requests. * @param handler the handler to be used for handling incoming requests on this channel. * @throws IOException if any I/O error occurs. */ @@ -128,13 +128,12 @@ public void write(Object data, SelectionKey key) { super.write(data, key); } - /** - * Container of data used for {@link NioDatagramChannel} to communicate with remote peer. - */ + /** Container of data used for {@link NioDatagramChannel} to communicate with remote peer. */ + @Getter public static class DatagramPacket { - private SocketAddress sender; private final ByteBuffer data; - private SocketAddress receiver; + @Setter private SocketAddress sender; + @Setter private SocketAddress receiver; /** * Creates a container with underlying data. @@ -144,50 +143,5 @@ public static class DatagramPacket { public DatagramPacket(ByteBuffer data) { this.data = data; } - - /** - * Get sender address. - * - * @return the sender address. - */ - public SocketAddress getSender() { - return sender; - } - - /** - * Sets the sender address of this packet. - * - * @param sender the sender address. - */ - public void setSender(SocketAddress sender) { - this.sender = sender; - } - - /** - * Get receiver address. - * - * @return the receiver address. - */ - public SocketAddress getReceiver() { - return receiver; - } - - /** - * Sets the intended receiver address. This must be set when writing to the channel. - * - * @param receiver the receiver address. - */ - public void setReceiver(SocketAddress receiver) { - this.receiver = receiver; - } - - /** - * Get data. - * - * @return the underlying message that will be written on channel. - */ - public ByteBuffer getData() { - return data; - } } } diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java index fb0ab5784acd..714b16d59929 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java @@ -42,9 +42,8 @@ * synchronously de-multiplexes the event which can be any of read, write or accept, and dispatches * the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}. * - *

    Implementation: A NIO reactor runs in its own thread when it is started using {@link - * #start()} method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event - * De-multiplexing. + *

    Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()} + * method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing. * *

    NOTE: This is one of the ways to implement NIO reactor, and it does not take care of all * possible edge cases which are required in a real application. This implementation is meant to @@ -55,6 +54,7 @@ public class NioReactor { private final Selector selector; private final Dispatcher dispatcher; + /** * All the work of altering the SelectionKey operations and Selector operations are performed in * the context of main event loop of reactor. So when any channel needs to change its readability @@ -62,6 +62,7 @@ public class NioReactor { * the command and executes it in next iteration. */ private final Queue pendingCommands = new ConcurrentLinkedQueue<>(); + private final ExecutorService reactorMain = Executors.newSingleThreadExecutor(); /** @@ -76,25 +77,24 @@ public NioReactor(Dispatcher dispatcher) throws IOException { this.selector = Selector.open(); } - /** - * Starts the reactor event loop in a new thread. - */ + /** Starts the reactor event loop in a new thread. */ public void start() { - reactorMain.execute(() -> { - try { - LOGGER.info("Reactor started, waiting for events..."); - eventLoop(); - } catch (IOException e) { - LOGGER.error("exception in event loop", e); - } - }); + reactorMain.execute( + () -> { + try { + LOGGER.info("Reactor started, waiting for events..."); + eventLoop(); + } catch (IOException e) { + LOGGER.error("exception in event loop", e); + } + }); } /** * Stops the reactor and related resources such as dispatcher. * * @throws InterruptedException if interrupted while stopping the reactor. - * @throws IOException if any I/O error occurs. + * @throws IOException if any I/O error occurs. */ public void stop() throws InterruptedException, IOException { reactorMain.shutdown(); @@ -112,7 +112,7 @@ public void stop() throws InterruptedException, IOException { * AbstractNioChannel#getInterestedOps()} to know about the interested operation of this channel. * * @param channel a new channel on which reactor will wait for events. The channel must be bound - * prior to being registered. + * prior to being registered. * @return this * @throws IOException if any I/O error occurs. */ @@ -217,7 +217,7 @@ private void onChannelAcceptable(SelectionKey key) throws IOException { *

    This is a non-blocking method and does not guarantee that the operations have changed when * this method returns. * - * @param key the key for which operations have to be changed. + * @param key the key for which operations have to be changed. * @param interestedOps the new interest operations. */ public void changeOps(SelectionKey key, int interestedOps) { @@ -225,9 +225,7 @@ public void changeOps(SelectionKey key, int interestedOps) { selector.wakeup(); } - /** - * A command that changes the interested operations of the key provided. - */ + /** A command that changes the interested operations of the key provided. */ static class ChangeKeyOpsCommand implements Runnable { private final SelectionKey key; private final int interestedOps; diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java index bc512f1c9b25..3e56b3fe6e37 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java @@ -43,13 +43,13 @@ public class NioServerSocketChannel extends AbstractNioChannel { private final int port; /** - * Creates a {@link ServerSocketChannel} which will bind at provided port and use - * handler to handle incoming events on this channel. + * Creates a {@link ServerSocketChannel} which will bind at provided port and use handler + * to handle incoming events on this channel. * *

    Note the constructor does not bind the socket, {@link #bind()} method should be called for * binding the socket. * - * @param port the port on which channel will be bound to accept incoming connection requests. + * @param port the port on which channel will be bound to accept incoming connection requests. * @param handler the handler that will handle incoming requests on this channel. * @throws IOException if any I/O error occurs. */ @@ -58,7 +58,6 @@ public NioServerSocketChannel(int port, ChannelHandler handler) throws IOExcepti this.port = port; } - @Override public int getInterestedOps() { // being a server socket channel it is interested in accepting connection from remote peers. diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java b/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java index b075c71b6f2b..b06fe8a66e99 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java @@ -38,8 +38,9 @@ public class SameThreadDispatcher implements Dispatcher { /** - * Dispatches the read event in the context of caller thread.
    Note this is a blocking call. - * It returns only after the associated handler has handled the read event. + * Dispatches the read event in the context of caller thread.
    + * Note this is a blocking call. It returns only after the associated handler has handled the read + * event. */ @Override public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) { @@ -50,9 +51,7 @@ public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, Se channel.getHandler().handleChannelRead(channel, readObject, key); } - /** - * No resources to free. - */ + /** No resources to free. */ @Override public void stop() { // no-op diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java b/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java index c6c479d35c07..b202c9003c2d 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java @@ -49,8 +49,9 @@ public ThreadPoolDispatcher(int poolSize) { /** * Submits the work of dispatching the read event to worker pool, where it gets picked up by - * worker threads.
    Note that this is a non-blocking call and returns immediately. It is not - * guaranteed that the event has been handled by associated handler. + * worker threads.
    + * Note that this is a non-blocking call and returns immediately. It is not guaranteed that the + * event has been handled by associated handler. */ @Override public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) { diff --git a/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java b/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java index cb9fd970d553..79d4ffa59f28 100644 --- a/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java +++ b/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java @@ -42,7 +42,7 @@ class ReactorTest { /** * Test the application using pooled thread dispatcher. * - * @throws IOException if any I/O error occurs. + * @throws IOException if any I/O error occurs. * @throws InterruptedException if interrupted while stopping the application. */ @Test @@ -74,7 +74,7 @@ void testAppUsingThreadPoolDispatcher() throws IOException, InterruptedException /** * Test the application using same thread dispatcher. * - * @throws IOException if any I/O error occurs. + * @throws IOException if any I/O error occurs. * @throws InterruptedException if interrupted while stopping the application. */ @Test diff --git a/reader-writer-lock/README.md b/reader-writer-lock/README.md deleted file mode 100644 index b45bb149af8a..000000000000 --- a/reader-writer-lock/README.md +++ /dev/null @@ -1,249 +0,0 @@ ---- -title: Reader Writer Lock -category: Concurrency -language: en -tag: - - Performance ---- - -## Intent - -Regular lock does not distinguish the ‘read lock’ and ‘write lock’, as when access the data structure patterns -consists of many threads reading the data, each thread will have to lock it which produces unnecessary serialization. -The existence of reader-writer lock resolves this issue as it is well known as -“multiple concurrent readers, single writer locks”, used to consist of multiple threads reading the data concurrently -and allow only one thread to write or modify the data. All others (readers or writers) will be blocked while the writer -is modifying or writing the data and unblocked until the writer finishes writing. - -## Explanation - -Real world example - -> Consider if we obtain a database for bank accounts. If Alice wants to transfer from account1 to account2, at the -> same time Bob transfer money from account2 to account3. Alice will first read the totals of account1 and account2. Then, Bob's -> transaction executed completely. Alice is now working with outdated values, therefore, the total amount in account2 -> would be incorrect. With transactions, Bob would have to wait until Alice finishes her process of the accounts. - - - - -In plain words - -> Reader-writer lock enables either multiple readers or single writer to hold the lock at any given time. - - -Wikipedia says -> In computer science, a readers–writer (single-writer lock, a multi-reader lock, a push lock, or an MRSW lock) -> is a synchronization primitive that solves one of the readers–writers problems. - - -**Programmatic Example** - -In our programmatic example, we demonstrate the implementation of the access to either reader or writer. -We first create a `Reader` class which read when it acquired the read lock. It creates the reader and simulate the read operation. -```java -@Slf4j -public class Reader implements Runnable { - - private Lock readLock; - - private String name; - - private long readingTime; - - public Reader(String name, Lock readLock, long readingTime) { - this.name = name; - this.readLock = readLock; - this.readingTime = readingTime; - } - - public Reader(String name, Lock readLock) { - this(name, readLock, 250L); - } - - - @Override - public void run() { - readLock.lock(); - try { - read(); - } catch (InterruptedException e) { - LOGGER.info("InterruptedException when reading", e); - Thread.currentThread().interrupt(); - } finally { - readLock.unlock(); - } - } - - public void read() throws InterruptedException { - LOGGER.info("{} begin", name); - Thread.sleep(readingTime); - LOGGER.info("{} finish after reading {}ms", name, readingTime); - } -} - - -``` - - -In the `Writer` class, we operate write when it acquired the writer lock. It follows the similar process as the `Reader` class -which creates the writer and simulate the write operation. -```java - -public class Writer implements Runnable { - - private final Lock writeLock; - private final String name; - private final long writingTime; - - public Writer(String name, Lock writeLock) { - this(name, writeLock, 250L); - } - - public Writer(String name, Lock writeLock, long writingTime) { - this.name = name; - this.writeLock = writeLock; - this.writingTime = writingTime; - } - - @Override - public void run() { - writeLock.lock(); - try { - write(); - } catch (InterruptedException e) { - LOGGER.info("InterruptedException when writing", e); - Thread.currentThread().interrupt(); - } finally { - writeLock.unlock(); - } - } - - public void write() throws InterruptedException { - LOGGER.info("{} begin", name); - Thread.sleep(writingTime); - LOGGER.info("{} finished after writing {}ms", name, writingTime); - } -} - - - -``` - - -Now, in the `ReadWriteLock` class which would take the responsibilities to control the access for either the reader or the writer. -In the `ReadLock` class, it restricts that if there's no writer that gets the lock, then multiple readers could be access concurrently. -In the `WriteLock` class, it restricts that only one writer could be accessed. - -```java - -public class ReadWriteLock implements ReaderWriterLock { - - private final Object readerMutex = new Object(); - private int currentReaderCount; - - - private final Set globalMutex = new HashSet<>(); - private final ReadLock readerLock = new ReadLock(); - private final WriteLock writerLock = new WriteLock(); - - - public Lock readLock() { - return readerLock; - } - - public Lock writeLock() { - return writerLock; - } - - private boolean doesWriterOwnThisLock() { - return globalMutex.contains(writerLock); - } - - private boolean isLockFree() { - return globalMutex.isEmpty(); - } - - - private class ReadLock implements Lock { - - @Override - public void lock() { - synchronized (readerMutex) { - currentReaderCount++; - if (currentReaderCount == 1) { - acquireForReaders(); - } - } - } - - @Override - public void unlock() { - synchronized (readerMutex) { - currentReaderCount--; - - if (currentReaderCount == 0) { - synchronized (globalMutex) { - globalMutex.remove(this); - globalMutex.notifyAll(); - } - } - } - - } - } - - - private class WriteLock implements Lock { - - @Override - public void lock() { - synchronized (globalMutex) { - while (!isLockFree()) { - try { - globalMutex.wait(); - } catch (InterruptedException e) { - LOGGER.info("InterruptedException while waiting for globalMutex to begin writing", e); - Thread.currentThread().interrupt(); - } - } - globalMutex.add(this); - } - } - - @Override - public void unlock() { - synchronized (globalMutex) { - globalMutex.remove(this); - globalMutex.notifyAll(); - } - } - - } -} - - - - - -``` - - -## Class diagram -![alt text](./etc/reader-writer-lock.png "Reader writer lock") - -## Applicability - -Use the Reader-writer lock when: -* You need to increase the performance of resource synchronize for multiple thread, in particularly there are mixed read/write operations. -* In the bank transaction system, you want to ensure when two users are transacting the same account, one people will wait until the other finishes. - - - - - -## Credits - -* [Readers–writer lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock) -* [Readers–writers_problem](https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem) - diff --git a/reader-writer-lock/etc/reader-writer-lock.png b/reader-writer-lock/etc/reader-writer-lock.png deleted file mode 100644 index f7b600530926..000000000000 Binary files a/reader-writer-lock/etc/reader-writer-lock.png and /dev/null differ diff --git a/reader-writer-lock/etc/reader-writer-lock.ucls b/reader-writer-lock/etc/reader-writer-lock.ucls deleted file mode 100644 index 920904e76843..000000000000 --- a/reader-writer-lock/etc/reader-writer-lock.ucls +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/reader-writer-lock/etc/reader-writer-lock.urm.puml b/reader-writer-lock/etc/reader-writer-lock.urm.puml deleted file mode 100644 index f0e33ab3cb40..000000000000 --- a/reader-writer-lock/etc/reader-writer-lock.urm.puml +++ /dev/null @@ -1,65 +0,0 @@ -@startuml -package com.iluwatar.reader.writer.lock { - class App { - - LOGGER : Logger {static} - + App() - + main(args : String[]) {static} - } - class Reader { - - LOGGER : Logger {static} - - name : String - - readLock : Lock - - readingTime : long - + Reader(name : String, readLock : Lock) - + Reader(name : String, readLock : Lock, readingTime : long) - + read() - + run() - } - class ReaderWriterLock { - - LOGGER : Logger {static} - - currentReaderCount : int - - globalMutex : Set - - readerLock : ReadLock - - readerMutex : Object - - writerLock : WriteLock - + ReaderWriterLock() - - doesWriterOwnThisLock() : boolean - - isLockFree() : boolean - + readLock() : Lock - + writeLock() : Lock - } - -class ReadLock { - - ReadLock() - - acquireForReaders() - + lock() - + lockInterruptibly() - + newCondition() : Condition - + tryLock() : boolean - + tryLock(time : long, unit : TimeUnit) : boolean - + unlock() - } - -class WriteLock { - - WriteLock() - + lock() - + lockInterruptibly() - + newCondition() : Condition - + tryLock() : boolean - + tryLock(time : long, unit : TimeUnit) : boolean - + unlock() - } - class Writer { - - LOGGER : Logger {static} - - name : String - - writeLock : Lock - - writingTime : long - + Writer(name : String, writeLock : Lock) - + Writer(name : String, writeLock : Lock, writingTime : long) - + run() - + write() - } -} -ReaderWriterLock --> "-readerLock" ReadLock -ReadLock --+ ReaderWriterLock -WriteLock --+ ReaderWriterLock -ReaderWriterLock --> "-writerLock" WriteLock -@enduml \ No newline at end of file diff --git a/reader-writer-lock/pom.xml b/reader-writer-lock/pom.xml deleted file mode 100644 index 5f0b7b3ac100..000000000000 --- a/reader-writer-lock/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - reader-writer-lock - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.reader.writer.lock.App - - - - - - - - - diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java deleted file mode 100644 index 48ce50463af0..000000000000 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.reader.writer.lock; - -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import lombok.extern.slf4j.Slf4j; - -/** - * In a multiple thread applications, the threads may try to synchronize the shared resources - * regardless of read or write operation. It leads to a low performance especially in a "read more - * write less" system as indeed the read operations are thread-safe to another read operation. - * - *

    Reader writer lock is a synchronization primitive that try to resolve this problem. This - * pattern allows concurrent access for read-only operations, while write operations require - * exclusive access. This means that multiple threads can read the data in parallel but an exclusive - * lock is needed for writing or modifying data. When a writer is writing the data, all other - * writers or readers will be blocked until the writer is finished writing. - * - *

    This example use two mutex to demonstrate the concurrent access of multiple readers and - * writers. - * - * @author hongshuwei@gmail.com - */ -@Slf4j -public class App { - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - - var executeService = Executors.newFixedThreadPool(10); - var lock = new ReaderWriterLock(); - - // Start writers - for (var i = 0; i < 5; i++) { - var writingTime = ThreadLocalRandom.current().nextLong(5000); - executeService.submit(new Writer("Writer " + i, lock.writeLock(), writingTime)); - } - LOGGER.info("Writers added..."); - - // Start readers - for (var i = 0; i < 5; i++) { - var readingTime = ThreadLocalRandom.current().nextLong(10); - executeService.submit(new Reader("Reader " + i, lock.readLock(), readingTime)); - } - LOGGER.info("Readers added..."); - - try { - Thread.sleep(5000L); - } catch (InterruptedException e) { - LOGGER.error("Error sleeping before adding more readers", e); - Thread.currentThread().interrupt(); - } - - // Start readers - for (var i = 6; i < 10; i++) { - var readingTime = ThreadLocalRandom.current().nextLong(10); - executeService.submit(new Reader("Reader " + i, lock.readLock(), readingTime)); - } - LOGGER.info("More readers added..."); - - - // In the system console, it can see that the read operations are executed concurrently while - // write operations are exclusive. - executeService.shutdown(); - try { - executeService.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOGGER.error("Error waiting for ExecutorService shutdown", e); - Thread.currentThread().interrupt(); - } - - } - -} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java deleted file mode 100644 index 4c09b1e690ee..000000000000 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.reader.writer.lock; - -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import lombok.extern.slf4j.Slf4j; - -/** - * Class responsible for control the access for reader or writer - * - *

    Allows multiple readers to hold the lock at same time, but if any writer holds the lock then - * readers wait. If reader holds the lock then writer waits. This lock is not fair. - */ -@Slf4j -public class ReaderWriterLock implements ReadWriteLock { - - - private final Object readerMutex = new Object(); - - private int currentReaderCount; - - /** - * Global mutex is used to indicate that whether reader or writer gets the lock in the moment. - * - *

    1. When it contains the reference of {@link #readerLock}, it means that the lock is - * acquired by the reader, another reader can also do the read operation concurrently.
    2. - * When it contains the reference of reference of {@link #writerLock}, it means that the lock is - * acquired by the writer exclusively, no more reader or writer can get the lock. - * - *

    This is the most important field in this class to control the access for reader/writer. - */ - private final Set globalMutex = new HashSet<>(); - - private final ReadLock readerLock = new ReadLock(); - private final WriteLock writerLock = new WriteLock(); - - @Override - public Lock readLock() { - return readerLock; - } - - @Override - public Lock writeLock() { - return writerLock; - } - - /** - * return true when globalMutex hold the reference of writerLock. - */ - private boolean doesWriterOwnThisLock() { - return globalMutex.contains(writerLock); - } - - /** - * Nobody get the lock when globalMutex contains nothing. - */ - private boolean isLockFree() { - return globalMutex.isEmpty(); - } - - /** - * Reader Lock, can be access for more than one reader concurrently if no writer get the lock. - */ - private class ReadLock implements Lock { - - @Override - public void lock() { - synchronized (readerMutex) { - currentReaderCount++; - if (currentReaderCount == 1) { - acquireForReaders(); - } - } - } - - /** - * Acquire the globalMutex lock on behalf of current and future concurrent readers. Make sure no - * writers currently owns the lock. - */ - private void acquireForReaders() { - // Try to get the globalMutex lock for the first reader - synchronized (globalMutex) { - // If the no one get the lock or the lock is locked by reader, just set the reference - // to the globalMutex to indicate that the lock is locked by Reader. - while (doesWriterOwnThisLock()) { - try { - globalMutex.wait(); - } catch (InterruptedException e) { - var message = "InterruptedException while waiting for globalMutex in acquireForReaders"; - LOGGER.info(message, e); - Thread.currentThread().interrupt(); - } - } - globalMutex.add(this); - } - } - - @Override - public void unlock() { - synchronized (readerMutex) { - currentReaderCount--; - // Release the lock only when it is the last reader, it is ensure that the lock is released - // when all reader is completely. - if (currentReaderCount == 0) { - synchronized (globalMutex) { - // Notify the waiter, mostly the writer - globalMutex.remove(this); - globalMutex.notifyAll(); - } - } - } - - } - - @Override - public void lockInterruptibly() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean tryLock() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean tryLock(long time, TimeUnit unit) { - throw new UnsupportedOperationException(); - } - - @Override - public Condition newCondition() { - throw new UnsupportedOperationException(); - } - - } - - /** - * Writer Lock, can only be accessed by one writer concurrently. - */ - private class WriteLock implements Lock { - - @Override - public void lock() { - synchronized (globalMutex) { - - // Wait until the lock is free. - while (!isLockFree()) { - try { - globalMutex.wait(); - } catch (InterruptedException e) { - LOGGER.info("InterruptedException while waiting for globalMutex to begin writing", e); - Thread.currentThread().interrupt(); - } - } - // When the lock is free, acquire it by placing an entry in globalMutex - globalMutex.add(this); - } - } - - @Override - public void unlock() { - synchronized (globalMutex) { - globalMutex.remove(this); - // Notify the waiter, other writer or reader - globalMutex.notifyAll(); - } - } - - @Override - public void lockInterruptibly() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean tryLock() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean tryLock(long time, TimeUnit unit) { - throw new UnsupportedOperationException(); - } - - @Override - public Condition newCondition() { - throw new UnsupportedOperationException(); - } - } - -} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java deleted file mode 100644 index bc171614680a..000000000000 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.reader.writer.lock; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.iluwatar.reader.writer.lock.utils.InMemoryAppender; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author hongshuwei@gmail.com - */ -class ReaderAndWriterTest { - - private InMemoryAppender appender; - - @BeforeEach - void setUp() { - appender = new InMemoryAppender(); - } - - @AfterEach - void tearDown() { - appender.stop(); - } - - private static final Logger LOGGER = LoggerFactory.getLogger(ReaderAndWriterTest.class); - - /** - * Verify reader and writer can only get the lock to read and write orderly - */ - @Test - void testReadAndWrite() throws Exception { - - var lock = new ReaderWriterLock(); - - var reader1 = new Reader("Reader 1", lock.readLock()); - var writer1 = new Writer("Writer 1", lock.writeLock()); - - var executeService = Executors.newFixedThreadPool(2); - executeService.submit(reader1); - // Let reader1 execute first - Thread.sleep(150); - executeService.submit(writer1); - - executeService.shutdown(); - try { - executeService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOGGER.error("Error waiting for ExecutorService shutdown", e); - } - - assertTrue(appender.logContains("Reader 1 begin")); - assertTrue(appender.logContains("Reader 1 finish")); - assertTrue(appender.logContains("Writer 1 begin")); - assertTrue(appender.logContains("Writer 1 finish")); - } - - /** - * Verify reader and writer can only get the lock to read and write orderly - */ - @Test - void testWriteAndRead() throws Exception { - - var executeService = Executors.newFixedThreadPool(2); - var lock = new ReaderWriterLock(); - - var reader1 = new Reader("Reader 1", lock.readLock()); - var writer1 = new Writer("Writer 1", lock.writeLock()); - - executeService.submit(writer1); - // Let writer1 execute first - Thread.sleep(150); - executeService.submit(reader1); - - executeService.shutdown(); - try { - executeService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOGGER.error("Error waiting for ExecutorService shutdown", e); - } - - assertTrue(appender.logContains("Writer 1 begin")); - assertTrue(appender.logContains("Writer 1 finish")); - assertTrue(appender.logContains("Reader 1 begin")); - assertTrue(appender.logContains("Reader 1 finish")); - } -} - diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java deleted file mode 100644 index 2fb31f36c279..000000000000 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.reader.writer.lock; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.spy; - -import com.iluwatar.reader.writer.lock.utils.InMemoryAppender; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author hongshuwei@gmail.com - */ -class ReaderTest { - - private InMemoryAppender appender; - - @BeforeEach - void setUp() { - appender = new InMemoryAppender(Reader.class); - } - - @AfterEach - void tearDown() { - appender.stop(); - } - - private static final Logger LOGGER = LoggerFactory.getLogger(ReaderTest.class); - - /** - * Verify that multiple readers can get the read lock concurrently - */ - @Test - void testRead() throws Exception { - - var executeService = Executors.newFixedThreadPool(2); - var lock = new ReaderWriterLock(); - - var reader1 = spy(new Reader("Reader 1", lock.readLock())); - var reader2 = spy(new Reader("Reader 2", lock.readLock())); - - executeService.submit(reader1); - Thread.sleep(150); - executeService.submit(reader2); - - executeService.shutdown(); - try { - executeService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOGGER.error("Error waiting for ExecutorService shutdown", e); - } - - // Read operation will hold the read lock 250 milliseconds, so here we prove that multiple reads - // can be performed in the same time. - assertTrue(appender.logContains("Reader 1 begin")); - assertTrue(appender.logContains("Reader 2 begin")); - assertTrue(appender.logContains("Reader 1 finish")); - assertTrue(appender.logContains("Reader 2 finish")); - } -} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java deleted file mode 100644 index 9c8422c0fd2a..000000000000 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.reader.writer.lock; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.spy; - -import com.iluwatar.reader.writer.lock.utils.InMemoryAppender; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author hongshuwei@gmail.com - */ -class WriterTest { - - private InMemoryAppender appender; - - @BeforeEach - void setUp() { - appender = new InMemoryAppender(Writer.class); - } - - @AfterEach - void tearDown() { - appender.stop(); - } - - private static final Logger LOGGER = LoggerFactory.getLogger(WriterTest.class); - - /** - * Verify that multiple writers will get the lock in order. - */ - @Test - void testWrite() throws Exception { - - var executeService = Executors.newFixedThreadPool(2); - var lock = new ReaderWriterLock(); - - var writer1 = spy(new Writer("Writer 1", lock.writeLock())); - var writer2 = spy(new Writer("Writer 2", lock.writeLock())); - - executeService.submit(writer1); - // Let write1 execute first - Thread.sleep(150); - executeService.submit(writer2); - - executeService.shutdown(); - try { - executeService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOGGER.error("Error waiting for ExecutorService shutdown", e); - } - // Write operation will hold the write lock 250 milliseconds, so here we verify that when two - // writer execute concurrently, the second writer can only writes only when the first one is - // finished. - assertTrue(appender.logContains("Writer 1 begin")); - assertTrue(appender.logContains("Writer 1 finish")); - assertTrue(appender.logContains("Writer 2 begin")); - assertTrue(appender.logContains("Writer 2 finish")); - } -} diff --git a/registry/README.md b/registry/README.md index 7f4e447a88ab..aa40bb80941d 100644 --- a/registry/README.md +++ b/registry/README.md @@ -1,84 +1,158 @@ --- -title: Registry +title: "Registry Pattern in Java: Implementing a Centralized Control Mechanism for Java Objects" +shortTitle: Registry +description: "Explore the Registry design pattern in Java: Learn how it centralizes object management for improved access and control. Perfect for software developers and architects looking to optimize Java application architecture." category: Creational language: en tag: - - Instantiation + - API design + - Data access + - Decoupling + - Dependency management + - Enterprise patterns + - Instantiation --- -## Intent -Stores the objects of a single class and provide a global point of access to them. -Similar to Multiton pattern, only difference is that in a registry there is no restriction on the number of objects. +## Intent of Registry Design Pattern -## Explanation +Registry Design Pattern centralizes the creation and management of a global set of objects, providing a single point of access and ensuring controlled instantiation. -In Plain Words +## Detailed Explanation of Registry Pattern with Real-World Examples -> Registry is a well-known object that other objects can use to find common objects and services. +Real-world example -**Programmatic Example** -Below is a `Customer` Class +> In a large corporation, managing IT assets such as laptops, desktops, servers, and software licenses can be challenging. To streamline this process, the company uses a centralized IT Asset Management System, which functions as a Registry. +> +> * The system provides a single point of access for registering, tracking, and retrieving information about all IT assets. +> * When a new device or software is procured, it is registered in the system with details such as serial number, purchase date, warranty period, and assigned user. +> * IT staff can query the system to get details about any asset, check its current status, and update information as needed. +> * This centralized management promotes efficient utilization and maintenance of assets, ensures compliance with software licenses, and helps in planning for upgrades or replacements. +> +> In this analogy, the IT Asset Management System acts as a Registry, managing the lifecycle and providing global access to information about IT assets within the organization. -```java -public class Customer { +In plain words - private final String id; - private final String name; +> Registry is a well-known object that other objects can use to find common objects and services. - public Customer(String id, String name) { - this.id = id; - this.name = name; - } +wiki.c2.com says - public String getId() { - return id; - } +> A registry is a global association from keys to objects, allowing the objects to be reached from anywhere. It involves two methods: one that takes a key and an object and add objects to the registry and one that takes a key and returns the object for the key. It's similar to the MultitonPattern, but doesn't restrict instances to only those in the registry. - public String getName() { - return name; - } +## Programmatic Example of Registry Pattern in Java + +The Registry design pattern is a well-known pattern used in software design where objects are stored and provide a global point of access to them. This pattern is particularly useful when you need to manage a global collection of objects, decouple the creation of objects from their usage, ensure a controlled lifecycle for objects, and avoid redundant creation of objects. +First, we have the `Customer` record. It represents the objects that will be stored in the registry. Each `Customer` has an `id` and a `name`. + +```java +public record Customer(String id, String name) { + + @Override + public String toString() { + return "Customer{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } } ``` -This registry of the `Customer` objects is `CustomerRegistry` +Next, we have the `CustomerRegistry` class. This class is the actual registry where `Customer` objects are stored. It provides methods to add and retrieve customers. The `CustomerRegistry` is a singleton, meaning there is only one instance of it in the application. + ```java public final class CustomerRegistry { - private static final CustomerRegistry instance = new CustomerRegistry(); + @Getter + private static final CustomerRegistry instance = new CustomerRegistry(); - public static CustomerRegistry getInstance() { - return instance; - } + private final Map customerMap; - private final Map customerMap; + private CustomerRegistry() { + customerMap = new ConcurrentHashMap<>(); + } - private CustomerRegistry() { - customerMap = new ConcurrentHashMap<>(); - } + public Customer addCustomer(Customer customer) { + return customerMap.put(customer.id(), customer); + } - public Customer addCustomer(Customer customer) { - return customerMap.put(customer.getId(), customer); - } + public Customer getCustomer(String id) { + return customerMap.get(id); + } - public Customer getCustomer(String id) { - return customerMap.get(id); +} +``` + +Finally, we have the `App` class. This class demonstrates how to use the `CustomerRegistry`. It creates two `Customer` objects, adds them to the `CustomerRegistry`, and then retrieves them. + +```java +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) { + CustomerRegistry customerRegistry = CustomerRegistry.getInstance(); + var john = new Customer("1", "John"); + customerRegistry.addCustomer(john); + var julia = new Customer("2", "Julia"); + customerRegistry.addCustomer(julia); + + LOGGER.info("John {}", customerRegistry.getCustomer("1")); + LOGGER.info("Julia {}", customerRegistry.getCustomer("2")); } } ``` -## Class diagram -![Registry](./etc/registry.png) +In this example, the `CustomerRegistry` provides a global point of access to `Customer` objects. This allows us to manage these objects in a centralized way, promoting reuse and sharing, and facilitating decoupling between components. + +Running the example produces the following output: + +``` +09:55:31.109 [main] INFO com.iluwatar.registry.App -- John Customer{id='1', name='John'} +09:55:31.113 [main] INFO com.iluwatar.registry.App -- Julia Customer{id='2', name='Julia'} +``` + +## When to Use the Registry Pattern in Java + +* When you need to manage a global collection of objects. +* When you need to decouple the creation of objects from their usage. +* When you need to ensure a controlled lifecycle for objects, such as services or resources. +* When you want to avoid redundant creation of objects. + +## Real-World Applications of Registry Pattern in Java + +* Managing database connections in an enterprise application. +* Providing a central place to register and retrieve services or components in a modular application. +* Creating a central configuration registry that various parts of an application can access. + +## Benefits and Trade-offs of Registry Pattern + +Benefits: + +Key advantages of adopting the Registry pattern in Java + +* Centralizes object management, making the application easier to maintain. +* Promotes reuse and sharing of objects, which can reduce memory footprint and initialization time. +* Facilitates decoupling between components. + +Trade-offs: + +* Can become a bottleneck if not implemented efficiently. +* May introduce a single point of failure if the registry is not designed to be fault-tolerant. +* Increases complexity in managing the lifecycle of objects. -## Applicability -Use Registry pattern when +## Related Patterns -* client wants reference of some object, so client can lookup for that object in the object's registry. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used in conjunction with the Registry to ensure there is a single instance of the Registry. +* [Factory](https://java-design-patterns.com/patterns/factory/): Used to encapsulate the instantiation logic that might be needed when objects are retrieved from the Registry. +* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): A pattern that is similar in intent and structure, often used interchangeably with the Registry. +* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): Provides an alternative method for managing dependencies, which can sometimes replace the need for a Registry. +* [Multiton](https://java-design-patterns.com/patterns/multiton/): Similar to the Registry in that it manages multiple instances, but does so based on keys, ensuring only one instance per key. -## Consequences -Large number of bulky objects added to registry would result in a lot of memory consumption as objects in the registry are not garbage collected. +## References and Credits -## Credits -* https://www.martinfowler.com/eaaCatalog/registry.html -* https://wiki.c2.com/?RegistryPattern +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Registry (Martin Fowler)](https://www.martinfowler.com/eaaCatalog/registry.html) +* [Registry pattern (wiki.c2.com)](https://wiki.c2.com/?RegistryPattern) diff --git a/registry/pom.xml b/registry/pom.xml index ec6431452c2e..1063e53376da 100644 --- a/registry/pom.xml +++ b/registry/pom.xml @@ -34,6 +34,14 @@ registry + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/registry/src/main/java/com/iluwatar/registry/App.java b/registry/src/main/java/com/iluwatar/registry/App.java index 1a9c5234b81d..8e463a67cd43 100644 --- a/registry/src/main/java/com/iluwatar/registry/App.java +++ b/registry/src/main/java/com/iluwatar/registry/App.java @@ -28,11 +28,11 @@ import org.slf4j.LoggerFactory; /** - * In Registry pattern, objects of a single class are stored and provide a global point of access to them. - * Note that there is no restriction on the number of objects. - * - *

    The given example {@link CustomerRegistry} represents the registry used to store and - * access {@link Customer} objects.

    + * In Registry pattern, objects of a single class are stored and provide a global point of access to + * them. Note that there is no restriction on the number of objects. + * + *

    The given example {@link CustomerRegistry} represents the registry used to store and access + * {@link Customer} objects. */ public class App { @@ -54,5 +54,4 @@ public static void main(String[] args) { LOGGER.info("John {}", customerRegistry.getCustomer("1")); LOGGER.info("Julia {}", customerRegistry.getCustomer("2")); } - } diff --git a/registry/src/main/java/com/iluwatar/registry/Customer.java b/registry/src/main/java/com/iluwatar/registry/Customer.java index 75dd42a5f027..8998624ab326 100644 --- a/registry/src/main/java/com/iluwatar/registry/Customer.java +++ b/registry/src/main/java/com/iluwatar/registry/Customer.java @@ -24,16 +24,11 @@ */ package com.iluwatar.registry; -/** - * Customer entity used in registry pattern example. - */ +/** Customer entity used in registry pattern example. */ public record Customer(String id, String name) { @Override public String toString() { - return "Customer{" - + "id='" + id + '\'' - + ", name='" + name + '\'' - + '}'; + return "Customer{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } } diff --git a/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java b/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java index 14039ca8b5a7..4a3c8b2d690b 100644 --- a/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java +++ b/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java @@ -26,17 +26,12 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; -/** - * CustomerRegistry class used to store/access {@link Customer} objects. - */ +/** CustomerRegistry class used to store/access {@link Customer} objects. */ public final class CustomerRegistry { - private static final CustomerRegistry instance = new CustomerRegistry(); - - public static CustomerRegistry getInstance() { - return instance; - } + @Getter private static final CustomerRegistry instance = new CustomerRegistry(); private final Map customerMap; @@ -51,5 +46,4 @@ public Customer addCustomer(Customer customer) { public Customer getCustomer(String id) { return customerMap.get(id); } - } diff --git a/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java b/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java index af0697c458c9..48edaa6e26f4 100644 --- a/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java +++ b/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java @@ -24,13 +24,13 @@ */ package com.iluwatar.registry; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + class CustomerRegistryTest { private static CustomerRegistry customerRegistry; diff --git a/repository/README.md b/repository/README.md index a4cfaae22aaf..e2be527a2bcb 100644 --- a/repository/README.md +++ b/repository/README.md @@ -1,39 +1,37 @@ --- -title: Repository -category: Architectural +title: "Repository Pattern in Java: Simplifying Data Access with Abstracted Persistence" +shortTitle: Repository +description: "Learn how the Repository design pattern in Java enhances data access and abstraction, simplifying application architecture while boosting maintainability and decoupling. Ideal for Java developers seeking streamlined data management solutions." +category: Data access language: en tag: - - Data access + - Abstraction + - Data access + - Decoupling + - Persistence --- -## Intent +## Intent of Repository Design Pattern -Repository layer is added between the domain and data mapping layers to isolate domain objects from -details of the database access code and to minimize scattering and duplication of query code. The -Repository pattern is especially useful in systems where number of domain classes is large or heavy -querying is utilized. +The Repository design pattern acts as a central hub for managing all Java data access logic, abstracting the details of data storage and retrieval from the rest of the application. -## Explanation +## Detailed Explanation of Repository Pattern with Real-World Examples -Real world example +Real-world example -> Let's say we need a persistent data store for persons. Adding new persons and searching for them -> according to different criteria must be easy. +> Imagine a library system where a librarian acts as the repository. Instead of each library patron searching through the entire library for a book (the data), they go to the librarian (the repository) who knows exactly where each book is located, regardless of whether it's on a shelf, in the storeroom, or borrowed by someone else. The librarian abstracts the complexities of book storage, allowing patrons to request books without needing to understand the storage system. This setup simplifies the process for patrons (clients) and centralizes the management of books (data access logic). In plain words -> Repository architectural pattern creates a uniform layer of data repositories that can be used for -> CRUD operations. +> The Repository pattern provides a central place for handling all data access logic, abstracting the complexities of data storage and retrieval from the rest of the application. [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design) says -> Repositories are classes or components that encapsulate the logic required to access data sources. -> They centralize common data access functionality, providing better maintainability and decoupling -> the infrastructure or technology used to access databases from the domain model layer. +> Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. -**Programmatic Example** +## Programmatic Example of Repository Pattern in Java -Let's first look at the person entity that we need to persist. +Let's first look at the person entity that we need to persist. ```java @@ -45,151 +43,176 @@ Let's first look at the person entity that we need to persist. @NoArgsConstructor public class Person { - @Id - @GeneratedValue - private Long id; - private String name; - private String surname; - private int age; - - /** - * Constructor. - */ - public Person(String name, String surname, int age) { - this.name = name; - this.surname = surname; - this.age = age; - } - + @Id + @GeneratedValue + private Long id; + private String name; + private String surname; + private int age; + + public Person(String name, String surname, int age) { + this.name = name; + this.surname = surname; + this.age = age; + } } ``` -We are using Spring Data to create the `PersonRepository` so it becomes really simple. +We are using Spring Data to create the `PersonRepository`, so it becomes really simple. ```java @Repository -public interface PersonRepository - extends CrudRepository, JpaSpecificationExecutor { - - Person findByName(String name); +public interface PersonRepository extends CrudRepository, JpaSpecificationExecutor { + Person findByName(String name); } ``` -Additionally we define a helper class `PersonSpecifications` for specification queries. +Additionally, we define a helper class `PersonSpecifications` for specification queries. ```java public class PersonSpecifications { - public static class AgeBetweenSpec implements Specification { + public static class AgeBetweenSpec implements Specification { - private final int from; + private final int from; + private final int to; - private final int to; - - public AgeBetweenSpec(int from, int to) { - this.from = from; - this.to = to; - } + public AgeBetweenSpec(int from, int to) { + this.from = from; + this.to = to; + } - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { - return cb.between(root.get("age"), from, to); + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return cb.between(root.get("age"), from, to); + } } - } + public static class NameEqualSpec implements Specification { - public static class NameEqualSpec implements Specification { + public String name; - public String name; + public NameEqualSpec(String name) { + this.name = name; + } - public NameEqualSpec(String name) { - this.name = name; + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return cb.equal(root.get("name"), this.name); + } } - - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { - return cb.equal(root.get("name"), this.name); - } - } - } ``` And here's the repository example in action. ```java + public static void main(String[] args) { + + var context = new ClassPathXmlApplicationContext("applicationContext.xml"); + var repository = context.getBean(PersonRepository.class); + var peter = new Person("Peter", "Sagan", 17); var nasta = new Person("Nasta", "Kuzminova", 25); var john = new Person("John", "lawrence", 35); var terry = new Person("Terry", "Law", 36); + // Add new Person records repository.save(peter); repository.save(nasta); repository.save(john); repository.save(terry); + // Count Person records LOGGER.info("Count Person records: {}", repository.count()); + // Print all records var persons = (List) repository.findAll(); persons.stream().map(Person::toString).forEach(LOGGER::info); + // Update Person nasta.setName("Barbora"); nasta.setSurname("Spotakova"); repository.save(nasta); repository.findById(2L).ifPresent(p -> LOGGER.info("Find by id 2: {}", p)); + + // Remove record from Person repository.deleteById(2L); + // count records LOGGER.info("Count Person records: {}", repository.count()); + // find by name repository - .findOne(new PersonSpecifications.NameEqualSpec("John")) - .ifPresent(p -> LOGGER.info("Find by John is {}", p)); + .findOne(new PersonSpecifications.NameEqualSpec("John")) + .ifPresent(p -> LOGGER.info("Find by John is {}", p)); + // find by age persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); LOGGER.info("Find Person with age between 20,40: "); persons.stream().map(Person::toString).forEach(LOGGER::info); repository.deleteAll(); + + context.close(); +} ``` Program output: ``` -Count Person records: 4 -Person [id=1, name=Peter, surname=Sagan, age=17] -Person [id=2, name=Nasta, surname=Kuzminova, age=25] -Person [id=3, name=John, surname=lawrence, age=35] -Person [id=4, name=Terry, surname=Law, age=36] -Find by id 2: Person [id=2, name=Barbora, surname=Spotakova, age=25] -Count Person records: 3 -Find by John is Person [id=3, name=John, surname=lawrence, age=35] -Find Person with age between 20,40: -Person [id=3, name=John, surname=lawrence, age=35] -Person [id=4, name=Terry, surname=Law, age=36] +INFO [2024-05-27 07:00:32,847] com.iluwatar.repository.App: Count Person records: 4 +INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=1, name=Peter, surname=Sagan, age=17) +INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=2, name=Nasta, surname=Kuzminova, age=25) +INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=3, name=John, surname=lawrence, age=35) +INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=4, name=Terry, surname=Law, age=36) +INFO [2024-05-27 07:00:32,869] com.iluwatar.repository.App: Find by id 2: Person(id=2, name=Barbora, surname=Spotakova, age=25) +INFO [2024-05-27 07:00:32,873] com.iluwatar.repository.App: Count Person records: 3 +INFO [2024-05-27 07:00:32,878] com.iluwatar.repository.App: Find by John is Person(id=3, name=John, surname=lawrence, age=35) +INFO [2024-05-27 07:00:32,881] com.iluwatar.repository.App: Find Person with age between 20,40: +INFO [2024-05-27 07:00:32,881] com.iluwatar.repository.App: Person(id=3, name=John, surname=lawrence, age=35) +INFO [2024-05-27 07:00:32,881] com.iluwatar.repository.App: Person(id=4, name=Terry, surname=Law, age=36) ``` -## Class diagram +## When to Use the Repository Pattern in Java + +* Apply the Repository pattern when aiming to decouple business logic from data access layers in Java applications, ensuring more flexible and maintainable code. +* Suitable for scenarios where multiple data sources might be used and the business logic should remain unaware of the data source specifics. +* Ideal for testing purposes as it allows the use of mock repositories. + +## Repository Pattern Java Tutorials + +* [Don’t use DAO, use Repository (Thinking in Objects)](http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/) +* [Advanced Spring Data JPA - Specifications and Querydsl (Spring)](https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/) +* [Repository Pattern Benefits and Spring Implementation (Stack Overflow)](https://stackoverflow.com/questions/40068965/repository-pattern-benefits-and-spring-implementation) +* [Design patterns that I often avoid: Repository pattern (InfoWorld)](https://www.infoworld.com/article/3117713/design-patterns-that-i-often-avoid-repository-pattern.html) + +## Real-World Applications of Repository Pattern in Java + +* Spring Data JPA exemplifies the Repository pattern by providing a robust repository abstraction layer over JPA implementations, tailored for Java. +* Hibernate: Often used with DAOs that act as repositories for accessing and managing data entities. +* Java EE applications frequently utilize repository patterns to separate business logic from data access code. + +## Benefits and Trade-offs of Repository Pattern -![alt text](./etc/repository.png "Repository") +Benefits: -## Applicability +* Improves code maintainability and readability by centralizing data access logic. +* Enhances testability by allowing mock implementations of repositories. +* Promotes loose coupling between business logic and data access layers. -Use the Repository pattern when +Trade-offs: -* The number of domain objects is large. -* You want to avoid duplication of query code. -* You want to keep the database querying code in single place. -* You have multiple data sources. +* Introduces additional layers of abstraction which might add complexity. +* Potential performance overhead due to the abstraction layer. -## Real world examples +## Related Java Design Patterns -* [Spring Data](http://projects.spring.io/spring-data/) +* [Data Mapper](https://java-design-patterns.com/patterns/data-mapper/): While Repository handles data access, Data Mapper is responsible for transferring data between objects and a database, maintaining the data integrity. +* [Unit of Work](https://java-design-patterns.com/patterns/unit-of-work/): Often used alongside Repository to manage transactions and track changes to the data. -## Credits +## References and Credits -* [Don’t use DAO, use Repository](http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/) -* [Advanced Spring Data JPA - Specifications and Querydsl](https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/) -* [Repository Pattern Benefits and Spring Implementation](https://stackoverflow.com/questions/40068965/repository-pattern-benefits-and-spring-implementation) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) -* [Design patterns that I often avoid: Repository pattern](https://www.infoworld.com/article/3117713/design-patterns-that-i-often-avoid-repository-pattern.html) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/repository/pom.xml b/repository/pom.xml index 6bd1b9a6ab0c..108c64a68b95 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -33,26 +33,15 @@ 1.26.0-SNAPSHOT repository - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.3 - import - - - org.hibernate - hibernate-core - 6.4.4.Final - - - + + org.springframework.boot + spring-boot-starter + org.springframework.data spring-data-jpa + 3.4.4 org.hibernate @@ -74,14 +63,22 @@ jakarta.xml.bind jakarta.xml.bind-api + 4.0.2 jakarta.annotation jakarta.annotation-api + 2.1.1 org.springframework.boot spring-boot-starter-test + test + + + org.hibernate + hibernate-core + 6.4.4.Final diff --git a/repository/src/main/java/com/iluwatar/repository/App.java b/repository/src/main/java/com/iluwatar/repository/App.java index d7aba4dee8e0..53d4b04cc685 100644 --- a/repository/src/main/java/com/iluwatar/repository/App.java +++ b/repository/src/main/java/com/iluwatar/repository/App.java @@ -101,6 +101,5 @@ public static void main(String[] args) { repository.deleteAll(); context.close(); - } } diff --git a/repository/src/main/java/com/iluwatar/repository/AppConfig.java b/repository/src/main/java/com/iluwatar/repository/AppConfig.java index 995c790b6e35..121399fcdaf0 100644 --- a/repository/src/main/java/com/iluwatar/repository/AppConfig.java +++ b/repository/src/main/java/com/iluwatar/repository/AppConfig.java @@ -60,9 +60,7 @@ public DataSource dataSource() { return basicDataSource; } - /** - * Factory to create a especific instance of Entity Manager. - */ + /** Factory to create a specific instance of Entity Manager. */ @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { var entityManager = new LocalContainerEntityManagerFactoryBean(); @@ -73,9 +71,7 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory() { return entityManager; } - /** - * Properties for Jpa. - */ + /** Properties for Jpa. */ private static Properties jpaProperties() { var properties = new Properties(); properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); @@ -83,9 +79,7 @@ private static Properties jpaProperties() { return properties; } - /** - * Get transaction manager. - */ + /** Get transaction manager. */ @Bean public JpaTransactionManager transactionManager() { var transactionManager = new JpaTransactionManager(); @@ -145,7 +139,5 @@ public static void main(String[] args) { persons.stream().map(Person::toString).forEach(LOGGER::info); context.close(); - } - } diff --git a/repository/src/main/java/com/iluwatar/repository/Person.java b/repository/src/main/java/com/iluwatar/repository/Person.java index 923fd8f25fec..107b557a7e5f 100644 --- a/repository/src/main/java/com/iluwatar/repository/Person.java +++ b/repository/src/main/java/com/iluwatar/repository/Person.java @@ -33,9 +33,7 @@ import lombok.Setter; import lombok.ToString; -/** - * Person entity. - */ +/** Person entity. */ @ToString @EqualsAndHashCode @Setter @@ -44,20 +42,15 @@ @NoArgsConstructor public class Person { - @Id - @GeneratedValue - private Long id; + @Id @GeneratedValue private Long id; private String name; private String surname; private int age; - /** - * Constructor. - */ + /** Constructor. */ public Person(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; } - } diff --git a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java index a82b14630d18..f67e618b6cba 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java @@ -29,9 +29,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -/** - * Person repository. - */ +/** Person repository. */ @Repository public interface PersonRepository extends CrudRepository, JpaSpecificationExecutor { diff --git a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java index ae0d6e864c20..193409d87be4 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java @@ -30,14 +30,10 @@ import jakarta.persistence.criteria.Root; import org.springframework.data.jpa.domain.Specification; -/** - * Helper class, includes vary Specification as the abstraction of sql query criteria. - */ +/** Helper class, includes vary Specification as the abstraction of sql query criteria. */ public class PersonSpecifications { - /** - * Specifications stating the Between (From - To) Age Specification. - */ + /** Specifications stating the Between (From - To) Age Specification. */ public static class AgeBetweenSpec implements Specification { private final int from; @@ -53,12 +49,9 @@ public AgeBetweenSpec(int from, int to) { public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.between(root.get("age"), from, to); } - } - /** - * Name specification. - */ + /** Name specification. */ public static class NameEqualSpec implements Specification { public final String name; @@ -67,12 +60,9 @@ public NameEqualSpec(String name) { this.name = name; } - /** - * Get predicate. - */ + /** Get predicate. */ public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.equal(root.get("name"), this.name); } } - } diff --git a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java index 1843a42cc1fa..66d5d9916b3f 100644 --- a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.List; import jakarta.annotation.Resource; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,8 +45,7 @@ @SpringBootTest(classes = {AppConfig.class}) class AnnotationBasedRepositoryTest { - @Resource - private PersonRepository repository; + @Resource private PersonRepository repository; private final Person peter = new Person("Peter", "Sagan", 17); private final Person nasta = new Person("Nasta", "Kuzminova", 25); @@ -55,9 +54,7 @@ class AnnotationBasedRepositoryTest { private final List persons = List.of(peter, nasta, john, terry); - /** - * Prepare data for test - */ + /** Prepare data for test */ @BeforeEach void setup() { repository.saveAll(persons); @@ -114,5 +111,4 @@ void testFindOneByNameEqualSpec() { void cleanup() { repository.deleteAll(); } - } diff --git a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java index f0213411f3db..27b18d9e0ef6 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java @@ -36,37 +36,32 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; -/** - * This case is Just for test the Annotation Based configuration - */ +/** This case is Just for test the Annotation Based configuration */ @ExtendWith(SpringExtension.class) @SpringBootTest(classes = {AppConfig.class}) class AppConfigTest { - @Autowired - DataSource dataSource; + @Autowired DataSource dataSource; - /** - * Test for bean instance - */ + /** Test for bean instance */ @Test void testDataSource() { assertNotNull(dataSource); } - /** - * Test for correct query execution - */ + /** Test for correct query execution */ @Test @Transactional void testQuery() throws SQLException { - var resultSet = dataSource.getConnection().createStatement().executeQuery("SELECT 1"); - var expected = "1"; - String result = null; - while (resultSet.next()) { - result = resultSet.getString(1); + String expected; + String result; + try (var resultSet = dataSource.getConnection().createStatement().executeQuery("SELECT 1")) { + expected = "1"; + result = null; + while (resultSet.next()) { + result = resultSet.getString(1); + } } assertEquals(expected, result); } - } diff --git a/repository/src/test/java/com/iluwatar/repository/AppTest.java b/repository/src/test/java/com/iluwatar/repository/AppTest.java index e9687dd3866d..600085930762 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.repository; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Repository example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Repository example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java index 745fc9bc599b..226e3745c76d 100644 --- a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.List; import jakarta.annotation.Resource; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,8 +45,7 @@ @SpringBootTest(properties = {"locations=classpath:applicationContext.xml"}) class RepositoryTest { - @Resource - private PersonRepository repository; + @Resource private PersonRepository repository; private final Person peter = new Person("Peter", "Sagan", 17); private final Person nasta = new Person("Nasta", "Kuzminova", 25); @@ -55,9 +54,7 @@ class RepositoryTest { private final List persons = List.of(peter, nasta, john, terry); - /** - * Prepare data for test - */ + /** Prepare data for test */ @BeforeEach void setup() { repository.saveAll(persons); @@ -114,5 +111,4 @@ void testFindOneByNameEqualSpec() { void cleanup() { repository.deleteAll(); } - } diff --git a/resource-acquisition-is-initialization/README.md b/resource-acquisition-is-initialization/README.md index 5c84dfe3403c..5c6105dce96c 100644 --- a/resource-acquisition-is-initialization/README.md +++ b/resource-acquisition-is-initialization/README.md @@ -1,18 +1,144 @@ --- -title: Resource Acquisition Is Initialization -category: Idiom +title: "Resource Acquisition Is Initialization in Java: Ensuring Safe Resource Management" +shortTitle: Resource Acquisition Is Initialization (RAII) +description: "Discover how the RAII (Resource Acquisition Is Initialization) pattern can streamline resource management in Java. Learn to implement RAII with practical examples and improve code reliability and maintenance." +category: Resource management language: en tag: - - Data access + - Encapsulation + - Memory management + - Resource management --- -## Intent -Resource Acquisition Is Initialization pattern can be used to implement exception safe resource management. +## Also known as -## Class diagram -![alt text](./etc/resource-acquisition-is-initialization.png "Resource Acquisition Is Initialization") +* RAII +* Scope-based Resource Management -## Applicability -Use the Resource Acquisition Is Initialization pattern when +## Intent of Resource Acquisition Is Initialization Design Pattern -* You have resources that must be closed in every condition +Ensure efficient Java resource management by tying the resource lifecycle to object lifetime, utilizing the RAII pattern. + +## Detailed Explanation of Resource Acquisition Is Initialization Pattern with Real-World Examples + +Real-world example + +> In a car rental service, each car represents a resource. Using the RAII pattern, when a customer rents a car (acquires the resource), the car is marked as rented. When the customer returns the car (the object goes out of scope), the car is automatically made available for the next customer. This ensures that cars are properly managed and available without manual intervention for checking availability or returns. + +In plain words + +> The RAII pattern in Java allows for exception-safe resource management, ensuring robust handling of critical resources. + +Wikipedia says + +> Resource acquisition is initialization (RAII) is a programming idiom used in several object-oriented, statically typed programming languages to describe a particular language behavior. Resource allocation (or acquisition) is done during object creation (specifically initialization), by the constructor, while resource deallocation (release) is done during object destruction (specifically finalization), by the destructor. + +## Programmatic Example of RAII Pattern in Java + +The RAII pattern is a common idiom used in software design where the acquisition of a resource is done during object creation (initialization), and the release of the resource is done during object destruction. This pattern is particularly useful in dealing with resource leaks and is critical in writing exception-safe code in C++. In Java, RAII is achieved with try-with-resources statement and interfaces `java.io.Closeable` and `AutoCloseable`. + +```java +// This is an example of a resource class that implements the AutoCloseable interface. +// The resource is acquired in the constructor and released in the close method. + +@Slf4j +public class SlidingDoor implements AutoCloseable { + + public SlidingDoor() { + LOGGER.info("Sliding door opens."); // Resource acquisition is done here + } + + @Override + public void close() { + LOGGER.info("Sliding door closes."); // Resource release is done here + } +} +``` + +In the above code, `SlidingDoor` is a resource that implements the `AutoCloseable` interface. The resource (in this case, a door) is "acquired" in the constructor (the door is opened), and "released" in the `close` method (the door is closed). + +```java +// This is another example of a resource class that implements the Closeable interface. +// The resource is acquired in the constructor and released in the close method. + +@Slf4j +public class TreasureChest implements Closeable { + + public TreasureChest() { + LOGGER.info("Treasure chest opens."); // Resource acquisition is done here + } + + @Override + public void close() { + LOGGER.info("Treasure chest closes."); // Resource release is done here + } +} +``` + +Similarly, `TreasureChest` is another resource that implements the `Closeable` interface. The resource (a treasure chest) is "acquired" in the constructor (the chest is opened), and "released" in the `close` method (the chest is closed). + +```java +// This is an example of how to use the RAII pattern in Java using the try-with-resources statement. + +@Slf4j +public class App { + + public static void main(String[] args) { + + try (var ignored = new SlidingDoor()) { + LOGGER.info("Walking in."); + } + + try (var ignored = new TreasureChest()) { + LOGGER.info("Looting contents."); + } + } +} +``` + +In the `main` method of the `App` class, we see the RAII pattern in action. The `try-with-resources` statement is used to ensure that each resource is closed at the end of the statement. This is where the `AutoCloseable` or `Closeable` interfaces come into play. When the `try` block is exited (either normally or via an exception), the `close` method of the resource is automatically called, thus ensuring the resource is properly released. + +The console output: + +``` +10:07:14.833 [main] INFO com.iluwatar.resource.acquisition.is.initialization.SlidingDoor -- Sliding door opens. +10:07:14.835 [main] INFO com.iluwatar.resource.acquisition.is.initialization.App -- Walking in. +10:07:14.835 [main] INFO com.iluwatar.resource.acquisition.is.initialization.SlidingDoor -- Sliding door closes. +10:07:14.835 [main] INFO com.iluwatar.resource.acquisition.is.initialization.TreasureChest -- Treasure chest opens. +10:07:14.835 [main] INFO com.iluwatar.resource.acquisition.is.initialization.App -- Looting contents. +10:07:14.835 [main] INFO com.iluwatar.resource.acquisition.is.initialization.TreasureChest -- Treasure chest closes. +``` + +## When to Use the Resource Acquisition Is Initialization Pattern in Java + +* Implement RAII in Java applications to manage essential resources such as file handles, network connections, and memory seamlessly. +* Suitable in environments where deterministic resource management is crucial, such as real-time systems or applications with strict resource constraints. + +## Real-World Applications of RAII Pattern in Java + +* Java `try-with-resources` statement: Ensures that resources are closed automatically at the end of the statement. +* Database connections: Using connection pools where the connection is obtained at the beginning of a scope and released at the end. +* File I/O: Automatically closing files using `try-with-resources`. + +## Benefits and Trade-offs of Resource Acquisition Is Initialization Pattern + +Benefits: + +* Automatic and deterministic resource management. +* Reduces the likelihood of resource leaks. +* Enhances code readability and maintainability by clearly defining the scope of resource usage. + +Trade-offs: + +* May introduce complexity in understanding object lifetimes. +* Requires careful design to ensure all resources are correctly encapsulated. + +## Related Java Design Patterns + +* [Object Pool](https://java-design-patterns.com/patterns/object-pool/): Manages a pool of reusable objects to optimize resource allocation and performance, often used for resources that are expensive to create and manage. + +## References and Credits + +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Resource acquisition is initialization](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization) diff --git a/resource-acquisition-is-initialization/pom.xml b/resource-acquisition-is-initialization/pom.xml index c8061093a0e9..465f7316e30a 100644 --- a/resource-acquisition-is-initialization/pom.xml +++ b/resource-acquisition-is-initialization/pom.xml @@ -34,6 +34,14 @@ resource-acquisition-is-initialization + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java index e1b9fe749534..d2bb59613452 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java @@ -48,10 +48,8 @@ @Slf4j public class App { - /** - * Program entry point. - */ - public static void main(String[] args) throws Exception { + /** Program entry point. */ + public static void main(String[] args) { try (var ignored = new SlidingDoor()) { LOGGER.info("Walking in."); diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java index f29bdefba554..8d4347df640c 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SlidingDoor resource. - */ +/** SlidingDoor resource. */ @Slf4j public class SlidingDoor implements AutoCloseable { diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java index 4085ee6515ad..5c71261d28f8 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java @@ -27,9 +27,7 @@ import java.io.Closeable; import lombok.extern.slf4j.Slf4j; -/** - * TreasureChest resource. - */ +/** TreasureChest resource. */ @Slf4j public class TreasureChest implements Closeable { diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java index 06d8af8235ba..dbd0979a053f 100644 --- a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.resource.acquisition.is.initialization; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java index 32b34e00d45c..0b4c42949b08 100644 --- a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java @@ -36,11 +36,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/28/15 - 9:31 PM - * - * @author Jeroen Meulemeester - */ +/** ClosableTest */ class ClosableTest { private InMemoryAppender appender; @@ -57,7 +53,8 @@ void tearDown() { @Test void testOpenClose() { - try (final var ignored = new SlidingDoor(); final var ignored1 = new TreasureChest()) { + try (final var ignored = new SlidingDoor(); + final var ignored1 = new TreasureChest()) { assertTrue(appender.logContains("Sliding door opens.")); assertTrue(appender.logContains("Treasure chest opens.")); } @@ -65,10 +62,8 @@ void testOpenClose() { assertTrue(appender.logContains("Sliding door closes.")); } - /** - * Logging Appender Implementation - */ - class InMemoryAppender extends AppenderBase { + /** Logging Appender Implementation */ + static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender() { @@ -85,5 +80,4 @@ public boolean logContains(String message) { return log.stream().anyMatch(event -> event.getMessage().equals(message)); } } - } diff --git a/retry/README.md b/retry/README.md index eb2d3f3dac7b..193866e436b5 100644 --- a/retry/README.md +++ b/retry/README.md @@ -1,47 +1,30 @@ --- -title: Retry -category: Behavioral +title: "Retry Pattern in Java: Building Fault-Tolerant Systems with Adaptive Retries" +shortTitle: Retry +description: "Explore the Retry pattern in Java for robust software design. Learn how to implement fault tolerance and improve application reliability through transparent retries of operations involving external communications like network requests." +category: Resilience language: en tag: + - Fault tolerance - Performance - - Cloud distributed + - Retry + - Resilience --- -## Intent +## Also known as -Transparently retry certain operations that involve communication with external resources, -particularly over the network, isolating calling code from the retry implementation details. +* Retry Logic +* Retry Mechanism -## Explanation +## Intent of Retry Design Pattern -Retry pattern consists retrying operations on remote resources over the network a set number of -times. It closely depends on both business and technical requirements: How much time will the -business allow the end user to wait while the operation finishes? What are the performance -characteristics of the remote resource during peak loads as well as our application as more threads -are waiting for the remote resource's availability? Among the errors returned by the remote service, -which can be safely ignored in order to retry? Is the operation -[idempotent](https://en.wikipedia.org/wiki/Idempotence)? +The Retry pattern in Java transparently retries certain operations that involve communication with external resources, particularly over the network, isolating calling code from the retry implementation details. It is crucial for developing resilient software systems that handle transient failures gracefully. -Another concern is the impact on the calling code by implementing the retry mechanism. The retry -mechanics should ideally be completely transparent to the calling code (service interface remains -unaltered). There are two general approaches to this problem: From an enterprise architecture -standpoint (strategic), and a shared library standpoint (tactical). +## Detailed Explanation of Retry Pattern with Real-World Examples -From a strategic point of view, this would be solved by having requests redirected to a separate -intermediary system, traditionally an [ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), -but more recently a [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a). +Real-world example -From a tactical point of view, this would be solved by reusing shared libraries like -[Hystrix](https://github.com/Netflix/Hystrix) (please note that Hystrix is a complete implementation -of the [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) pattern, of -which the Retry pattern can be considered a subset of). This is the type of solution showcased in -the simple example that accompanies this `README.md`. - -Real world example - -> Our application uses a service providing customer information. Once in a while the service seems -> to be flaky and can return errors or sometimes it just times out. To circumvent these problems we -> apply the retry pattern. +> Imagine you're a delivery driver attempting to deliver a package to a customer's house. You ring the doorbell, but no one answers. Instead of leaving immediately, you wait for a few minutes and try again, repeating this process a few times. This is similar to the Retry pattern in software, where a system retries a failed operation (e.g., making a network request) a certain number of times before finally giving up, in hopes that the issue (e.g., transient network glitch) will be resolved and the operation will succeed. In plain words @@ -49,14 +32,13 @@ In plain words [Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry) says -> Enable an application to handle transient failures when it tries to connect to a service or -> network resource, by transparently retrying a failed operation. This can improve the stability of -> the application. +> Enable an application to handle transient failures when it tries to connect to a service or network resource, by transparently retrying a failed operation. This can improve the stability of the application. + +## Programmatic Example of Retry Pattern in Java -**Programmatic Example** +The Retry design pattern is a resilience pattern that allows an application to transparently attempt to execute operations multiple times in the expectation that it'll succeed. This pattern is particularly useful when the application is connecting to a network service or a remote resource, where temporary failures are common. -In our hypothetical application, we have a generic interface for all operations on remote -interfaces. +First, we have a `BusinessOperation` interface that represents an operation that can be performed and might throw a `BusinessException`. ```java public interface BusinessOperation { @@ -64,92 +46,185 @@ public interface BusinessOperation { } ``` -And we have an implementation of this interface that finds our customers by looking up a database. +Next, we have a `FindCustomer` class that implements this interface. This class simulates a flaky service that intermittently fails by throwing `BusinessException`s before eventually returning a customer's ID. ```java public final class FindCustomer implements BusinessOperation { @Override public String perform() throws BusinessException { - ... + // ... } } ``` -Our `FindCustomer` implementation can be configured to throw `BusinessException`s before returning -the customer's ID, thereby simulating a flaky service that intermittently fails. Some exceptions, -like the `CustomerNotFoundException`, are deemed to be recoverable after some hypothetical analysis -because the root cause of the error stems from "some database locking issue". However, the -`DatabaseNotAvailableException` is considered to be a definite showstopper - the application should -not attempt to recover from this error. - -We can model a recoverable scenario by instantiating `FindCustomer` like this: +The `Retry` class is where the Retry pattern is implemented. It takes a `BusinessOperation` and a number of attempts, and it will keep trying to perform the operation until it either succeeds or the maximum number of attempts is reached. ```java -final var op = new FindCustomer( - "12345", - new CustomerNotFoundException("not found"), - new CustomerNotFoundException("still not found"), - new CustomerNotFoundException("don't give up yet!") -); +public final class Retry implements BusinessOperation { + + private final BusinessOperation op; + private final int maxAttempts; + private final long delay; + private final AtomicInteger attempts; + private final Predicate test; + private final List errors; + + @SafeVarargs + public Retry( + BusinessOperation op, + int maxAttempts, + long delay, + Predicate... ignoreTests + ) { + this.op = op; + this.maxAttempts = maxAttempts; + this.delay = delay; + this.attempts = new AtomicInteger(); + this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false); + this.errors = new ArrayList<>(); + } + + public List errors() { + return Collections.unmodifiableList(this.errors); + } + + public int attempts() { + return this.attempts.intValue(); + } + + @Override + public T perform() throws BusinessException { + do { + try { + return this.op.perform(); + } catch (BusinessException e) { + this.errors.add(e); + + if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { + throw e; + } + + try { + Thread.sleep(this.delay); + } catch (InterruptedException f) { + //ignore + } + } + } while (true); + } +} ``` -In this configuration, `FindCustomer` will throw `CustomerNotFoundException` three times, after -which it will consistently return the customer's ID (`12345`). +In this class, the `perform` method tries to perform the operation. If the operation throws an exception, it checks if the exception is recoverable and if the maximum number of attempts has not been reached. If both conditions are true, it waits for a specified delay and then tries again. If the exception is not recoverable or the maximum number of attempts has been reached, it rethrows the exception. -In our hypothetical scenario, our analysts indicate that this operation typically fails 2-4 times -for a given input during peak hours, and that each worker thread in the database subsystem typically -needs 50ms to "recover from an error". Applying these policies would yield something like this: +Finally, here is the `App` class driving the retry pattern example. ```java -final var op = new Retry<>( - new FindCustomer( - "1235", - new CustomerNotFoundException("not found"), - new CustomerNotFoundException("still not found"), - new CustomerNotFoundException("don't give up yet!") - ), - 5, - 100, - e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) -); +public final class App { + + private static final Logger LOG = LoggerFactory.getLogger(App.class); + public static final String NOT_FOUND = "not found"; + private static BusinessOperation op; + + public static void main(String[] args) throws Exception { + noErrors(); + errorNoRetry(); + errorWithRetry(); + errorWithRetryExponentialBackoff(); + } + + private static void noErrors() throws Exception { + op = new FindCustomer("123"); + op.perform(); + LOG.info("Sometimes the operation executes with no errors."); + } + + private static void errorNoRetry() throws Exception { + op = new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)); + try { + op.perform(); + } catch (CustomerNotFoundException e) { + LOG.info("Yet the operation will throw an error every once in a while."); + } + } + + private static void errorWithRetry() throws Exception { + final var retry = new Retry<>( + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), + 3, //3 attempts + 100, //100 ms delay between attempts + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) + ); + op = retry; + final var customerId = op.perform(); + LOG.info(String.format( + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", customerId, retry.attempts() + )); + } + + private static void errorWithRetryExponentialBackoff() throws Exception { + final var retry = new RetryExponentialBackoff<>( + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), + 6, //6 attempts + 30000, //30 s max delay between attempts + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) + ); + op = retry; + final var customerId = op.perform(); + LOG.info(String.format( + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", customerId, retry.attempts() + )); + } +} +``` + +Running the code produces the following console output. + +``` +10:12:19.573 [main] INFO com.iluwatar.retry.App -- Sometimes the operation executes with no errors. +10:12:19.575 [main] INFO com.iluwatar.retry.App -- Yet the operation will throw an error every once in a while. +10:12:19.682 [main] INFO com.iluwatar.retry.App -- However, retrying the operation while ignoring a recoverable error will eventually yield the result 123 after a number of attempts 1 +10:12:22.297 [main] INFO com.iluwatar.retry.App -- However, retrying the operation while ignoring a recoverable error will eventually yield the result 123 after a number of attempts 1 ``` -Executing `op` once would automatically trigger at most 5 retry attempts, with a 100 millisecond -delay between attempts, ignoring any `CustomerNotFoundException` thrown while trying. In this -particular scenario, due to the configuration for `FindCustomer`, there will be 1 initial attempt -and 3 additional retries before finally returning the desired result `12345`. +This way, the Retry pattern allows the application to handle temporary failures gracefully, improving its resilience and reliability. -If our `FindCustomer` operation were instead to throw a fatal `DatabaseNotFoundException`, which we -were instructed not to ignore, but more importantly we did not instruct our `Retry` to ignore, then -the operation would have failed immediately upon receiving the error, not matter how many attempts -were left. +## When to Use the Retry Pattern in Java -## Class diagram +Applying the Retry pattern is particularly effective -![alt text](./etc/retry.png "Retry") +* When operations can fail transiently, such as network calls, database connections, or external service integrations. +* In scenarios where the likelihood of transient failure is high but the cost of retries is low. -## Applicability +## Real-World Applications of Retry Pattern in Java -Whenever an application needs to communicate with an external resource, particularly in a cloud -environment, and if the business requirements allow it. +* In network communication libraries to handle transient failures. +* Database connection libraries to manage temporary outages or timeouts. +* APIs interacting with third-party services that may be temporarily unavailable. -## Consequences +## Benefits and Trade-offs of Retry Pattern -**Pros:** +Benefits: -* Resiliency -* Provides hard data on external failures +* Increases the robustness and fault tolerance of applications. +* Can significantly reduce the impact of transient failures. -**Cons:** +Trade-offs: -* Complexity -* Operations maintenance +* May introduce latency due to retries. +* Can lead to resource exhaustion if not managed properly. +* Requires careful configuration of retry parameters to avoid exacerbating the problem. -## Related Patterns +## Related Java Design Patterns -* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Used to stop the flow of requests to an external service after a failure threshold is reached, preventing system overload. -## Credits +## References and Credits -* [Retry pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry) -* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications](https://www.amazon.com/gp/product/1621140369/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1621140369&linkId=3e3f686af5e60a7a453b48adb286797b) +* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications](https://amzn.to/4dLvowg) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) +* [Release It!: Design and Deploy Production-Ready Software](https://amzn.to/3UPwmPh) +* [Retry pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry) diff --git a/retry/pom.xml b/retry/pom.xml index 331feb316b2e..ba2248eeb7a6 100644 --- a/retry/pom.xml +++ b/retry/pom.xml @@ -35,6 +35,14 @@ retry jar + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -43,6 +51,7 @@ org.hamcrest hamcrest-core + 3.0 test diff --git a/retry/src/main/java/com/iluwatar/retry/App.java b/retry/src/main/java/com/iluwatar/retry/App.java index 957b2b13566d..2fdcbdc922f4 100644 --- a/retry/src/main/java/com/iluwatar/retry/App.java +++ b/retry/src/main/java/com/iluwatar/retry/App.java @@ -37,24 +37,23 @@ * operations that our application performs involving remote systems. The calling code should remain * decoupled from implementations. * - *

    {@link FindCustomer} is a business operation that looks up a customer's record and returns - * its ID. Imagine its job is performed by looking up the customer in our local database and - * returning its ID. We can pass {@link CustomerNotFoundException} as one of its {@link + *

    {@link FindCustomer} is a business operation that looks up a customer's record and returns its + * ID. Imagine its job is performed by looking up the customer in our local database and returning + * its ID. We can pass {@link CustomerNotFoundException} as one of its {@link * FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) constructor * parameters} in order to simulate not finding the customer. * *

    Imagine that, lately, this operation has experienced intermittent failures due to some weird * corruption and/or locking in the data. After retrying a few times the customer is found. The - * database is still, however, expected to always be available. While a definitive solution is - * found to the problem, our engineers advise us to retry the operation a set number of times with a - * set delay between retries, although not too many retries otherwise the end user will be left - * waiting for a long time, while delays that are too short will not allow the database to recover - * from the load. + * database is still, however, expected to always be available. While a definitive solution is found + * to the problem, our engineers advise us to retry the operation a set number of times with a set + * delay between retries, although not too many retries otherwise the end user will be left waiting + * for a long time, while delays that are too short will not allow the database to recover from the + * load. * *

    To keep the calling code as decoupled as possible from this workaround, we have implemented * the retry mechanism as a {@link BusinessOperation} named {@link Retry}. * - * @author George Aristy (george.aristy@gmail.com) * @see Retry pattern * (Microsoft Azure Docs) */ @@ -92,32 +91,34 @@ private static void errorNoRetry() throws Exception { } private static void errorWithRetry() throws Exception { - final var retry = new Retry<>( - new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), - 3, //3 attempts - 100, //100 ms delay between attempts - e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) - ); + final var retry = + new Retry<>( + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), + 3, // 3 attempts + 100, // 100 ms delay between attempts + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())); op = retry; final var customerId = op.perform(); - LOG.info(String.format( - "However, retrying the operation while ignoring a recoverable error will eventually yield " - + "the result %s after a number of attempts %s", customerId, retry.attempts() - )); + LOG.info( + String.format( + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", + customerId, retry.attempts())); } private static void errorWithRetryExponentialBackoff() throws Exception { - final var retry = new RetryExponentialBackoff<>( - new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), - 6, //6 attempts - 30000, //30 s max delay between attempts - e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) - ); + final var retry = + new RetryExponentialBackoff<>( + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), + 6, // 6 attempts + 30000, // 30 s max delay between attempts + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())); op = retry; final var customerId = op.perform(); - LOG.info(String.format( - "However, retrying the operation while ignoring a recoverable error will eventually yield " - + "the result %s after a number of attempts %s", customerId, retry.attempts() - )); + LOG.info( + String.format( + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", + customerId, retry.attempts())); } } diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessException.java b/retry/src/main/java/com/iluwatar/retry/BusinessException.java index 117e603d2247..e4912c69aefb 100644 --- a/retry/src/main/java/com/iluwatar/retry/BusinessException.java +++ b/retry/src/main/java/com/iluwatar/retry/BusinessException.java @@ -31,12 +31,9 @@ * occurred. Its use is reserved as a "catch-all" for cases where no other subtype captures the * specificity of the error condition in question. Calling code is not expected to be able to handle * this error and should be reported to the maintainers immediately. - * - * @author George Aristy (george.aristy@gmail.com) */ public class BusinessException extends Exception { - @Serial - private static final long serialVersionUID = 6235833142062144336L; + @Serial private static final long serialVersionUID = 6235833142062144336L; /** * Ctor. diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java index c4f21f876adc..1e7edb34515b 100644 --- a/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java +++ b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java @@ -28,7 +28,6 @@ * Performs some business operation. * * @param the return type - * @author George Aristy (george.aristy@gmail.com) */ @FunctionalInterface public interface BusinessOperation { @@ -38,7 +37,7 @@ public interface BusinessOperation { * * @return the return value * @throws BusinessException if the operation fails. Implementations are allowed to throw more - * specific subtypes depending on the error conditions + * specific subtypes depending on the error conditions */ T perform() throws BusinessException; } diff --git a/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java index 64f7a6327ab3..222b251c3b9c 100644 --- a/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java +++ b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java @@ -24,17 +24,18 @@ */ package com.iluwatar.retry; +import java.io.Serial; + /** * Indicates that the customer was not found. * *

    The severity of this error is bounded by its context: was the search for the customer * triggered by an input from some end user, or were the search parameters pulled from your * database? - * - * @author George Aristy (george.aristy@gmail.com) */ public final class CustomerNotFoundException extends BusinessException { - private static final long serialVersionUID = -6972888602621778664L; + + @Serial private static final long serialVersionUID = -6972888602621778664L; /** * Ctor. diff --git a/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java index 5069bc66b63f..24a71a22df35 100644 --- a/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java +++ b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java @@ -26,14 +26,9 @@ import java.io.Serial; -/** - * Catastrophic error indicating that we have lost connection to our database. - * - * @author George Aristy (george.aristy@gmail.com) - */ +/** Catastrophic error indicating that we have lost connection to our database. */ public final class DatabaseNotAvailableException extends BusinessException { - @Serial - private static final long serialVersionUID = -3750769625095997799L; + @Serial private static final long serialVersionUID = -3750769625095997799L; /** * Ctor. diff --git a/retry/src/main/java/com/iluwatar/retry/FindCustomer.java b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java index 815cdcb3acd3..b827935dea7e 100644 --- a/retry/src/main/java/com/iluwatar/retry/FindCustomer.java +++ b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java @@ -34,14 +34,13 @@ *

    This is an imaginary operation that, for some imagined input, returns the ID for a customer. * However, this is a "flaky" operation that is supposed to fail intermittently, but for the * purposes of this example it fails in a programmed way depending on the constructor parameters. - * - * @author George Aristy (george.aristy@gmail.com) */ - -public record FindCustomer(String customerId, Deque errors) implements BusinessOperation { +public record FindCustomer(String customerId, Deque errors) + implements BusinessOperation { public FindCustomer(String customerId, BusinessException... errors) { this(customerId, new ArrayDeque<>(List.of(errors))); } + @Override public String perform() throws BusinessException { if (!this.errors.isEmpty()) { diff --git a/retry/src/main/java/com/iluwatar/retry/Retry.java b/retry/src/main/java/com/iluwatar/retry/Retry.java index d9bde0e9e306..fb0e6c6c8502 100644 --- a/retry/src/main/java/com/iluwatar/retry/Retry.java +++ b/retry/src/main/java/com/iluwatar/retry/Retry.java @@ -35,7 +35,6 @@ * Decorates {@link BusinessOperation business operation} with "retry" capabilities. * * @param the remote op's return type - * @author George Aristy (george.aristy@gmail.com) */ public final class Retry implements BusinessOperation { private final BusinessOperation op; @@ -48,19 +47,15 @@ public final class Retry implements BusinessOperation { /** * Ctor. * - * @param op the {@link BusinessOperation} to retry + * @param op the {@link BusinessOperation} to retry * @param maxAttempts number of times to retry - * @param delay delay (in milliseconds) between attempts + * @param delay delay (in milliseconds) between attempts * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions - * will be ignored if no tests are given + * will be ignored if no tests are given */ @SafeVarargs public Retry( - BusinessOperation op, - int maxAttempts, - long delay, - Predicate... ignoreTests - ) { + BusinessOperation op, int maxAttempts, long delay, Predicate... ignoreTests) { this.op = op; this.maxAttempts = maxAttempts; this.delay = delay; @@ -102,7 +97,7 @@ public T perform() throws BusinessException { try { Thread.sleep(this.delay); } catch (InterruptedException f) { - //ignore + // ignore } } } while (true); diff --git a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java index e5957fe94e57..024097b6b5c7 100644 --- a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java +++ b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java @@ -36,7 +36,6 @@ * Decorates {@link BusinessOperation business operation} with "retry" capabilities. * * @param the remote op's return type - * @author George Aristy (george.aristy@gmail.com) */ public final class RetryExponentialBackoff implements BusinessOperation { private static final Random RANDOM = new Random(); @@ -50,18 +49,17 @@ public final class RetryExponentialBackoff implements BusinessOperation { /** * Ctor. * - * @param op the {@link BusinessOperation} to retry + * @param op the {@link BusinessOperation} to retry * @param maxAttempts number of times to retry * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions - * will be ignored if no tests are given + * will be ignored if no tests are given */ @SafeVarargs public RetryExponentialBackoff( BusinessOperation op, int maxAttempts, long maxDelay, - Predicate... ignoreTests - ) { + Predicate... ignoreTests) { this.op = op; this.maxAttempts = maxAttempts; this.maxDelay = maxDelay; @@ -105,10 +103,9 @@ public T perform() throws BusinessException { var delay = Math.min(testDelay, this.maxDelay); Thread.sleep(delay); } catch (InterruptedException f) { - //ignore + // ignore } } } while (true); } } - diff --git a/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java index a04eb264b041..95ddba4bdd72 100644 --- a/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java +++ b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java @@ -30,23 +30,15 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link FindCustomer}. - * - * @author George Aristy (george.aristy@gmail.com) - */ +/** Unit tests for {@link FindCustomer}. */ class FindCustomerTest { - /** - * Returns the given result with no exceptions. - */ + /** Returns the given result with no exceptions. */ @Test void noExceptions() throws Exception { assertThat(new FindCustomer("123").perform(), is("123")); } - /** - * Throws the given exception. - */ + /** Throws the given exception. */ @Test void oneException() { var findCustomer = new FindCustomer("123", new BusinessException("test")); @@ -60,20 +52,20 @@ void oneException() { */ @Test void resultAfterExceptions() throws Exception { - final var op = new FindCustomer( - "123", - new CustomerNotFoundException("not found"), - new DatabaseNotAvailableException("not available") - ); + final var op = + new FindCustomer( + "123", + new CustomerNotFoundException("not found"), + new DatabaseNotAvailableException("not available")); try { op.perform(); } catch (CustomerNotFoundException e) { - //ignore + // ignore } try { op.perform(); } catch (DatabaseNotAvailableException e) { - //ignore + // ignore } assertThat(op.perform(), is("123")); diff --git a/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java b/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java index 5198699a46a3..ceec4f523b77 100644 --- a/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java +++ b/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java @@ -30,29 +30,23 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link Retry}. - * - * @author George Aristy (george.aristy@gmail.com) - */ +/** Unit tests for {@link Retry}. */ class RetryExponentialBackoffTest { - /** - * Should contain all errors thrown. - */ + /** Should contain all errors thrown. */ @Test void errors() { final var e = new BusinessException("unhandled"); - final var retry = new RetryExponentialBackoff( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new RetryExponentialBackoff( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.errors(), hasItem(e)); @@ -65,17 +59,17 @@ void errors() { @Test void attempts() { final var e = new BusinessException("unhandled"); - final var retry = new RetryExponentialBackoff( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new RetryExponentialBackoff( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(1)); @@ -88,18 +82,18 @@ void attempts() { @Test void ignore() { final var e = new CustomerNotFoundException("customer not found"); - final var retry = new RetryExponentialBackoff( - () -> { - throw e; - }, - 2, - 0, - ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()) - ); + final var retry = + new RetryExponentialBackoff( + () -> { + throw e; + }, + 2, + 0, + ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(2)); diff --git a/retry/src/test/java/com/iluwatar/retry/RetryTest.java b/retry/src/test/java/com/iluwatar/retry/RetryTest.java index 8de2048d0ddd..c7f753ccbf31 100644 --- a/retry/src/test/java/com/iluwatar/retry/RetryTest.java +++ b/retry/src/test/java/com/iluwatar/retry/RetryTest.java @@ -30,30 +30,24 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link Retry}. - * - * @author George Aristy (george.aristy@gmail.com) - */ +/** Unit tests for {@link Retry}. */ class RetryTest { - /** - * Should contain all errors thrown. - */ + /** Should contain all errors thrown. */ @Test void errors() { final var e = new BusinessException("unhandled"); - final var retry = new Retry( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new Retry( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.errors(), hasItem(e)); @@ -66,17 +60,17 @@ void errors() { @Test void attempts() { final var e = new BusinessException("unhandled"); - final var retry = new Retry( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new Retry( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(1)); @@ -89,21 +83,20 @@ void attempts() { @Test void ignore() { final var e = new CustomerNotFoundException("customer not found"); - final var retry = new Retry( - () -> { - throw e; - }, - 2, - 0, - ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()) - ); + final var retry = + new Retry( + () -> { + throw e; + }, + 2, + 0, + ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(2)); } - -} \ No newline at end of file +} diff --git a/role-object/README.md b/role-object/README.md index cb58815567f5..98b29bb284fb 100644 --- a/role-object/README.md +++ b/role-object/README.md @@ -1,32 +1,183 @@ --- -title: Role Object +title: "Role Object Pattern in Java: Enhancing Flexibility in Object Roles and Behaviors" +shortTitle: Role Object +description: "Explore the Role Object design pattern in Java, which allows objects to dynamically assume various roles, enhancing flexibility and system adaptability. Ideal for developers looking to implement dynamic behavior in applications." category: Structural language: en tag: - - Extensibility + - Abstraction + - Decoupling + - Extensibility + - Interface + - Object composition + - Polymorphism + - Runtime --- -## Also known as -Post pattern, Extension Object pattern +## Intent of Role Object Design Pattern -## Intent -Adapt an object to different client’s needs through transparently attached role objects, each one representing a role -the object has to play in that client’s context. The object manages its role set dynamically. By representing roles as -individual objects, different contexts are kept separate and system configuration is simplified. +Efficiently assign dynamic roles to Java objects, enabling them to adapt behaviors and responsibilities on-the-fly, optimizing runtime flexibility. -## Class diagram -![alt text](./etc/role-object.urm.png "Role Object pattern class diagram") +## Detailed Explanation of Role Object Pattern with Real-World Examples -## Applicability -Use the Role Object pattern, if: +Real-world example -- You want to handle a key abstraction in different contexts and you do not want to put the resulting context specific interfaces into the same class interface. -- You want to handle the available roles dynamically so that they can be attached and removed on demand, that is at runtime, rather than fixing them statically at compile-time. -- You want to treat the extensions transparently and need to preserve the logical object identity of the resultingobject conglomerate. -- You want to keep role/client pairs independent from each other so that changes to a role do not affect clients that are not interested in that role. +> Imagine a restaurant where staff members can take on different roles based on the needs of the moment. For example, an employee could be a server, a cashier, or a kitchen helper depending on the situation. When the restaurant is busy, a server might also take on the role of a cashier to help process payments quickly. Later, the same employee might assist in the kitchen during a rush. This flexibility allows the restaurant to dynamically allocate responsibilities to meet real-time demands, enhancing efficiency and customer satisfaction. The Role Object pattern in software mimics this by allowing objects to assume different roles and behaviors at runtime, providing similar flexibility and adaptability. -## Credits +In plain words -- [Hillside - Role object pattern](https://hillside.net/plop/plop97/Proceedings/riehle.pdf) -- [Role object](http://wiki.c2.com/?RoleObject) -- [Fowler - Dealing with roles](https://martinfowler.com/apsupp/roles.pdf) +> The Role Object pattern in Java models context-specific views through separate, dynamically managed role objects, enhancing modular design and runtime adaptability. + +wiki.c2.com says + +> Adapt an object to different client’s needs through transparently attached role objects, each one representing a role the object has to play in that client’s context. The object manages its role set dynamically. By representing roles as individual objects, different contexts are kept separate and system configuration is simplified. + +## Programmatic Example of Role Object Pattern in Java + +The Role Object design pattern is a pattern that suggests modeling context-specific views of an object as separate role objects. These role objects are dynamically attached to and removed from the core object. The resulting composite object structure, consisting of the core and its role objects, is called a subject. A subject often plays several roles and the same role is likely to be played by different subjects. + +In the provided code, we have a `Customer` object that can play different roles such as `Borrower` and `Investor`. These roles are represented by `BorrowerRole` and `InvestorRole` classes respectively, which extend the `CustomerRole` class. + +Here is the `Customer` class: + +```java +public abstract class Customer { + + public abstract boolean addRole(Role role); + + public abstract boolean hasRole(Role role); + + public abstract boolean remRole(Role role); + + public abstract Optional getRole(Role role, Class expectedRole); + + public static Customer newCustomer() { + return new CustomerCore(); + } + + public static Customer newCustomer(Role... role) { + var customer = newCustomer(); + Arrays.stream(role).forEach(customer::addRole); + return customer; + } +} +``` + +Here is the `BorrowerRole` class: + +```java +@Getter +@Setter +public class BorrowerRole extends CustomerRole { + + private String name; + + public String borrow() { + return String.format("Borrower %s wants to get some money.", name); + } +} +``` + +In this class, the `borrow` method represents an operation specific to the `Borrower` role. + +Similarly, the `InvestorRole` class represents the `Investor` role: + +```java +@Getter +@Setter +public class InvestorRole extends CustomerRole { + + private String name; + + private long amountToInvest; + + public String invest() { + return String.format("Investor %s has invested %d dollars", name, amountToInvest); + } +} +``` + +In the `InvestorRole` class, the `invest` method represents an operation specific to the `Investor` role. + +The `Customer` object can play either of these roles or both. This is demonstrated in the `main` function: + +```java +public static void main(String[] args) { + var customer = Customer.newCustomer(BORROWER, INVESTOR); + + LOGGER.info("New customer created : {}", customer); + + var hasBorrowerRole = customer.hasRole(BORROWER); + LOGGER.info("Customer has a borrower role - {}", hasBorrowerRole); + var hasInvestorRole = customer.hasRole(INVESTOR); + LOGGER.info("Customer has an investor role - {}", hasInvestorRole); + + customer.getRole(INVESTOR, InvestorRole.class) + .ifPresent(inv -> { + inv.setAmountToInvest(1000); + inv.setName("Billy"); + }); + customer.getRole(BORROWER, BorrowerRole.class) + .ifPresent(inv -> inv.setName("Johny")); + + customer.getRole(INVESTOR, InvestorRole.class) + .map(InvestorRole::invest) + .ifPresent(LOGGER::info); + + customer.getRole(BORROWER, BorrowerRole.class) + .map(BorrowerRole::borrow) + .ifPresent(LOGGER::info); +} +``` + +In this class, a `Customer` object is created with both `Borrower` and `Investor` roles. The `hasRole` method is used to check if the `Customer` object has a specific role. The `getRole` method is used to get a reference to the role object, which is then used to perform role-specific operations. + +Running the example outputs: + +``` +10:22:02.561 [main] INFO com.iluwatar.roleobject.ApplicationRoleObject -- New customer created : Customer{roles=[INVESTOR, BORROWER]} +10:22:02.564 [main] INFO com.iluwatar.roleobject.ApplicationRoleObject -- Customer has a borrower role - true +10:22:02.564 [main] INFO com.iluwatar.roleobject.ApplicationRoleObject -- Customer has an investor role - true +10:22:02.574 [main] INFO com.iluwatar.roleobject.ApplicationRoleObject -- Investor Billy has invested 1000 dollars +10:22:02.575 [main] INFO com.iluwatar.roleobject.ApplicationRoleObject -- Borrower Johny wants to get some money. +``` + +## When to Use the Role Object Pattern in Java + +* When an object needs to change its behavior dynamically based on its role. +* When multiple objects share common behaviors but should exhibit those behaviors differently based on their roles. +* In scenarios where roles can be added, removed, or changed at runtime. + +## Real-World Applications of Role Object Pattern in Java + +* User role management in applications where users can have different permissions and responsibilities. +* Game character roles where characters can take on different roles (e.g., healer, warrior, mage) dynamically. +* Workflow systems where tasks can be assigned different roles depending on the context. + +## Benefits and Trade-offs of Role Object Pattern + +Benefits: + +* Enhances software flexibility by enabling Java objects to dynamically switch roles, catering to evolving application needs. +* Enhances code maintainability by decoupling role-specific behaviors from core object logic. +* Facilitates the addition of new roles without modifying existing code. + +Trade-offs: + +* Increases complexity due to the need for managing multiple role objects. +* Potential performance overhead due to the dynamic nature of role assignment and behavior switching. + +## Related Java Design Patterns + +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Similar in dynamically changing an object's behavior, but Role Object focuses on roles that can be combined. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Both can add behaviors to objects, but Role Object allows for dynamic role switching rather than static enhancement. +* [State](https://java-design-patterns.com/patterns/state/): Manages state transitions similar to role changes, but Role Object deals more with behavioral roles rather than states. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3xZ1ELU) +* [Role-Based Access Control](https://amzn.to/3UJzL2l) +* [Dealing with Roles (Martin Fowler)](https://martinfowler.com/apsupp/roles.pdf) +* [Role Object (wiki.c2.com)](http://wiki.c2.com/?RoleObject) +* [The Role Object Pattern (Dirk Bäumer, Dirk Riehle, Wolf Siberski, and Martina Wulf)](https://hillside.net/plop/plop97/Proceedings/riehle.pdf) diff --git a/role-object/pom.xml b/role-object/pom.xml index 066c1bfb67cf..3c75482e8e9a 100644 --- a/role-object/pom.xml +++ b/role-object/pom.xml @@ -34,6 +34,14 @@ role-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java b/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java index 51a106864b68..125fc24af6c8 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java @@ -24,8 +24,8 @@ */ package com.iluwatar.roleobject; -import static com.iluwatar.roleobject.Role.Borrower; -import static com.iluwatar.roleobject.Role.Investor; +import static com.iluwatar.roleobject.Role.BORROWER; +import static com.iluwatar.roleobject.Role.INVESTOR; import lombok.extern.slf4j.Slf4j; @@ -39,22 +39,22 @@ * customer-specific roles is provided by {@link CustomerRole}, which also supports the {@link * Customer} interface. * - *

    The {@link CustomerRole} class is abstract and not meant to be instantiated. - * Concrete subclasses of {@link CustomerRole}, for example {@link BorrowerRole} or {@link - * InvestorRole}, define and implement the interface for specific roles. It is only these subclasses - * which are instantiated at runtime. The {@link BorrowerRole} class defines the context-specific - * view of {@link Customer} objects as needed by the loan department. It defines additional - * operations to manage the customer’s credits and securities. Similarly, the {@link InvestorRole} - * class adds operations specific to the investment department’s view of customers. A client like - * the loan application may either work with objects of the {@link CustomerRole} class, using the - * interface class {@link Customer}, or with objects of concrete {@link CustomerRole} subclasses. - * Suppose the loan application knows a particular {@link Customer} instance through its {@link - * Customer} interface. The loan application may want to check whether the {@link Customer} object - * plays the role of Borrower. To this end it calls {@link Customer#hasRole(Role)} with a suitable - * role specification. For the purpose of our example, let’s assume we can name roles with enum. If - * the {@link Customer} object can play the role named “Borrower,” the loan application will ask it - * to return a reference to the corresponding object. The loan application may now use this - * reference to call Borrower-specific operations. + *

    The {@link CustomerRole} class is abstract and not meant to be instantiated. Concrete + * subclasses of {@link CustomerRole}, for example {@link BorrowerRole} or {@link InvestorRole}, + * define and implement the interface for specific roles. It is only these subclasses which are + * instantiated at runtime. The {@link BorrowerRole} class defines the context-specific view of + * {@link Customer} objects as needed by the loan department. It defines additional operations to + * manage the customer’s credits and securities. Similarly, the {@link InvestorRole} class adds + * operations specific to the investment department’s view of customers. A client like the loan + * application may either work with objects of the {@link CustomerRole} class, using the interface + * class {@link Customer}, or with objects of concrete {@link CustomerRole} subclasses. Suppose the + * loan application knows a particular {@link Customer} instance through its {@link Customer} + * interface. The loan application may want to check whether the {@link Customer} object plays the + * role of Borrower. To this end it calls {@link Customer#hasRole(Role)} with a suitable role + * specification. For the purpose of our example, let’s assume we can name roles with enum. If the + * {@link Customer} object can play the role named “Borrower,” the loan application will ask it to + * return a reference to the corresponding object. The loan application may now use this reference + * to call Borrower-specific operations. */ @Slf4j public class ApplicationRoleObject { @@ -65,28 +65,31 @@ public class ApplicationRoleObject { * @param args program arguments */ public static void main(String[] args) { - var customer = Customer.newCustomer(Borrower, Investor); + var customer = Customer.newCustomer(BORROWER, INVESTOR); - LOGGER.info(" the new customer created : {}", customer); + LOGGER.info("New customer created : {}", customer); - var hasBorrowerRole = customer.hasRole(Borrower); - LOGGER.info(" customer has a borrowed role - {}", hasBorrowerRole); - var hasInvestorRole = customer.hasRole(Investor); - LOGGER.info(" customer has an investor role - {}", hasInvestorRole); + var hasBorrowerRole = customer.hasRole(BORROWER); + LOGGER.info("Customer has a borrower role - {}", hasBorrowerRole); + var hasInvestorRole = customer.hasRole(INVESTOR); + LOGGER.info("Customer has an investor role - {}", hasInvestorRole); - customer.getRole(Investor, InvestorRole.class) - .ifPresent(inv -> { - inv.setAmountToInvest(1000); - inv.setName("Billy"); - }); - customer.getRole(Borrower, BorrowerRole.class) - .ifPresent(inv -> inv.setName("Johny")); + customer + .getRole(INVESTOR, InvestorRole.class) + .ifPresent( + inv -> { + inv.setAmountToInvest(1000); + inv.setName("Billy"); + }); + customer.getRole(BORROWER, BorrowerRole.class).ifPresent(inv -> inv.setName("Johny")); - customer.getRole(Investor, InvestorRole.class) + customer + .getRole(INVESTOR, InvestorRole.class) .map(InvestorRole::invest) .ifPresent(LOGGER::info); - customer.getRole(Borrower, BorrowerRole.class) + customer + .getRole(BORROWER, BorrowerRole.class) .map(BorrowerRole::borrow) .ifPresent(LOGGER::info); } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java index e082438a16a2..172e9d489388 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java @@ -24,23 +24,17 @@ */ package com.iluwatar.roleobject; -/** - * Borrower role. - */ +import lombok.Getter; +import lombok.Setter; + +/** Borrower role. */ +@Getter +@Setter public class BorrowerRole extends CustomerRole { private String name; - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - public String borrow() { return String.format("Borrower %s wants to get some money.", name); } - } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/Customer.java b/role-object/src/main/java/com/iluwatar/roleobject/Customer.java index 573604cbc3ef..1b5d15592229 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/Customer.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/Customer.java @@ -27,9 +27,7 @@ import java.util.Arrays; import java.util.Optional; -/** - * The main abstraction to work with Customer. - */ +/** The main abstraction to work with Customer. */ public abstract class Customer { /** @@ -46,7 +44,6 @@ public abstract class Customer { * @param role to check * @return true if the role exists otherwise false */ - public abstract boolean hasRole(Role role); /** @@ -60,13 +57,12 @@ public abstract class Customer { /** * Get specific instance associated with this role @see {@link Role}. * - * @param role to get + * @param role to get * @param expectedRole instance class expected to get * @return optional with value if the instance exists and corresponds expected class */ public abstract Optional getRole(Role role, Class expectedRole); - public static Customer newCustomer() { return new CustomerCore(); } @@ -82,5 +78,4 @@ public static Customer newCustomer(Role... role) { Arrays.stream(role).forEach(customer::addRole); return customer; } - } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java index 04652039feff..079f6cb5f34b 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java @@ -45,12 +45,12 @@ public CustomerCore() { @Override public boolean addRole(Role role) { - return role - .instance() - .map(inst -> { - roles.put(role, inst); - return true; - }) + return role.instance() + .map( + inst -> { + roles.put(role, inst); + return true; + }) .orElse(false); } @@ -66,8 +66,7 @@ public boolean remRole(Role role) { @Override public Optional getRole(Role role, Class expectedRole) { - return Optional - .ofNullable(roles.get(role)) + return Optional.ofNullable(roles.get(role)) .filter(expectedRole::isInstance) .map(expectedRole::cast); } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java b/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java index cb8ad7bb8496..044c80cf8a3a 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java @@ -24,8 +24,5 @@ */ package com.iluwatar.roleobject; -/** - * Key abstraction for segregated roles. - */ -public abstract class CustomerRole extends CustomerCore { -} +/** Key abstraction for segregated roles. */ +public abstract class CustomerRole extends CustomerCore {} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java index 0606c9b54799..ef8cb73ae3e8 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java @@ -24,31 +24,18 @@ */ package com.iluwatar.roleobject; -/** - * Investor role. - */ +import lombok.Getter; +import lombok.Setter; + +/** Investor role. */ +@Getter +@Setter public class InvestorRole extends CustomerRole { private String name; private long amountToInvest; - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public long getAmountToInvest() { - return amountToInvest; - } - - public void setAmountToInvest(long amountToInvest) { - this.amountToInvest = amountToInvest; - } - public String invest() { return String.format("Investor %s has invested %d dollars", name, amountToInvest); } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/Role.java b/role-object/src/main/java/com/iluwatar/roleobject/Role.java index de216b0d7a07..98ef516b3ec1 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/Role.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/Role.java @@ -29,12 +29,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Possible roles. - */ +/** Possible roles. */ public enum Role { - - Borrower(BorrowerRole.class), Investor(InvestorRole.class); + BORROWER(BorrowerRole.class), + INVESTOR(InvestorRole.class); private final Class typeCst; @@ -44,18 +42,18 @@ public enum Role { private static final Logger logger = LoggerFactory.getLogger(Role.class); - /** - * Get instance. - */ + /** Get instance. */ @SuppressWarnings("unchecked") public Optional instance() { var typeCst = this.typeCst; try { return (Optional) Optional.of(typeCst.getDeclaredConstructor().newInstance()); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e) { logger.error("error creating an object", e); } return Optional.empty(); } - } diff --git a/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java index 7dab58c7e2e8..aa5be60bb4dc 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java @@ -32,6 +32,6 @@ class ApplicationRoleObjectTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> ApplicationRoleObject.main(new String[]{})); + assertDoesNotThrow(() -> ApplicationRoleObject.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java index 95e6c6a4e190..df366db16f1b 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java @@ -36,4 +36,4 @@ void borrowTest() { borrowerRole.setName("test"); assertEquals("Borrower test wants to get some money.", borrowerRole.borrow()); } -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java b/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java index 544d72a9e4b6..cd90277096bb 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java @@ -35,58 +35,57 @@ class CustomerCoreTest { @Test void addRole() { var core = new CustomerCore(); - assertTrue(core.addRole(Role.Borrower)); + assertTrue(core.addRole(Role.BORROWER)); } @Test void hasRole() { var core = new CustomerCore(); - core.addRole(Role.Borrower); - assertTrue(core.hasRole(Role.Borrower)); - assertFalse(core.hasRole(Role.Investor)); + core.addRole(Role.BORROWER); + assertTrue(core.hasRole(Role.BORROWER)); + assertFalse(core.hasRole(Role.INVESTOR)); } @Test void remRole() { var core = new CustomerCore(); - core.addRole(Role.Borrower); + core.addRole(Role.BORROWER); - var bRole = core.getRole(Role.Borrower, BorrowerRole.class); + var bRole = core.getRole(Role.BORROWER, BorrowerRole.class); assertTrue(bRole.isPresent()); - assertTrue(core.remRole(Role.Borrower)); + assertTrue(core.remRole(Role.BORROWER)); - var empt = core.getRole(Role.Borrower, BorrowerRole.class); + var empt = core.getRole(Role.BORROWER, BorrowerRole.class); assertFalse(empt.isPresent()); } @Test void getRole() { var core = new CustomerCore(); - core.addRole(Role.Borrower); + core.addRole(Role.BORROWER); - var bRole = core.getRole(Role.Borrower, BorrowerRole.class); + var bRole = core.getRole(Role.BORROWER, BorrowerRole.class); assertTrue(bRole.isPresent()); - var nonRole = core.getRole(Role.Borrower, InvestorRole.class); + var nonRole = core.getRole(Role.BORROWER, InvestorRole.class); assertFalse(nonRole.isPresent()); - var invRole = core.getRole(Role.Investor, InvestorRole.class); + var invRole = core.getRole(Role.INVESTOR, InvestorRole.class); assertFalse(invRole.isPresent()); } @Test void toStringTest() { var core = new CustomerCore(); - core.addRole(Role.Borrower); - assertEquals("Customer{roles=[Borrower]}", core.toString()); + core.addRole(Role.BORROWER); + assertEquals("Customer{roles=[BORROWER]}", core.toString()); core = new CustomerCore(); - core.addRole(Role.Investor); - assertEquals("Customer{roles=[Investor]}", core.toString()); + core.addRole(Role.INVESTOR); + assertEquals("Customer{roles=[INVESTOR]}", core.toString()); core = new CustomerCore(); assertEquals("Customer{roles=[]}", core.toString()); } - -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java index 14609c6f8283..79b26ced83e7 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java @@ -37,4 +37,4 @@ void investTest() { investorRole.setAmountToInvest(10); assertEquals("Investor test has invested 10 dollars", investorRole.invest()); } -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java index b501b469d155..825dc6fea095 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java @@ -33,8 +33,8 @@ class RoleTest { @Test void instanceTest() { - var instance = Role.Borrower.instance(); + var instance = Role.BORROWER.instance(); assertTrue(instance.isPresent()); assertEquals(instance.get().getClass(), BorrowerRole.class); } -} \ No newline at end of file +} diff --git a/saga/README.md b/saga/README.md index f25e1011420d..47709dd68ba4 100644 --- a/saga/README.md +++ b/saga/README.md @@ -1,47 +1,200 @@ --- -title: Saga -category: Concurrency +title: "Saga Pattern in Java: Mastering Long-Running Transactions in Distributed Systems" +shortTitle: Saga +description: "Explore the Saga pattern in Java for managing distributed transactions across microservices with resilience and fault tolerance. Learn how the Saga pattern ensures data consistency without locking resources." +category: Resilience language: en tag: - - Cloud distributed + - Asynchronous + - Decoupling + - Fault tolerance + - Integration + - Microservices + - Transactions --- -## Also known as -This pattern has a similar goal with two-phase commit (XA transaction) +## Intent of Saga Design Pattern -## Intent -This pattern is used in distributed services to perform a group of operations atomically. -This is an analog of transaction in a database but in terms of microservices architecture this is executed -in a distributed environment +To manage and coordinate distributed transactions across multiple services in a fault-tolerant and reliable manner. -## Explanation -A saga is a sequence of local transactions in a certain context. If one transaction fails for some reason, -the saga executes compensating transactions(rollbacks) to undo the impact of the preceding transactions. -There are two types of Saga: +## Detailed Explanation of Saga Pattern with Real-World Examples -- Choreography-Based Saga. -In this approach, there is no central orchestrator. -Each service participating in the Saga performs their transaction and publish events. -The other services act upon those events and perform their transactions. -Also, they may or not publish other events based on the situation. +Real-world example -- Orchestration-Based Saga -In this approach, there is a Saga orchestrator that manages all the transactions and directs -the participant services to execute local transactions based on events. -This orchestrator can also be though of as a Saga Manager. +> Imagine a travel agency coordinating a vacation package for a customer. The package includes booking a flight, reserving a hotel room, and renting a car. Each of these bookings is managed by a different service provider. If the flight booking is successful but the hotel is fully booked, the agency needs to cancel the flight and notify the customer. This ensures that the customer does not end up with only a partial vacation package. In the Saga pattern, this scenario is managed by a series of coordinated transactions, with compensating actions (like canceling the flight) to maintain consistency. -## Class diagram -![alt text](./etc/saga.urm.png "Saga pattern class diagram") +In plain words -## Applicability -Use the Saga pattern, if: +> The Saga pattern in Java coordinates distributed transactions across microservices using a sequence of events and compensating actions to ensure data consistency and fault tolerance. -- you need to perform a group of operations related to different microservices atomically -- you need to rollback changes in different places in case of failure one of the operation -- you need to take care of data consistency in different places including different databases -- you can not use 2PC(two phase commit) +Wikipedia says -## Credits +> Long-running transactions (also known as the saga interaction pattern) are computer database transactions that avoid locks on non-local resources, use compensation to handle failures, potentially aggregate smaller ACID transactions (also referred to as atomic transactions), and typically use a coordinator to complete or abort the transaction. In contrast to rollback in ACID transactions, compensation restores the original state, or an equivalent, and is business-specific. For example, the compensating action for making a hotel reservation is canceling that reservation. -- [Pattern: Saga](https://microservices.io/patterns/data/saga.html) -- [Saga distributed transactions pattern](https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga) +## Programmatic Example of Saga Pattern in Java + +The Saga design pattern is a sequence of local transactions where each transaction updates data within a single service. It's particularly useful in a microservices architecture where each service has its own database. The Saga pattern ensures data consistency and fault tolerance across services. Here are the key components of the Saga pattern: + +1. **Saga**: A Saga is a sequence of local transactions, each of which is called a chapter. The Saga manages the sequence of these transactions, ensuring that each transaction is performed in the correct order and that the Saga is rolled back if a transaction fails. + +2. **Chapter**: Each chapter in a Saga represents a local transaction. A chapter has a name, a result (which can be `INIT`, `SUCCESS`, or `ROLLBACK`), and an input value. The `Chapter` class provides methods to get and set these properties. + +3. **Service**: A service performs a local transaction. It processes the input value of a chapter and returns a `ChapterResult`. If the transaction fails, it sets the status of the chapter to `ROLLBACK`. + +4. **Service Discovery**: This component is responsible for discovering available services and executing the Saga. It processes each chapter in the Saga in order. If a chapter fails, the Saga will be rolled back. + +5. **Saga Result**: The result of a Saga can be `PROGRESS`, `FINISHED`, or `ROLLBACKED`. This is determined by the `getResult` method of the `Saga` class. + +In a real-world application, the `Service` class would contain the logic to perform the local transaction and handle failures. The `Saga` class would manage the sequence of local transactions, ensuring that each transaction is performed in the correct order and that the Saga is rolled back if a transaction fails. + +**Snippet 1: Creating a Saga** + +The first step in using the Saga pattern is to create a Saga. A Saga is a sequence of chapters, each representing a local transaction. The `Saga` class provides methods to add chapters and to check if a chapter is present. + +```java +// Create a new Saga +Saga saga = Saga.create(); +``` + +**Snippet 2: Adding Chapters to the Saga** + +Each chapter in a Saga represents a local transaction. We can add chapters to the Saga using the `chapter` method. + +```java +// Add chapters to the Saga +saga.chapter("init an order"); +saga.chapter("booking a Fly"); +saga.chapter("booking a Hotel"); +saga.chapter("withdrawing Money"); +``` + +**Snippet 3: Setting Input Values for Chapters** + +Each chapter in a Saga can have an input value. We can set the input value for the last added chapter using the `setInValue` method. + +```java +// Set input values for the chapters +saga.chapter("init an order").setInValue("good_order"); +``` + +**Snippet 4: Executing the Saga** + +We can execute the Saga using a service. The service will process each chapter in the Saga in order. If a chapter fails, the Saga will be rolled back. + +```java +// Execute the Saga +var service = sd.findAny(); +var goodOrderSaga = service.execute(saga); +``` + +**Snippet 5: Checking the Result of the Saga** + +We can check the result of the Saga using the `getResult` method. This method returns the result of the Saga, which can be `PROGRESS`, `FINISHED`, or `ROLLBACKED`. + +```java +// Check the result of the Saga +SagaResult result = goodOrderSaga.getResult(); +``` + +The `SagaApplication` class has a `main` method for running the example. + +```java +@Slf4j +public class SagaApplication { + + public static void main(String[] args) { + var sd = serviceDiscovery(); + var service = sd.findAny(); + var goodOrderSaga = service.execute(newSaga("good_order")); + var badOrderSaga = service.execute(newSaga("bad_order")); + LOGGER.info("orders: goodOrder is {}, badOrder is {}", + goodOrderSaga.getResult(), badOrderSaga.getResult()); + } + + private static Saga newSaga(Object value) { + return Saga + .create() + .chapter("init an order").setInValue(value) + .chapter("booking a Fly") + .chapter("booking a Hotel") + .chapter("withdrawing Money"); + } + + private static ServiceDiscoveryService serviceDiscovery() { + var sd = new ServiceDiscoveryService(); + return sd + .discover(new OrderService(sd)) + .discover(new FlyBookingService(sd)) + .discover(new HotelBookingService(sd)) + .discover(new WithdrawMoneyService(sd)); + } +} +``` + +1. **Saga**: The `SagaApplication` creates a new Saga using the `Saga.create()` method. It then adds chapters to the Saga using the `chapter` method and sets the input value for each chapter using the `setInValue` method. +2. **Service**: The `SagaApplication` uses services to execute the chapters in the Saga. Each service represents a local transaction. The `SagaApplication` uses the `ServiceDiscoveryService` to discover available services and execute the Saga. +3. **Service Discovery**: The `ServiceDiscoveryService` is used to discover available services. The `SagaApplication` uses this to find a service and execute the Saga. +4. **Saga Execution**: The `SagaApplication` executes the Saga using the `execute` method of a service. It creates two Sagas, one for a good order and one for a bad order, and executes them. +5. **Saga Result**: The `SagaApplication` checks the result of the Saga using the `getResult` method. It logs the result of the good order Saga and the bad order Saga. + +In summary, the `SagaApplication` creates a Saga, adds chapters to it, sets the input value for each chapter, discovers services, executes the Saga using a service, and checks the result of the Saga. + +Running the example produces the following console output: + +``` +11:32:17.779 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'init an order' has been started. The data good_order has been stored or calculated successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'booking a Fly' has been started. The data good_order has been stored or calculated successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'booking a Hotel' has been started. The data good_order has been stored or calculated successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'withdrawing Money' has been started. The data good_order has been stored or calculated successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- the saga has been finished with FINISHED status +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'init an order' has been started. The data bad_order has been stored or calculated successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'booking a Fly' has been started. The data bad_order has been stored or calculated successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'booking a Hotel' has been started. The data bad_order has been stored or calculated successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The chapter 'withdrawing Money' has been started. But the exception has been raised.The rollback is about to start +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The Rollback for a chapter 'booking a Hotel' has been started. The data bad_order has been rollbacked successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The Rollback for a chapter 'booking a Fly' has been started. The data bad_order has been rollbacked successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- The Rollback for a chapter 'init an order' has been started. The data bad_order has been rollbacked successfully +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.Service -- the saga has been finished with ROLLBACKED status +11:32:17.782 [main] INFO com.iluwatar.saga.choreography.SagaApplication -- orders: goodOrder is FINISHED, badOrder is ROLLBACKED +``` + +This is a basic example of how to use the Saga design pattern. In a real-world application, the `Saga` class would manage the sequence of local transactions, ensuring that each transaction is performed in the correct order and that the Saga is rolled back if a transaction fails. + +## When to Use the Saga Pattern in Java + +* When you have a complex transaction that spans multiple microservices. +* When you need to ensure data consistency across services without using a traditional two-phase commit. +* When you need to handle long-running transactions in an asynchronous manner. + +## Real-World Applications of Saga Pattern in Java + +* E-commerce platforms managing orders, inventory, and payment services. +* Banking systems coordinating between account debits and credits across multiple services. +* Travel booking systems coordinating flights, hotels, and car rentals. + +## Benefits and Trade-offs of Saga Pattern + +Benefits: + +* Improved fault tolerance and reliability. +* Scalability due to decoupled services. +* Flexibility in handling long-running transactions. + +Trade-offs: + +* Increased complexity in handling compensating transactions. +* Requires careful design to handle partial failures and rollback scenarios. +* Potential latency due to asynchronous nature. + +## Related Java Design Patterns + +* [Event Sourcing](https://java-design-patterns.com/patterns/event-sourcing/): Used to capture state changes as a sequence of events, which can complement the Saga pattern by providing a history of state changes. +* [Command Query Responsibility Segregation (CQRS)](https://java-design-patterns.com/patterns/cqrs/): Can be used in conjunction with the Saga pattern to separate command and query responsibilities, improving scalability and fault tolerance. + +## References and Credits + +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3y6yv1z) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) +* [Pattern: Saga (microservices.io)](https://microservices.io/patterns/data/saga.html) +* [Saga distributed transactions pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga) diff --git a/saga/pom.xml b/saga/pom.xml index 6ca621c868cf..7697d9fbc53b 100644 --- a/saga/pom.xml +++ b/saga/pom.xml @@ -34,6 +34,14 @@ saga + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java index 5839773a4a21..8f46ff63248a 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java @@ -24,7 +24,6 @@ */ package com.iluwatar.saga.choreography; - /** * ChoreographyChapter is an interface representing a contract for an external service. In that * case, a service needs to make a decision what to do further hence the server needs to get all @@ -62,6 +61,4 @@ public interface ChoreographyChapter { * @return result {@link Saga} */ Saga rollback(Saga saga); - - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java index 9140cd0527fa..491b4a590672 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java @@ -24,10 +24,7 @@ */ package com.iluwatar.saga.choreography; - -/** - * Class representing a service to book a fly. - */ +/** Class representing a service to book a fly. */ public class FlyBookingService extends Service { public FlyBookingService(ServiceDiscoveryService service) { super(service); diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java index 280c0f05b597..3f44fbd3ea76 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java @@ -24,10 +24,7 @@ */ package com.iluwatar.saga.choreography; - -/** - * Class representing a service to book a hotel. - */ +/** Class representing a service to book a hotel. */ public class HotelBookingService extends Service { public HotelBookingService(ServiceDiscoveryService service) { super(service); @@ -37,6 +34,4 @@ public HotelBookingService(ServiceDiscoveryService service) { public String getName() { return "booking a Hotel"; } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java index 55778bb1d048..36b8717c32c6 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java @@ -24,10 +24,7 @@ */ package com.iluwatar.saga.choreography; - -/** - * Class representing a service to init a new order. - */ +/** Class representing a service to init a new order. */ public class OrderService extends Service { public OrderService(ServiceDiscoveryService service) { diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java index a98abd33200a..4b690c3c2067 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java @@ -27,6 +27,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import lombok.Getter; +import lombok.Setter; /** * Saga representation. Saga consists of chapters. Every ChoreographyChapter is executed a certain @@ -39,7 +41,6 @@ public class Saga { private boolean forward; private boolean finished; - public static Saga create() { return new Saga(); } @@ -51,9 +52,7 @@ public static Saga create() { */ public SagaResult getResult() { if (finished) { - return forward - ? SagaResult.FINISHED - : SagaResult.ROLLBACKED; + return forward ? SagaResult.FINISHED : SagaResult.ROLLBACKED; } return SagaResult.PROGRESS; @@ -128,7 +127,6 @@ int back() { return --pos; } - private Saga() { this.chapters = new ArrayList<>(); this.pos = 0; @@ -140,7 +138,6 @@ Chapter getCurrent() { return chapters.get(pos); } - boolean isPresent() { return pos >= 0 && pos < chapters.size(); } @@ -154,37 +151,15 @@ boolean isCurrentSuccess() { * outcoming parameter). */ public static class Chapter { - private final String name; - private ChapterResult result; - private Object inValue; - + @Getter private final String name; + @Setter private ChapterResult result; + @Getter @Setter private Object inValue; public Chapter(String name) { this.name = name; this.result = ChapterResult.INIT; } - public Object getInValue() { - return inValue; - } - - public void setInValue(Object object) { - this.inValue = object; - } - - public String getName() { - return name; - } - - /** - * set result. - * - * @param result {@link ChapterResult} - */ - public void setResult(ChapterResult result) { - this.result = result; - } - /** * the result for chapter is good. * @@ -195,19 +170,18 @@ public boolean isSuccess() { } } - - /** - * result for chapter. - */ + /** result for chapter. */ public enum ChapterResult { - INIT, SUCCESS, ROLLBACK + INIT, + SUCCESS, + ROLLBACK } - /** - * result for saga. - */ + /** result for saga. */ public enum SagaResult { - PROGRESS, FINISHED, ROLLBACKED + PROGRESS, + FINISHED, + ROLLBACKED } @Override diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java index 86758da00067..09d68f4292d8 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java @@ -31,12 +31,12 @@ * an analog of transaction in a database but in terms of microservices architecture this is * executed in a distributed environment * - *

    A saga is a sequence of local transactions in a certain context. - * If one transaction fails for some reason, the saga executes compensating transactions(rollbacks) - * to undo the impact of the preceding transactions. + *

    A saga is a sequence of local transactions in a certain context. If one transaction fails for + * some reason, the saga executes compensating transactions(rollbacks) to undo the impact of the + * preceding transactions. * - *

    In this approach, there are no mediators or orchestrators services. - * All chapters are handled and moved by services manually. + *

    In this approach, there are no mediators or orchestrators services. All chapters are handled + * and moved by services manually. * *

    The major difference with choreography saga is an ability to handle crashed services * (otherwise in choreography services very hard to prevent a saga if one of them has been crashed) @@ -47,24 +47,22 @@ @Slf4j public class SagaApplication { - /** - * main method. - */ + /** main method. */ public static void main(String[] args) { var sd = serviceDiscovery(); var service = sd.findAny(); var goodOrderSaga = service.execute(newSaga("good_order")); var badOrderSaga = service.execute(newSaga("bad_order")); - LOGGER.info("orders: goodOrder is {}, badOrder is {}", - goodOrderSaga.getResult(), badOrderSaga.getResult()); - + LOGGER.info( + "orders: goodOrder is {}, badOrder is {}", + goodOrderSaga.getResult(), + badOrderSaga.getResult()); } - private static Saga newSaga(Object value) { - return Saga - .create() - .chapter("init an order").setInValue(value) + return Saga.create() + .chapter("init an order") + .setInValue(value) .chapter("booking a Fly") .chapter("booking a Hotel") .chapter("withdrawing Money"); @@ -72,8 +70,7 @@ private static Saga newSaga(Object value) { private static ServiceDiscoveryService serviceDiscovery() { var sd = new ServiceDiscoveryService(); - return sd - .discover(new OrderService(sd)) + return sd.discover(new OrderService(sd)) .discover(new FlyBookingService(sd)) .discover(new HotelBookingService(sd)) .discover(new WithdrawMoneyService(sd)); diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Service.java b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java index 3cd7b84ff2b2..6510b830c8fd 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/Service.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java @@ -28,7 +28,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * Common abstraction class representing services. implementing a general contract @see {@link * ChoreographyChapter} @@ -70,21 +69,24 @@ public Saga execute(Saga saga) { } var finalNextSaga = nextSaga; - return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga)) + return sd.find(chapterName) + .map(ch -> ch.execute(finalNextSaga)) .orElseThrow(serviceNotFoundException(chapterName)); } private Supplier serviceNotFoundException(String chServiceName) { - return () -> new RuntimeException( - String.format("the service %s has not been found", chServiceName)); + return () -> + new RuntimeException(String.format("the service %s has not been found", chServiceName)); } @Override public Saga process(Saga saga) { var inValue = saga.getCurrentValue(); - LOGGER.info("The chapter '{}' has been started. " + LOGGER.info( + "The chapter '{}' has been started. " + "The data {} has been stored or calculated successfully", - getName(), inValue); + getName(), + inValue); saga.setCurrentStatus(Saga.ChapterResult.SUCCESS); saga.setCurrentValue(inValue); return saga; @@ -93,9 +95,11 @@ public Saga process(Saga saga) { @Override public Saga rollback(Saga saga) { var inValue = saga.getCurrentValue(); - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been rollbacked successfully", - getName(), inValue); + getName(), + inValue); saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); saga.setCurrentValue(inValue); @@ -110,5 +114,4 @@ private boolean isSagaFinished(Saga saga) { } return false; } - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java index d159ea453893..49cc7c92fbf2 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java @@ -29,9 +29,7 @@ import java.util.NoSuchElementException; import java.util.Optional; -/** - * The class representing a service discovery pattern. - */ +/** The class representing a service discovery pattern. */ public class ServiceDiscoveryService { private final Map services; @@ -57,6 +55,4 @@ public ServiceDiscoveryService discover(ChoreographyChapter chapterService) { public ServiceDiscoveryService() { this.services = new HashMap<>(); } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java index 8dccab9d27f9..095170fb800d 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.choreography; -/** - * Class representing a service to withdraw a money. - */ +/** Class representing a service to withdraw a money. */ public class WithdrawMoneyService extends Service { public WithdrawMoneyService(ServiceDiscoveryService service) { @@ -43,9 +41,10 @@ public Saga process(Saga saga) { var inValue = saga.getCurrentValue(); if (inValue.equals("bad_order")) { - LOGGER.info("The chapter '{}' has been started. But the exception has been raised." + LOGGER.info( + "The chapter '{}' has been started. But the exception has been raised." + "The rollback is about to start", - getName(), inValue); + getName()); saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); return saga; } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java index 97768011210b..9790373f6275 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java @@ -24,19 +24,17 @@ */ package com.iluwatar.saga.orchestration; +import lombok.Getter; + /** * Executing result for chapter. * * @param incoming value */ public class ChapterResult { - private final K value; + @Getter private final K value; private final State state; - public K getValue() { - return value; - } - ChapterResult(K value, State state) { this.value = value; this.state = state; @@ -54,10 +52,9 @@ public static ChapterResult failure(K val) { return new ChapterResult<>(val, State.FAILURE); } - /** - * state for chapter. - */ + /** state for chapter. */ public enum State { - SUCCESS, FAILURE + SUCCESS, + FAILURE } } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java index 1540b4afba3b..19a0fc1f8d5b 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to book a fly. - */ +/** Class representing a service to book a fly. */ public class FlyBookingService extends Service { @Override public String getName() { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java index aa3a9266e957..ebd602032c83 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java @@ -24,29 +24,30 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to book a hotel. - */ +/** Class representing a service to book a hotel. */ public class HotelBookingService extends Service { @Override public String getName() { return "booking a Hotel"; } - @Override public ChapterResult rollback(String value) { if (value.equals("crashed_order")) { - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been failed.The saga has been crashed.", - getName(), value); + getName(), + value); return ChapterResult.failure(value); } - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been rollbacked successfully", - getName(), value); + getName(), + value); return super.rollback(value); } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java index 1197de110587..537e5af14e0d 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java @@ -53,5 +53,4 @@ public interface OrchestrationChapter { * @return result {@link ChapterResult} */ ChapterResult rollback(K value); - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java index 67d0f9089d68..2f1432d39ed3 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to init a new order. - */ +/** Class representing a service to init a new order. */ public class OrderService extends Service { @Override public String getName() { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java index 5c3756725d7b..b89886c4aef0 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java @@ -26,6 +26,8 @@ import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; /** * Saga representation. Saga consists of chapters. Every ChoreographyChapter is executed by a @@ -35,18 +37,15 @@ public class Saga { private final List chapters; - private Saga() { this.chapters = new ArrayList<>(); } - public Saga chapter(String name) { this.chapters.add(new Chapter(name)); return this; } - public Chapter get(int idx) { return chapters.get(idx); } @@ -55,30 +54,21 @@ public boolean isPresent(int idx) { return idx >= 0 && idx < chapters.size(); } - public static Saga create() { return new Saga(); } - /** - * result for saga. - */ + /** result for saga. */ public enum Result { - FINISHED, ROLLBACK, CRASHED + FINISHED, + ROLLBACK, + CRASHED } - /** - * class represents chapter name. - */ + /** class represents chapter name. */ + @AllArgsConstructor + @Getter public static class Chapter { String name; - - public Chapter(String name) { - this.name = name; - } - - public String getName() { - return name; - } } } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java index 2d5a748d94f4..200a73b8af4d 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java @@ -31,15 +31,14 @@ * an analog of transaction in a database but in terms of microservices architecture this is * executed in a distributed environment * - *

    A saga is a sequence of local transactions in a certain context. - * If one transaction fails for some reason, the saga executes compensating transactions(rollbacks) - * to undo the impact of the preceding transactions. + *

    A saga is a sequence of local transactions in a certain context. If one transaction fails for + * some reason, the saga executes compensating transactions(rollbacks) to undo the impact of the + * preceding transactions. * - *

    In this approach, there is an orchestrator @see {@link SagaOrchestrator} - * that manages all the transactions and directs the participant services to execute local - * transactions based on events. The major difference with choreography saga is an ability to handle - * crashed services (otherwise in choreography services very hard to prevent a saga if one of them - * has been crashed) + *

    In this approach, there is an orchestrator @see {@link SagaOrchestrator} that manages all the + * transactions and directs the participant services to execute local transactions based on events. + * The major difference with choreography saga is an ability to handle crashed services (otherwise + * in choreography services very hard to prevent a saga if one of them has been crashed) * * @see Saga * @see SagaOrchestrator @@ -48,9 +47,7 @@ @Slf4j public class SagaApplication { - /** - * method to show common saga logic. - */ + /** method to show common saga logic. */ public static void main(String[] args) { var sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); @@ -58,14 +55,15 @@ public static void main(String[] args) { Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); - LOGGER.info("orders: goodOrder is {}, badOrder is {},crashedOrder is {}", - goodOrder, badOrder, crashedOrder); + LOGGER.info( + "orders: goodOrder is {}, badOrder is {},crashedOrder is {}", + goodOrder, + badOrder, + crashedOrder); } - private static Saga newSaga() { - return Saga - .create() + return Saga.create() .chapter("init an order") .chapter("booking a Fly") .chapter("booking a Hotel") diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java index 88128879ad76..2cfe2acc0f80 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java @@ -31,7 +31,6 @@ import lombok.extern.slf4j.Slf4j; - /** * The orchestrator that manages all the transactions and directs the participant services to * execute local transactions based on events. @@ -42,12 +41,11 @@ public class SagaOrchestrator { private final ServiceDiscoveryService sd; private final CurrentState state; - /** * Create a new service to orchetrate sagas. * * @param saga saga to process - * @param sd service discovery @see {@link ServiceDiscoveryService} + * @param sd service discovery @see {@link ServiceDiscoveryService} */ public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { this.saga = saga; @@ -59,7 +57,7 @@ public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { * pipeline to execute saga process/story. * * @param value incoming value - * @param type for incoming value + * @param type for incoming value * @return result @see {@link Result} */ @SuppressWarnings("unchecked") @@ -101,15 +99,12 @@ public Result execute(K value) { } } - if (!saga.isPresent(next)) { return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK; } } - } - private static class CurrentState { int currentNumber; boolean isForward; @@ -124,7 +119,6 @@ void cleanUp() { this.isForward = true; } - boolean isForward() { return isForward; } @@ -145,5 +139,4 @@ int current() { return currentNumber; } } - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java index 6fc74d1943cf..00ec6d18e036 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java @@ -39,22 +39,23 @@ public abstract class Service implements OrchestrationChapter { @Override public abstract String getName(); - @Override public ChapterResult process(K value) { - LOGGER.info("The chapter '{}' has been started. " + LOGGER.info( + "The chapter '{}' has been started. " + "The data {} has been stored or calculated successfully", - getName(), value); + getName(), + value); return ChapterResult.success(value); } @Override public ChapterResult rollback(K value) { - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been rollbacked successfully", - getName(), value); + getName(), + value); return ChapterResult.success(value); } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java index 19b85fd92304..face085fb451 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java @@ -28,9 +28,7 @@ import java.util.Map; import java.util.Optional; -/** - * The class representing a service discovery pattern. - */ +/** The class representing a service discovery pattern. */ public class ServiceDiscoveryService { private final Map> services; @@ -46,6 +44,4 @@ public ServiceDiscoveryService discover(OrchestrationChapter orchestrationCha public ServiceDiscoveryService() { this.services = new HashMap<>(); } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java index bea7c3a33e30..b6fb2a03cb54 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to withdraw a money. - */ +/** Class representing a service to withdraw a money. */ public class WithdrawMoneyService extends Service { @Override public String getName() { @@ -36,9 +34,10 @@ public String getName() { @Override public ChapterResult process(String value) { if (value.equals("bad_order") || value.equals("crashed_order")) { - LOGGER.info("The chapter '{}' has been started. But the exception has been raised." + LOGGER.info( + "The chapter '{}' has been started. But the exception has been raised." + "The rollback is about to start", - getName(), value); + getName()); return ChapterResult.failure(value); } return super.process(value); diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java index 29f835684b46..a3d5b136e69d 100644 --- a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java @@ -35,6 +35,6 @@ class SagaApplicationTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> SagaApplication.main(new String[]{})); + assertDoesNotThrow(() -> SagaApplication.main(new String[] {})); } } diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java index 8154f368a8fc..14e6658f447c 100644 --- a/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.saga.choreography; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * test to check choreography saga - */ +import org.junit.jupiter.api.Test; + +/** test to check choreography saga */ class SagaChoreographyTest { @Test @@ -45,9 +43,9 @@ void executeTest() { } private static Saga newSaga(Object value) { - return Saga - .create() - .chapter("init an order").setInValue(value) + return Saga.create() + .chapter("init an order") + .setInValue(value) .chapter("booking a Fly") .chapter("booking a Hotel") .chapter("withdrawing Money"); @@ -55,8 +53,7 @@ private static Saga newSaga(Object value) { private static ServiceDiscoveryService serviceDiscovery() { var sd = new ServiceDiscoveryService(); - return sd - .discover(new OrderService(sd)) + return sd.discover(new OrderService(sd)) .discover(new FlyBookingService(sd)) .discover(new HotelBookingService(sd)) .discover(new WithdrawMoneyService(sd)); diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java index b06cb65a2ce0..a428a109e6b1 100644 --- a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Test if the application starts without throwing an exception. - */ +/** Test if the application starts without throwing an exception. */ class SagaApplicationTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> SagaApplication.main(new String[]{})); + assertDoesNotThrow(() -> SagaApplication.main(new String[] {})); } } diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java index a9ff4db05edb..1270e652595e 100644 --- a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.saga.orchestration; -import org.junit.jupiter.api.Test; - import static com.iluwatar.saga.orchestration.Saga.Result; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; -/** - * test to test orchestration logic - */ +/** test to test orchestration logic */ class SagaOrchestratorInternallyTest { private final List records = new ArrayList<>(); @@ -46,16 +43,12 @@ void executeTest() { var result = sagaOrchestrator.execute(1); assertEquals(Result.ROLLBACK, result); assertArrayEquals( - new String[]{"+1", "+2", "+3", "+4", "-4", "-3", "-2", "-1"}, - records.toArray(new String[]{})); + new String[] {"+1", "+2", "+3", "+4", "-4", "-3", "-2", "-1"}, + records.toArray(new String[] {})); } private static Saga newSaga() { - return Saga.create() - .chapter("1") - .chapter("2") - .chapter("3") - .chapter("4"); + return Saga.create().chapter("1").chapter("2").chapter("3").chapter("4"); } private ServiceDiscoveryService serviceDiscovery() { @@ -145,4 +138,4 @@ public ChapterResult rollback(Integer value) { return ChapterResult.success(value); } } -} \ No newline at end of file +} diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java index 687d772977b7..a136f90a927d 100644 --- a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.saga.orchestration; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * test to check general logic - */ +import org.junit.jupiter.api.Test; + +/** test to check general logic */ class SagaOrchestratorTest { @Test @@ -44,8 +42,7 @@ void execute() { } private static Saga newSaga() { - return Saga - .create() + return Saga.create() .chapter("init an order") .chapter("booking a Fly") .chapter("booking a Hotel") @@ -53,11 +50,10 @@ private static Saga newSaga() { } private static ServiceDiscoveryService serviceDiscovery() { - return - new ServiceDiscoveryService() - .discover(new OrderService()) - .discover(new FlyBookingService()) - .discover(new HotelBookingService()) - .discover(new WithdrawMoneyService()); + return new ServiceDiscoveryService() + .discover(new OrderService()) + .discover(new FlyBookingService()) + .discover(new HotelBookingService()) + .discover(new WithdrawMoneyService()); } -} \ No newline at end of file +} diff --git a/separated-interface/README.md b/separated-interface/README.md index faf04397a743..1b26f40aa255 100644 --- a/separated-interface/README.md +++ b/separated-interface/README.md @@ -1,136 +1,155 @@ --- -title: Separated Interface +title: "Separated Interface Pattern in Java: Streamlining Java Development with Interface Isolation" +shortTitle: Separated Interface +description: "Explore the Separated Interface design pattern in Java: Enhance software flexibility and maintainability by decoupling interfaces from implementations. Ideal for developers looking to improve code scalability and adaptability." category: Structural language: en tag: - - Decoupling + - API design + - Decoupling + - Interface --- +## Also known as -## Intent +* API Segregation +* Client-Server Interface -Separate the interface definition and implementation in different packages. This allows the client -to be completely unaware of the implementation. +## Intent of Separated Interface Design Pattern -## Explanation +The Separated Interface design pattern defines a client interface in a separate package from its implementation to allow for easier swapping of implementations and better separation of concerns. -Real world example +## Detailed Explanation of Separated Interface Pattern with Real-World Examples -> An Invoice generator may be created with ability to use different Tax calculators that may be -> added in the invoice depending upon type of purchase, region etc. +Real-world example -In plain words - -> Separated interface pattern encourages to keep the implementations of an interface decoupled from -> the client and its definition, so the client is not dependent on the implementation. - -A client code may abstract some specific functionality to an interface, and define the definition of -the interface as an SPI ([Service Programming Interface](https://en.wikipedia.org/wiki/Service_provider_interface) -is an API intended and open to be implemented or extended by a third party). Another package may -implement this interface definition with a concrete logic, which will be injected into the client -code at runtime (with a third class, injecting the implementation in the client) or at compile time -(using Plugin pattern with some configurable file). - -**Programmatic Example** +> Consider a restaurant where the menu (interface) is separate from the kitchen operations (implementation). +> +> In this analogy, the menu lists the dishes customers can order, without detailing how they are prepared. Different restaurants (or even different chefs within the same restaurant) can use their own recipes and methods to prepare the dishes listed on the menu. This separation allows the restaurant to update its menu or change its chefs without disrupting the overall dining experience. Similarly, in software, the Separated Interface pattern decouples the interface from its implementation, allowing changes and variations in the implementation without affecting the client code that relies on the interface. -**Client** +In plain words -`InvoiceGenerator` class accepts the cost of the product and calculates the total -amount payable inclusive of tax. +> Defines a client interface separate from its implementation to allow for flexible and interchangeable components. -```java -public class InvoiceGenerator { +## Programmatic Example of Separated Interface Pattern in Java - private final TaxCalculator taxCalculator; +The Java Separated Interface design pattern is a crucial software architecture strategy that promotes separating the interface definition from its implementation, crucial for enhancing system flexibility and scalability. This allows the client to be completely unaware of the implementation, promoting loose coupling and enhancing flexibility. - private final double amount; +In the given code, the `InvoiceGenerator` class is the client that uses the `TaxCalculator` interface to calculate tax. The `TaxCalculator` interface is implemented by two classes: `ForeignTaxCalculator` and `DomesticTaxCalculator`. These implementations are injected into the `InvoiceGenerator` class at runtime, demonstrating the Separated Interface pattern. - public InvoiceGenerator(double amount, TaxCalculator taxCalculator) { - this.amount = amount; - this.taxCalculator = taxCalculator; - } +Let's break down the code: - public double getAmountWithTax() { - return amount + taxCalculator.calculate(amount); - } - -} -``` - -The tax calculation logic is delegated to the `TaxCalculator` interface. +First, we have the `TaxCalculator` interface. This interface defines a single method `calculate` that takes an amount and returns the calculated tax. ```java public interface TaxCalculator { - double calculate(double amount); - } ``` -**Implementation package** - -In another package (which the client is completely unaware of) there exist multiple implementations -of the `TaxCalculator` interface. `ForeignTaxCalculator` is one of them which levies 60% tax -for international products. +Next, we have two classes `ForeignTaxCalculator` and `DomesticTaxCalculator` that implement the `TaxCalculator` interface. These classes provide the concrete logic for tax calculation. ```java public class ForeignTaxCalculator implements TaxCalculator { - public static final double TAX_PERCENTAGE = 60; @Override public double calculate(double amount) { return amount * TAX_PERCENTAGE / 100.0; } - } -``` -Another is `DomesticTaxCalculator` which levies 20% tax for international products. - -```java public class DomesticTaxCalculator implements TaxCalculator { - public static final double TAX_PERCENTAGE = 20; @Override public double calculate(double amount) { return amount * TAX_PERCENTAGE / 100.0; } +} +``` +The `InvoiceGenerator` class is the client that uses the `TaxCalculator` interface. It doesn't know about the concrete implementations of the `TaxCalculator` interface. It just knows that it has a `TaxCalculator` that can calculate tax. + +```java +public class InvoiceGenerator { + private final TaxCalculator taxCalculator; + private final double amount; + + public InvoiceGenerator(double amount, TaxCalculator taxCalculator) { + this.amount = amount; + this.taxCalculator = taxCalculator; + } + + public double getAmountWithTax() { + return amount + taxCalculator.calculate(amount); + } } ``` -These both implementations are instantiated and injected in the client class by the ```App.java``` -class. +Finally, in the `App` class, we create instances of `InvoiceGenerator` with different `TaxCalculator` implementations. This demonstrates how the Separated Interface pattern allows us to inject different implementations at runtime. ```java - var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator()); +public class App { + public static final double PRODUCT_COST = 50.0; + public static void main(String[] args) { + var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator()); LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax()); var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator()); - LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax()); + } +} +``` + +Console output: + +``` +11:38:53.208 [main] INFO com.iluwatar.separatedinterface.App -- Foreign Tax applied: 80.0 +11:38:53.210 [main] INFO com.iluwatar.separatedinterface.App -- Domestic Tax applied: 60.0 ``` -## Class diagram +In this way, the Separated Interface pattern allows us to decouple the interface of a component from its implementation, enhancing flexibility and maintainability and making it ideal for dynamic Java application environments. + +## When to Use the Separated Interface Pattern in Java + +* Use when you want to decouple the interface of a component from its implementation. +* Particularly effective in large-scale Java systems, where separate teams handle different components, the Separated Interface pattern ensures seamless integration and easier maintenance. +* Ideal when the implementation might change over time or vary between deployments. + +## Separated Interface Pattern Tutorials + +* [Separated Interface Design Pattern Explained (Ram N Java)](https://www.youtube.com/watch?v=d3k-hOA7k2Y) + +## Real-World Applications of Separated Interface Pattern in Java + +* Java's JDBC (Java Database Connectivity) API separates the client interface from the database driver implementations. +* Remote Method Invocation (RMI) in Java, where the client and server interfaces are defined separately from the implementations. + +## Benefits and Trade-offs of Separated Interface Pattern -![alt text](./etc/class_diagram.png "Separated Interface") +Benefits: -## Applicability +* Enhances flexibility by allowing multiple implementations to coexist. +* Facilitates testing by allowing mock implementations. +* Improves maintainability by isolating changes to specific parts of the code. -Use the Separated interface pattern when +Trade-offs: -* You are developing a framework package, and your framework needs to call some application code through interfaces. -* You have separate packages implementing the functionalities which may be plugged in your client code at runtime or compile-time. -* Your code resides in a layer that is not allowed to call the interface implementation layer by rule. For example, a domain layer needs to call a data mapper. +* Initial setup might be more complex. +* May lead to increased number of classes and interfaces in the codebase. -## Tutorial +## Related Java Design Patterns -* [Separated Interface Tutorial](https://www.youtube.com/watch?v=d3k-hOA7k2Y) +* [Adapter](https://java-design-patterns.com/patterns/adapter/): Adapts one interface to another, which can be used alongside Separated Interface to integrate different implementations. +* [Bridge](https://java-design-patterns.com/patterns/bridge/): Separates an object’s interface from its implementation, similar to Separated Interface but usually applied to larger-scale architectural issues. +* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): Often used to inject the implementation of a separated interface, promoting loose coupling. -## Credits +## References and Credits -* [Martin Fowler](https://www.martinfowler.com/eaaCatalog/separatedInterface.html) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=e08dfb7f2cf6153542ef1b5a00b10abc) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3xZ1ELU) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Separated Interface (Martin Fowler)](https://www.martinfowler.com/eaaCatalog/separatedInterface.html) diff --git a/separated-interface/pom.xml b/separated-interface/pom.xml index 1684ea1240fd..9dd5816e0414 100644 --- a/separated-interface/pom.xml +++ b/separated-interface/pom.xml @@ -34,6 +34,14 @@ separated-interface + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java index c50a5eb6cfd2..0f4cb00f845b 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java @@ -30,13 +30,13 @@ import lombok.extern.slf4j.Slf4j; /** - *

    The Separated Interface pattern encourages to separate the interface definition and + * The Separated Interface pattern encourages to separate the interface definition and * implementation in different packages. This allows the client to be completely unaware of the - * implementation.

    + * implementation. * *

    In this class the {@link InvoiceGenerator} class is injected with different instances of * {@link com.iluwatar.separatedinterface.invoice.TaxCalculator} implementations located in separate - * packages, to receive different responses for both of the implementations.

    + * packages, to receive different responses for both of the implementations. */ @Slf4j public class App { @@ -49,12 +49,12 @@ public class App { * @param args command line args */ public static void main(String[] args) { - //Create the invoice generator with product cost as 50 and foreign product tax - var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, - new ForeignTaxCalculator()); + // Create the invoice generator with product cost as 50 and foreign product tax + var internationalProductInvoice = + new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator()); LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax()); - //Create the invoice generator with product cost as 50 and domestic product tax + // Create the invoice generator with product cost as 50 and domestic product tax var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator()); LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax()); } diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java index cdb2b6c1d18d..a3e2c8396a6d 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java @@ -27,13 +27,11 @@ /** * InvoiceGenerator class generates an invoice, accepting the product cost and calculating the total * price payable inclusive tax (calculated by {@link TaxCalculator}). - * */ public record InvoiceGenerator(double amount, TaxCalculator taxCalculator) { - /** TaxCalculator description: - * The TaxCalculator interface to calculate the payable tax. - * Amount description: - * The base product amount without tax. + /** + * TaxCalculator description: The TaxCalculator interface to calculate the payable tax. Amount + * description: The base product amount without tax. */ public double getAmountWithTax() { return amount + taxCalculator.calculate(amount); diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java index 85b6e6786cfd..6132d0c48bd4 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java @@ -24,11 +24,8 @@ */ package com.iluwatar.separatedinterface.invoice; -/** - * TaxCalculator interface to demonstrate The Separated Interface pattern. - */ +/** TaxCalculator interface to demonstrate The Separated Interface pattern. */ public interface TaxCalculator { double calculate(double amount); - } diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java index cbf9146582c8..0b372a0e110d 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java @@ -26,9 +26,7 @@ import com.iluwatar.separatedinterface.invoice.TaxCalculator; -/** - * TaxCalculator for Domestic goods with 20% tax. - */ +/** TaxCalculator for Domestic goods with 20% tax. */ public class DomesticTaxCalculator implements TaxCalculator { public static final double TAX_PERCENTAGE = 20; @@ -37,5 +35,4 @@ public class DomesticTaxCalculator implements TaxCalculator { public double calculate(double amount) { return amount * TAX_PERCENTAGE / 100.0; } - } diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java index b4265a5d3792..0378c010837a 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java @@ -26,9 +26,7 @@ import com.iluwatar.separatedinterface.invoice.TaxCalculator; -/** - * TaxCalculator for foreign goods with 60% tax. - */ +/** TaxCalculator for foreign goods with 60% tax. */ public class ForeignTaxCalculator implements TaxCalculator { public static final double TAX_PERCENTAGE = 60; @@ -37,5 +35,4 @@ public class ForeignTaxCalculator implements TaxCalculator { public double calculate(double amount) { return amount * TAX_PERCENTAGE / 100.0; } - } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java index 995aabf1946d..245077b1b461 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.separatedinterface; -import org.junit.jupiter.api.Test; -import com.iluwatar.separatedinterface.App; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test. - */ +import org.junit.jupiter.api.Test; + +/** Application test. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java index 7772fc942b63..c0223ad258a8 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java @@ -48,5 +48,4 @@ void testGenerateTax() { Assertions.assertEquals(target.getAmountWithTax(), productCost + tax); verify(taxCalculatorMock, times(1)).calculate(productCost); } - } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java index be7261a2f98a..13974dc1dec9 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java @@ -38,5 +38,4 @@ void testTaxCalculation() { var tax = target.calculate(100.0); Assertions.assertEquals(tax, 20.0); } - } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java index 53ba5064d0d2..1e1441f22c34 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java @@ -38,5 +38,4 @@ void testTaxCalculation() { var tax = target.calculate(100.0); Assertions.assertEquals(tax, 60.0); } - } diff --git a/serialized-entity/README.md b/serialized-entity/README.md index d18a2c83e9ad..f7306dcdfa50 100644 --- a/serialized-entity/README.md +++ b/serialized-entity/README.md @@ -1,24 +1,42 @@ --- -title: Serialized Entity Pattern -categories: Architectural +title: "Serialized Entity Pattern in Java: Streamlining Data Persistence and Exchange" +shortTitle: Serialized Entity +description: "Explore the Serialized Entity design pattern in Java for efficient object serialization and storage. Learn how this pattern facilitates easy data transfer and persistence across different systems." +categories: Data access language: en tag: - - Data access + - Data access + - Data transfer + - Persistence --- +## Also known as -## Intent +* Object Serialization -To easily persist Java objects to the database. +## Intent of Serialized Entity Design Pattern -## Explanation -Java serialization allow us to convert the object to a set of bytes. We can store these bytes into database as -BLOB(binary long objects) and read them at any time and reconstruct them into Java objects. +The Serialized Entity pattern in Java allows for the object serialization to simplify data transfer and storage. It enables easy conversion of Java objects to and from a serialized format, allowing them to be efficiently stored and transferred. +## Detailed Explanation of Serialized Entity Pattern with Real-World Examples -**Programmatic Example** +Real-world example -Walking through our customers example, here's the basic `Customer` entity. +> An example of the Serialized Entity design pattern in the real world can be found in the process of saving and loading game state in video games. When a player decides to save their progress, the current state of the game, including the player's position, inventory, and achievements, is serialized into a file format such as JSON or binary. This file can then be stored on the player's device or on a cloud server. When the player wants to resume their game, the serialized data is deserialized back into the game's objects, allowing the player to continue from where they left off. This mechanism ensures that complex game states can be easily saved and restored, providing a seamless gaming experience. + +In plain words + +> The Serialized Entity design pattern enables the conversion of Java objects to and from a serialized format for easy storage and transfer. + +Wikipedia says + +> In computing, serialization is the process of translating a data structure or object state into a format that can be stored (e.g. files in secondary storage devices, data buffers in primary storage devices) or transmitted (e.g. data streams over computer networks) and reconstructed later (possibly in a different computer environment). When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object. For many complex objects, such as those that make extensive use of references, this process is not straightforward. Serialization of objects does not include any of their associated methods with which they were previously linked. + +## Programmatic Example of Serialized Entity Pattern in Java + +The Serialized Entity design pattern is a way to easily persist Java objects to the database. It uses the `Serializable` interface and the DAO (Data Access Object) pattern. The pattern first uses `Serializable` to convert a Java object into a set of bytes, then it uses the DAO pattern to store this set of bytes as a BLOB (Binary Large OBject) in the database. + +First, we have the `Country` class, which is a simple POJO (Plain Old Java Object) that represents the data that will be serialized and stored in the database. Implementing the Java `Serializable` interface is crucial in the Serialized Entity design pattern, which means that the object can be converted to a byte stream and restored from it. ```java @Getter @@ -32,190 +50,156 @@ public class Country implements Serializable { private String name; private String continents; private String language; - public static final long serialVersionUID = 7149851; - // Constructor -> - // getters and setters -> + @Serial + private static final long serialVersionUID = 7149851; } - ``` -Here is `CountrySchemaSql`, this class have method allow us to serialize `Country` object and insert it into the -database, also have a method that read serialized data from the database and deserialize it to `Country` object. -```java -@Slf4j -public class CountrySchemaSql { - public static final String CREATE_SCHEMA_SQL = "CREATE TABLE IF NOT EXISTS WORLD (ID INT PRIMARY KEY, COUNTRY BLOB)"; - - public static final String DELETE_SCHEMA_SQL = "DROP TABLE WORLD IF EXISTS"; - - private Country country; - private DataSource dataSource; - - /** - * Public constructor. - * - * @param dataSource datasource - * @param country country - */ - public CountrySchemaSql(Country country, DataSource dataSource) { - this.country = new Country( - country.getCode(), - country.getName(), - country.getContinents(), - country.getLanguage() - ); - this.dataSource = dataSource; - } - - /** - * This method will serialize a Country object and store it to database. - * @return int type, if successfully insert a serialized object to database then return country code, else return -1. - * @throws IOException if any. - */ - public int insertCountry() throws IOException { - var sql = "INSERT INTO WORLD (ID, COUNTRY) VALUES (?, ?)"; - try (var connection = dataSource.getConnection(); - var preparedStatement = connection.prepareStatement(sql); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oss = new ObjectOutputStream(baos)) { - - oss.writeObject(country); - oss.flush(); - - preparedStatement.setInt(1, country.getCode()); - preparedStatement.setBlob(2, new ByteArrayInputStream(baos.toByteArray())); - preparedStatement.execute(); - return country.getCode(); - } catch (SQLException e) { - LOGGER.info("Exception thrown " + e.getMessage()); - } - return -1; - } - - /** - * This method will select a data item from database and deserialize it. - * @return int type, if successfully select and deserialized object from database then return country code, - * else return -1. - * @throws IOException if any. - * @throws ClassNotFoundException if any. - */ - public int selectCountry() throws IOException, ClassNotFoundException { - var sql = "SELECT ID, COUNTRY FROM WORLD WHERE ID = ?"; - try (var connection = dataSource.getConnection(); - var preparedStatement = connection.prepareStatement(sql)) { - - preparedStatement.setInt(1, country.getCode()); - - try (ResultSet rs = preparedStatement.executeQuery()) { - if (rs.next()) { - Blob countryBlob = rs.getBlob("country"); - ByteArrayInputStream baos = new ByteArrayInputStream(countryBlob.getBytes(1, (int) countryBlob.length())); - ObjectInputStream ois = new ObjectInputStream(baos); - country = (Country) ois.readObject(); - LOGGER.info("Country: " + country); - } - return rs.getInt("id"); - } - } catch (SQLException e) { - LOGGER.info("Exception thrown " + e.getMessage()); - } - return -1; - } +Next, we have the `CountryDao` interface, which defines the methods for persisting and retrieving `Country` objects from the database. +```java +public interface CountryDao { + int insertCountry() throws IOException; + int selectCountry() throws IOException, ClassNotFoundException; } ``` -This `App.java` will first delete a World table from the h2 database(if there is one) and create a new table called -`WORLD` to ensure we have a table we want. -It will then create two `Country` objects called `China` and `UnitedArabEmirates`, then create two `CountrySchemaSql` -objects and use objects `China` and `UnitedArabEmirates` as arguments. -Finally, call `SerializedChina.insertCountry()` and `serializedUnitedArabEmirates.insertCountry()` to serialize and -store them to database, and call `SerializedChina.selectCountry()` and `serializedUnitedArabEmirates.selectCountry()` -methods to read and deserialize data items to `Country` objects. +The `CountrySchemaSql` class implements the `CountryDao` interface. It uses a `DataSource` to connect to the database and a `Country` object that it will serialize and store in the database. ```java @Slf4j -public class App { - private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; - - private App() { - - } - - /** - * Program entry point. - * @param args command line args. - * @throws IOException if any - * @throws ClassNotFoundException if any - */ - public static void main(String[] args) throws IOException, ClassNotFoundException { - final var dataSource = createDataSource(); - - deleteSchema(dataSource); - createSchema(dataSource); - - final var China = new Country( - 86, - "China", - "Asia", - "Chinese" +public class CountrySchemaSql implements CountryDao { + + public static final String CREATE_SCHEMA_SQL = "CREATE TABLE IF NOT EXISTS WORLD (ID INT PRIMARY KEY, COUNTRY BLOB)"; + public static final String DELETE_SCHEMA_SQL = "DROP TABLE WORLD IF EXISTS"; + + private Country country; + private DataSource dataSource; + + public CountrySchemaSql(Country country, DataSource dataSource) { + this.country = new Country( + country.getCode(), + country.getName(), + country.getContinents(), + country.getLanguage() ); - - final var UnitedArabEmirates = new Country( - 971, - "United Arab Emirates", - "Asia", - "Arabic" - ); - - final var serializedChina = new CountrySchemaSql(China, dataSource); - final var serializedUnitedArabEmirates = new CountrySchemaSql(UnitedArabEmirates, dataSource); - serializedChina.insertCountry(); - serializedUnitedArabEmirates.insertCountry(); - - serializedChina.selectCountry(); - serializedUnitedArabEmirates.selectCountry(); + this.dataSource = dataSource; } - private static void deleteSchema(DataSource dataSource) { + @Override + public int insertCountry() throws IOException { + var sql = "INSERT INTO WORLD (ID, COUNTRY) VALUES (?, ?)"; try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { - statement.execute(CountrySchemaSql.DELETE_SCHEMA_SQL); + var preparedStatement = connection.prepareStatement(sql); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oss = new ObjectOutputStream(baos)) { + + oss.writeObject(country); + oss.flush(); + + preparedStatement.setInt(1, country.getCode()); + preparedStatement.setBlob(2, new ByteArrayInputStream(baos.toByteArray())); + preparedStatement.execute(); + return country.getCode(); } catch (SQLException e) { LOGGER.info("Exception thrown " + e.getMessage()); } + return -1; } - private static void createSchema(DataSource dataSource) { + @Override + public int selectCountry() throws IOException, ClassNotFoundException { + var sql = "SELECT ID, COUNTRY FROM WORLD WHERE ID = ?"; try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { - statement.execute(CountrySchemaSql.CREATE_SCHEMA_SQL); + var preparedStatement = connection.prepareStatement(sql)) { + + preparedStatement.setInt(1, country.getCode()); + + try (ResultSet rs = preparedStatement.executeQuery()) { + if (rs.next()) { + Blob countryBlob = rs.getBlob("country"); + ByteArrayInputStream baos = new ByteArrayInputStream(countryBlob.getBytes(1, (int) countryBlob.length())); + ObjectInputStream ois = new ObjectInputStream(baos); + country = (Country) ois.readObject(); + LOGGER.info("Country: " + country); + } + return rs.getInt("id"); + } } catch (SQLException e) { LOGGER.info("Exception thrown " + e.getMessage()); } + return -1; } +} +``` - private static DataSource createDataSource() { - var dataSource = new JdbcDataSource(); - dataSource.setURL(DB_URL); - return dataSource; - } +Finally, in the `App` class, we create `Country` objects and `CountrySchemaSql` objects. We then call the `insertCountry` method to serialize the `Country` objects and store them in the database. We also call the `selectCountry` method to retrieve the serialized `Country` objects from the database and deserialize them back into `Country` objects. + +```java +public static void main(String[] args) throws IOException, ClassNotFoundException { + final var dataSource = createDataSource(); + + deleteSchema(dataSource); + createSchema(dataSource); + + final var China = new Country(86, "China", "Asia", "Chinese"); + final var UnitedArabEmirates = new Country(971, "United Arab Emirates", "Asia", "Arabic"); + + final var serializedChina = new CountrySchemaSql(China, dataSource); + final var serializedUnitedArabEmirates = new CountrySchemaSql(UnitedArabEmirates, dataSource); + + serializedChina.insertCountry(); + serializedUnitedArabEmirates.insertCountry(); + + serializedChina.selectCountry(); + serializedUnitedArabEmirates.selectCountry(); } ``` -## Class diagram -![alt_text](./etc/class_diagram.urm.png "Serialized Entity") +Console output: + +``` +11:55:32.842 [main] INFO com.iluwatar.serializedentity.CountrySchemaSql -- Country: Country(code=86, name=China, continents=Asia, language=Chinese) +11:55:32.870 [main] INFO com.iluwatar.serializedentity.CountrySchemaSql -- Country: Country(code=971, name=United Arab Emirates, continents=Asia, language=Arabic) +``` + +This is a basic example of the Serialized Entity design pattern. It shows how to serialize Java objects, store them in a database, and then retrieve and deserialize them. + +## When to Use the Serialized Entity Pattern in Java + +* This pattern is especially useful for applications requiring data persistence across various states in Java environments. +* Useful in scenarios where objects need to be shared over a network or saved to a file. + +## Real-World Applications of Serialized Entity Pattern in Java + +* Java's built-in serialization mechanism using `Serializable` interface. +* Storing session data in web applications. +* Caching objects to improve performance. +* Transferring objects in distributed systems using RMI or other RPC mechanisms. + +## Benefits and Trade-offs of Serialized Entity Pattern + +Benefits: + +* Simplifies the process of saving and restoring object state. +* Facilitates object transfer across different parts of a system. +* Reduces boilerplate code for manual serialization and deserialization. -## Applicability +Trade-offs: -Use the Serialized Entity pattern when +* Can introduce security risks if not handled properly (e.g., deserialization attacks). +* Serialized formats may not be easily readable or editable by humans. +* Changes to the class structure may break compatibility with previously serialized data. -* You want to easily persist Java objects to the database. +## Related Java Design Patterns -## Related patterns -[Data Access Object](https://github.com/iluwatar/java-design-patterns/tree/master/dao) +* [Data Transfer Object (DTO)](https://java-design-patterns.com/patterns/data-transfer-object/): Used to encapsulate data and send it over the network. Often serialized for transmission. +* [Memento](https://java-design-patterns.com/patterns/memento/): Provides a way to capture and restore an object's state, often using serialization. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Proxies can serialize requests to interact with remote objects. -## Credits +## References and Credits -* [J2EE Design Patterns by William Crawford, Jonathan Kaplan](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re21.html) -* [komminen](https://github.com/komminen/java-design-patterns) (His attempts of serialized-entity inspired me and learned a lot from his code) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Java Concurrency in Practice](https://amzn.to/4aRMruW) diff --git a/serialized-entity/pom.xml b/serialized-entity/pom.xml index 754089acf182..928e28097d73 100644 --- a/serialized-entity/pom.xml +++ b/serialized-entity/pom.xml @@ -1,4 +1,30 @@ + 4.0.0 @@ -8,6 +34,14 @@ serialized-entity + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java index 7f1f46905875..c4cab51dacb1 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java @@ -30,32 +30,33 @@ import lombok.extern.slf4j.Slf4j; import org.h2.jdbcx.JdbcDataSource; - /** * Serialized Entity Pattern. * - *

    Serialized Entity Pattern allow us to easily persist Java objects to the database. It uses Serializable interface - * and DAO pattern. Serialized Entity Pattern will first use Serializable to convert a Java object into a set of bytes, - * then it will using DAO pattern to store this set of bytes as BLOB to database.

    - * - *

    In this example, we first initialize two Java objects (Country) "China" and "UnitedArabEmirates", then we - * initialize "serializedChina" with "China" object and "serializedUnitedArabEmirates" with "UnitedArabEmirates", - * then we use method "serializedChina.insertCountry()" and "serializedUnitedArabEmirates.insertCountry()" to serialize - * "China" and "UnitedArabEmirates" and persist them to database. - * Last, with "serializedChina.selectCountry()" and "serializedUnitedArabEmirates.selectCountry()" we could read "China" - * and "UnitedArabEmirates" from database as sets of bytes, then deserialize them back to Java object (Country).

    + *

    Serialized Entity Pattern allow us to easily persist Java objects to the database. It uses + * Serializable interface and DAO pattern. Serialized Entity Pattern will first use Serializable to + * convert a Java object into a set of bytes, then it will using DAO pattern to store this set of + * bytes as BLOB to database. * + *

    In this example, we first initialize two Java objects (Country) "China" and + * "UnitedArabEmirates", then we initialize "serializedChina" with "China" object and + * "serializedUnitedArabEmirates" with "UnitedArabEmirates", then we use method + * "serializedChina.insertCountry()" and "serializedUnitedArabEmirates.insertCountry()" to serialize + * "China" and "UnitedArabEmirates" and persist them to database. Last, with + * "serializedChina.selectCountry()" and "serializedUnitedArabEmirates.selectCountry()" we could + * read "China" and "UnitedArabEmirates" from database as sets of bytes, then deserialize them back + * to Java object (Country). */ @Slf4j public class App { - private static final String DB_URL = "jdbc:h2:mem:testdb"; - private App() { + private static final String DB_URL = "jdbc:h2:~/testdb"; - } + private App() {} /** * Program entry point. + * * @param args command line args. * @throws IOException if any * @throws ClassNotFoundException if any @@ -67,20 +68,10 @@ public static void main(String[] args) throws IOException, ClassNotFoundExceptio createSchema(dataSource); // Initializing Country Object China - final var China = new Country( - 86, - "China", - "Asia", - "Chinese" - ); + final var China = new Country(86, "China", "Asia", "Chinese"); // Initializing Country Object UnitedArabEmirates - final var UnitedArabEmirates = new Country( - 971, - "United Arab Emirates", - "Asia", - "Arabic" - ); + final var UnitedArabEmirates = new Country(971, "United Arab Emirates", "Asia", "Arabic"); // Initializing CountrySchemaSql Object with parameter "China" and "dataSource" final var serializedChina = new CountrySchemaSql(China, dataSource); @@ -106,7 +97,7 @@ public static void main(String[] args) throws IOException, ClassNotFoundExceptio private static void deleteSchema(DataSource dataSource) { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CountrySchemaSql.DELETE_SCHEMA_SQL); } catch (SQLException e) { LOGGER.info("Exception thrown " + e.getMessage()); @@ -115,7 +106,7 @@ private static void deleteSchema(DataSource dataSource) { private static void createSchema(DataSource dataSource) { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CountrySchemaSql.CREATE_SCHEMA_SQL); } catch (SQLException e) { LOGGER.info("Exception thrown " + e.getMessage()); @@ -127,4 +118,4 @@ private static DataSource createDataSource() { dataSource.setURL(DB_URL); return dataSource; } -} \ No newline at end of file +} diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java index 6ef08e83b8d6..b406ab62f88b 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java @@ -23,6 +23,8 @@ * THE SOFTWARE. */ package com.iluwatar.serializedentity; + +import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -30,9 +32,7 @@ import lombok.Setter; import lombok.ToString; -/** - * A Country POJO that represents the data that will serialize and store in database. - */ +/** A Country POJO that represents the data that will serialize and store in database. */ @Getter @Setter @EqualsAndHashCode @@ -44,6 +44,5 @@ public class Country implements Serializable { private String name; private String continents; private String language; - public static final long serialVersionUID = 7149851; - + @Serial private static final long serialVersionUID = 7149851; } diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java index 2bd3a7305443..beffe1f4c6db 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + /** * In an application the Data Access Object (DAO) is a part of Data access layer. It is an object * that provides an interface to some type of persistence mechanism. By mapping application calls to @@ -41,10 +42,9 @@ import java.io.IOException; -/** - * DAO interface for Country transactions. - */ +/** DAO interface for Country transactions. */ public interface CountryDao { int insertCountry() throws IOException; + int selectCountry() throws IOException, ClassNotFoundException; } diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java index 971f875c6b81..2de487fa0308 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java @@ -35,12 +35,11 @@ import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; -/** - * Country Schema SQL Class. - */ +/** Country Schema SQL Class. */ @Slf4j public class CountrySchemaSql implements CountryDao { - public static final String CREATE_SCHEMA_SQL = "CREATE TABLE IF NOT EXISTS WORLD (ID INT PRIMARY KEY, COUNTRY BLOB)"; + public static final String CREATE_SCHEMA_SQL = + "CREATE TABLE IF NOT EXISTS WORLD (ID INT PRIMARY KEY, COUNTRY BLOB)"; public static final String DELETE_SCHEMA_SQL = "DROP TABLE WORLD IF EXISTS"; @@ -54,27 +53,26 @@ public class CountrySchemaSql implements CountryDao { * @param country country */ public CountrySchemaSql(Country country, DataSource dataSource) { - this.country = new Country( - country.getCode(), - country.getName(), - country.getContinents(), - country.getLanguage() - ); + this.country = + new Country( + country.getCode(), country.getName(), country.getContinents(), country.getLanguage()); this.dataSource = dataSource; } /** * This method will serialize a Country object and store it to database. - * @return int type, if successfully insert a serialized object to database then return country code, else return -1. + * + * @return int type, if successfully insert a serialized object to database then return country + * code, else return -1. * @throws IOException if any. */ @Override public int insertCountry() throws IOException { var sql = "INSERT INTO WORLD (ID, COUNTRY) VALUES (?, ?)"; try (var connection = dataSource.getConnection(); - var preparedStatement = connection.prepareStatement(sql); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oss = new ObjectOutputStream(baos)) { + var preparedStatement = connection.prepareStatement(sql); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oss = new ObjectOutputStream(baos)) { oss.writeObject(country); oss.flush(); @@ -91,8 +89,9 @@ public int insertCountry() throws IOException { /** * This method will select a data item from database and deserialize it. - * @return int type, if successfully select and deserialized object from database then return country code, - * else return -1. + * + * @return int type, if successfully select and deserialized object from database then return + * country code, else return -1. * @throws IOException if any. * @throws ClassNotFoundException if any. */ @@ -100,14 +99,15 @@ public int insertCountry() throws IOException { public int selectCountry() throws IOException, ClassNotFoundException { var sql = "SELECT ID, COUNTRY FROM WORLD WHERE ID = ?"; try (var connection = dataSource.getConnection(); - var preparedStatement = connection.prepareStatement(sql)) { + var preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setInt(1, country.getCode()); try (ResultSet rs = preparedStatement.executeQuery()) { if (rs.next()) { Blob countryBlob = rs.getBlob("country"); - ByteArrayInputStream baos = new ByteArrayInputStream(countryBlob.getBytes(1, (int) countryBlob.length())); + ByteArrayInputStream baos = + new ByteArrayInputStream(countryBlob.getBytes(1, (int) countryBlob.length())); ObjectInputStream ois = new ObjectInputStream(baos); country = (Country) ois.readObject(); LOGGER.info("Country: " + country); @@ -119,5 +119,4 @@ public int selectCountry() throws IOException, ClassNotFoundException { } return -1; } - } diff --git a/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java b/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java index a15557d5ffdb..ad6a9d6d18fe 100644 --- a/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java +++ b/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.serializedentity; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Serialized Entity example runs without errors. - */ -class AppTest { +import org.junit.jupiter.api.Test; - /** - * Issue: Add at least one assertion to this test case. - * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. - */ +/** Tests that Serialized Entity example runs without errors. */ +class AppTest { - @Test - void shouldExecuteSerializedEntityWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + /** + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link App#main(String[])} throws an exception. + */ + @Test + void shouldExecuteSerializedEntityWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java index 556aba1735ea..36d7baceb081 100644 --- a/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java +++ b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java @@ -23,81 +23,74 @@ * THE SOFTWARE. */ package com.iluwatar.serializedentity; -import org.junit.jupiter.api.Test; - -import java.io.*; import static org.junit.jupiter.api.Assertions.*; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +@Slf4j public class CountryTest { @Test void testGetMethod() { - Country China = new Country( - 86, - "China", - "Asia", - "Chinese" - ); + Country China = new Country(86, "China", "Asia", "Chinese"); - assertEquals(86, China.getCode()); - assertEquals("China", China.getName()); - assertEquals("Asia", China.getContinents()); - assertEquals("Chinese", China.getLanguage()); + assertEquals(86, China.getCode()); + assertEquals("China", China.getName()); + assertEquals("Asia", China.getContinents()); + assertEquals("Chinese", China.getLanguage()); } @Test void testSetMethod() { - Country country = new Country( - 86, - "China", - "Asia", - "Chinese" - ); + Country country = new Country(86, "China", "Asia", "Chinese"); - country.setCode(971); - country.setName("UAE"); - country.setContinents("West-Asia"); - country.setLanguage("Arabic"); + country.setCode(971); + country.setName("UAE"); + country.setContinents("West-Asia"); + country.setLanguage("Arabic"); - assertEquals(971, country.getCode()); - assertEquals("UAE", country.getName()); - assertEquals("West-Asia", country.getContinents()); - assertEquals("Arabic", country.getLanguage()); + assertEquals(971, country.getCode()); + assertEquals("UAE", country.getName()); + assertEquals("West-Asia", country.getContinents()); + assertEquals("Arabic", country.getLanguage()); } @Test - void testSerializable(){ - // Serializing Country - try { - Country country = new Country( - 86, - "China", - "Asia", - "Chinese"); - ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("output.txt")); - objectOutputStream.writeObject(country); - objectOutputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } + void testSerializable() { + // Serializing Country + try { + Country country = new Country(86, "China", "Asia", "Chinese"); + ObjectOutputStream objectOutputStream = + new ObjectOutputStream(new FileOutputStream("output.txt")); + objectOutputStream.writeObject(country); + objectOutputStream.close(); + } catch (IOException e) { + LOGGER.error("Error occurred: ", e); + } - // De-serialize Country - try { - ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("output.txt")); - Country country = (Country) objectInputStream.readObject(); - objectInputStream.close(); - System.out.println(country); + // De-serialize Country + try { + ObjectInputStream objectInputStream = + new ObjectInputStream(new FileInputStream("output.txt")); + Country country = (Country) objectInputStream.readObject(); + objectInputStream.close(); + System.out.println(country); - Country China = new Country( - 86, - "China", - "Asia", - "Chinese"); + Country China = new Country(86, "China", "Asia", "Chinese"); - assertEquals(China, country); - } catch (Exception e) { - e.printStackTrace(); - } + assertEquals(China, country); + } catch (Exception e) { + LOGGER.error("Error occurred: ", e); + } + try { + Files.deleteIfExists(Paths.get("output.txt")); + } catch (IOException e) { + LOGGER.error("Error occurred: ", e); + } } } diff --git a/serialized-lob/README.md b/serialized-lob/README.md new file mode 100644 index 000000000000..06eea05f4b08 --- /dev/null +++ b/serialized-lob/README.md @@ -0,0 +1,226 @@ +--- +title: "Serialized LOB Pattern in Java: Managing Large Data Objects with Ease" +shortTitle: Serialized LOB +description: "Explore the Serialized LOB pattern for managing large objects in Java applications. Learn how it simplifies data access and storage of files, multimedia, and large strings efficiently." +category: Data access +language: en +tag: + - Data access + - Data processing + - Persistence +--- + +## Also known as + +* Serialized Large Object +* Serialized BLOB +* Serialized CLOB + +## Intent of Serialized LOB Design Pattern + +Efficiently manage and store large data objects, such as multimedia files and extensive text strings, using the Serialized LOB pattern in Java, a strategy for robust database optimization. + +## Detailed Explanation of Serialized LOB Pattern with Real-World Examples + +Real-world example + +> Imagine a social media platform optimized for performance, where users can upload and seamlessly share multimedia content, leveraging Java's Serialized LOB pattern for enhanced data handling. Instead of storing these large multimedia files on a separate file server, the platform uses the Serialized LOB design pattern to store the files directly in the database. Each uploaded image or video is serialized into a binary large object (BLOB) and stored within the user's record. This approach ensures that the multimedia files are managed within the same transactional context as other user data, providing consistency and simplifying data access and retrieval. + +In plain words + +> The Serialized LOB design pattern manages the storage of large objects, such as files or multimedia, by serializing and storing them directly within a database. + +## Programmatic Example of Serialized LOB Pattern in Java + +The Serialized Large Object (LOB) design pattern is a way to handle large objects in a database. It involves serializing an object graph into a single large object (a BLOB or CLOB, for Binary Large Object or Character Large Object, respectively) and storing it in the database. When the object graph needs to be retrieved, it is read from the database and deserialized back into the original object graph. + +Here's a programmatic example of the Serialized LOB design pattern: + +```java +public abstract class LobSerializer implements AutoCloseable { + // ... omitted for brevity + public abstract Object serialize(Forest toSerialize) throws Exception; + public abstract Forest deSerialize(Object toDeserialize) throws Exception; + // ... omitted for brevity +} +``` + +The `LobSerializer` class is an abstract class that provides the structure for serializing and deserializing objects. It has two abstract methods: `serialize` and `deSerialize`. These methods are implemented by the subclasses `ClobSerializer` and `BlobSerializer`. + +```java +public class ClobSerializer extends LobSerializer { + // ... omitted for brevity + @Override + public Object serialize(Forest forest) throws ParserConfigurationException, TransformerException { + // ... omitted for brevity + } + + @Override + public Forest deSerialize(Object toDeserialize) + throws ParserConfigurationException, IOException, SAXException { + // ... omitted for brevity + } +} +``` + +The `ClobSerializer` class provides an implementation for serializing and deserializing objects into XML strings. The `serialize` method converts a `Forest` object into an XML string, and the `deSerialize` method converts an XML string back into a `Forest` object. + +```java +public class BlobSerializer extends LobSerializer { + // ... omitted for brevity + @Override + public Object serialize(Forest toSerialize) throws IOException { + // ... omitted for brevity + } + + @Override + public Forest deSerialize(Object toDeserialize) throws IOException, ClassNotFoundException { + // ... omitted for brevity + } +} +``` + +The `BlobSerializer` class provides an implementation for serializing and deserializing objects into binary data. The `serialize` method converts a `Forest` object into binary data, and the `deSerialize` method converts binary data back into a `Forest` object. + +Finally, here is the `App` class with `main` method that can be used to execute the serialization example. + +```java +public class App { + + public static final String CLOB = "CLOB"; + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) throws SQLException { + Forest forest = createForest(); + LobSerializer serializer = createLobSerializer(args); + executeSerializer(forest, serializer); + } + + private static LobSerializer createLobSerializer(String[] args) throws SQLException { + LobSerializer serializer; + if (args.length > 0 && Objects.equals(args[0], CLOB)) { + serializer = new ClobSerializer(); + } else { + serializer = new BlobSerializer(); + } + return serializer; + } + + private static Forest createForest() { + Plant grass = new Plant("Grass", "Herb"); + Plant oak = new Plant("Oak", "Tree"); + + Animal zebra = new Animal("Zebra", Set.of(grass), Collections.emptySet()); + Animal buffalo = new Animal("Buffalo", Set.of(grass), Collections.emptySet()); + Animal lion = new Animal("Lion", Collections.emptySet(), Set.of(zebra, buffalo)); + + return new Forest("Amazon", Set.of(lion, buffalo, zebra), Set.of(grass, oak)); + } + + private static void executeSerializer(Forest forest, LobSerializer lobSerializer) { + try (LobSerializer serializer = lobSerializer) { + + Object serialized = serializer.serialize(forest); + int id = serializer.persistToDb(1, forest.getName(), serialized); + + Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); + Forest forestFromDb = serializer.deSerialize(fromDb); + + LOGGER.info(forestFromDb.toString()); + } catch (SQLException | IOException | TransformerException | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} +``` + +Console output: + +``` +12:01:21.061 [main] INFO com.iluwatar.slob.App -- +Forest Name = Amazon +Animals found in the Amazon Forest: + +-------------------------- + +Animal Name = Lion + Animals Eaten by Lion: + +Animal Name = Buffalo + + Plants Eaten by Buffalo: + Name = Grass,Type = Herb + +Animal Name = Zebra + + Plants Eaten by Zebra: + Name = Grass,Type = Herb + +-------------------------- + +-------------------------- + +Animal Name = Buffalo + + Plants Eaten by Buffalo: + Name = Grass,Type = Herb +-------------------------- + +-------------------------- + +Animal Name = Zebra + + Plants Eaten by Zebra: + Name = Grass,Type = Herb +-------------------------- + +Plants in the Amazon Forest: + +-------------------------- +Name = Oak,Type = Tree +-------------------------- + +-------------------------- +Name = Grass,Type = Herb +-------------------------- +``` + +## When to Use the Serialized LOB Pattern in Java + +* Use when you need to store large objects in a database and want to optimize data access and storage. +* Ideal for applications that deal with large binary or character data such as multimedia files, logs, or documents. + +## Real-World Applications of Serialized LOB Pattern in Java + +* Storing and retrieving images or multimedia files in a database. +* Managing large text documents or logs in enterprise applications. +* Handling binary data in applications that require efficient data retrieval and storage. + +## Benefits and Trade-offs of Serialized LOB Pattern + +Benefits: + +* Simplifies the handling of large objects by leveraging Java serialization. +* Reduces the need for external file storage systems. +* Ensures consistency by storing LOBs within the same transactional context as other data. + +Trade-offs: + +* Increases database size due to the storage of serialized data. +* Potential performance overhead during serialization and deserialization. +* Requires careful management of serialization format to maintain backward compatibility. + +## Related Java Design Patterns + +* [DAO (Data Access Object)](https://java-design-patterns.com/patterns/dao/): Often used in conjunction with Serialized LOB to encapsulate data access logic. +* Active Record: Can use Serialized LOB for managing large data within the same record. +* [Repository](https://java-design-patterns.com/patterns/repository/): Uses Serialized LOB to handle complex queries and data manipulation involving large objects. + +## References and Credits + +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Serialized LOB (Martin Fowler)](https://martinfowler.com/eaaCatalog/serializedLOB.html) diff --git a/serialized-lob/etc/serialized-lob.urm.puml b/serialized-lob/etc/serialized-lob.urm.puml new file mode 100644 index 000000000000..3e0c4c2e31b1 --- /dev/null +++ b/serialized-lob/etc/serialized-lob.urm.puml @@ -0,0 +1,122 @@ +@startuml +package com.iluwatar.slob.lob { + class Animal { + - animalsEaten : Set + - name : String + - plantsEaten : Set + + Animal() + + Animal(name : String, plantsEaten : Set, animalsEaten : Set) + # canEqual(other : Object) : boolean + + createObjectFromXml(node : Node) + + equals(o : Object) : boolean + + getAnimalsEaten() : Set + + getName() : String + + getPlantsEaten() : Set + + hashCode() : int + # iterateXmlForAnimalAndPlants(childNodes : NodeList, animalsEaten : Set, plantsEaten : Set) {static} + + setAnimalsEaten(animalsEaten : Set) + + setName(name : String) + + setPlantsEaten(plantsEaten : Set) + + toString() : String + + toXmlElement(xmlDoc : Document) : Element + } + class Forest { + - animals : Set + - name : String + - plants : Set + + Forest() + + Forest(name : String, animals : Set, plants : Set) + # canEqual(other : Object) : boolean + + createObjectFromXml(document : Document) + + equals(o : Object) : boolean + + getAnimals() : Set + + getName() : String + + getPlants() : Set + - getXmlDoc() : Document + + hashCode() : int + + setAnimals(animals : Set) + + setName(name : String) + + setPlants(plants : Set) + + toString() : String + + toXmlElement() : Element + } + class Plant { + - name : String + - type : String + + Plant() + + Plant(name : String, type : String) + # canEqual(other : Object) : boolean + + createObjectFromXml(node : Node) + + equals(o : Object) : boolean + + getName() : String + + getType() : String + + hashCode() : int + + setName(name : String) + + setType(type : String) + + toString() : String + + toXmlElement(xmlDoc : Document) : Element + } +} +package com.iluwatar.slob.serializers { + class BlobSerializer { + + TYPE_OF_DATA_FOR_DB : String {static} + + BlobSerializer() + + deSerialize(toDeserialize : Object) : Forest + + serialize(toSerialize : Forest) : Object + } + class ClobSerializer { + + TYPE_OF_DATA_FOR_DB : String {static} + + ClobSerializer() + + deSerialize(toDeserialize : Object) : Forest + - elementToXmlString(node : Element) : String {static} + + serialize(forest : Forest) : Object + } + abstract class LobSerializer { + - databaseService : DatabaseService + # LobSerializer(dataTypeDb : String) + + close() + + deSerialize(Object) : Forest {abstract} + + loadFromDb(id : int, columnName : String) : Object + + persistToDb(id : int, name : String, object : Object) : int + + serialize(Forest) : Object {abstract} + } +} +package com.iluwatar.slob.dbservice { + class DatabaseService { + + BINARY_DATA : String {static} + + CREATE_BINARY_SCHEMA_DDL : String {static} + + CREATE_TEXT_SCHEMA_DDL : String {static} + - DB_URL : String {static} + + DELETE_SCHEMA_SQL : String {static} + - INSERT : String {static} + - LOGGER : Logger {static} + - SELECT : String {static} + - dataSource : DataSource {static} + + dataTypeDb : String + + DatabaseService(dataTypeDb : String) + - createDataSource() : DataSource {static} + + insert(id : int, name : String, data : Object) + + select(id : long, columnsName : String) : Object + + shutDownService() + + startupService() + } +} +package com.iluwatar.slob { + class App { + + CLOB : String {static} + - LOGGER : Logger {static} + + App() + - createForest() : Forest {static} + - createLobSerializer(args : String[]) : LobSerializer {static} + - executeSerializer(forest : Forest, lobSerializer : LobSerializer) {static} + + main(args : String[]) {static} + } +} +Animal --> "-plantsEaten" Plant +LobSerializer --> "-databaseService" DatabaseService +Forest --> "-animals" Animal +Forest --> "-plants" Plant +Animal --> "-animalsEaten" Animal +BlobSerializer --|> LobSerializer +ClobSerializer --|> LobSerializer +@enduml \ No newline at end of file diff --git a/serialized-lob/etc/slob.urm.png b/serialized-lob/etc/slob.urm.png new file mode 100644 index 000000000000..e852dfb21d08 Binary files /dev/null and b/serialized-lob/etc/slob.urm.png differ diff --git a/serialized-lob/etc/slob.urm.puml b/serialized-lob/etc/slob.urm.puml new file mode 100644 index 000000000000..3e0c4c2e31b1 --- /dev/null +++ b/serialized-lob/etc/slob.urm.puml @@ -0,0 +1,122 @@ +@startuml +package com.iluwatar.slob.lob { + class Animal { + - animalsEaten : Set + - name : String + - plantsEaten : Set + + Animal() + + Animal(name : String, plantsEaten : Set, animalsEaten : Set) + # canEqual(other : Object) : boolean + + createObjectFromXml(node : Node) + + equals(o : Object) : boolean + + getAnimalsEaten() : Set + + getName() : String + + getPlantsEaten() : Set + + hashCode() : int + # iterateXmlForAnimalAndPlants(childNodes : NodeList, animalsEaten : Set, plantsEaten : Set) {static} + + setAnimalsEaten(animalsEaten : Set) + + setName(name : String) + + setPlantsEaten(plantsEaten : Set) + + toString() : String + + toXmlElement(xmlDoc : Document) : Element + } + class Forest { + - animals : Set + - name : String + - plants : Set + + Forest() + + Forest(name : String, animals : Set, plants : Set) + # canEqual(other : Object) : boolean + + createObjectFromXml(document : Document) + + equals(o : Object) : boolean + + getAnimals() : Set + + getName() : String + + getPlants() : Set + - getXmlDoc() : Document + + hashCode() : int + + setAnimals(animals : Set) + + setName(name : String) + + setPlants(plants : Set) + + toString() : String + + toXmlElement() : Element + } + class Plant { + - name : String + - type : String + + Plant() + + Plant(name : String, type : String) + # canEqual(other : Object) : boolean + + createObjectFromXml(node : Node) + + equals(o : Object) : boolean + + getName() : String + + getType() : String + + hashCode() : int + + setName(name : String) + + setType(type : String) + + toString() : String + + toXmlElement(xmlDoc : Document) : Element + } +} +package com.iluwatar.slob.serializers { + class BlobSerializer { + + TYPE_OF_DATA_FOR_DB : String {static} + + BlobSerializer() + + deSerialize(toDeserialize : Object) : Forest + + serialize(toSerialize : Forest) : Object + } + class ClobSerializer { + + TYPE_OF_DATA_FOR_DB : String {static} + + ClobSerializer() + + deSerialize(toDeserialize : Object) : Forest + - elementToXmlString(node : Element) : String {static} + + serialize(forest : Forest) : Object + } + abstract class LobSerializer { + - databaseService : DatabaseService + # LobSerializer(dataTypeDb : String) + + close() + + deSerialize(Object) : Forest {abstract} + + loadFromDb(id : int, columnName : String) : Object + + persistToDb(id : int, name : String, object : Object) : int + + serialize(Forest) : Object {abstract} + } +} +package com.iluwatar.slob.dbservice { + class DatabaseService { + + BINARY_DATA : String {static} + + CREATE_BINARY_SCHEMA_DDL : String {static} + + CREATE_TEXT_SCHEMA_DDL : String {static} + - DB_URL : String {static} + + DELETE_SCHEMA_SQL : String {static} + - INSERT : String {static} + - LOGGER : Logger {static} + - SELECT : String {static} + - dataSource : DataSource {static} + + dataTypeDb : String + + DatabaseService(dataTypeDb : String) + - createDataSource() : DataSource {static} + + insert(id : int, name : String, data : Object) + + select(id : long, columnsName : String) : Object + + shutDownService() + + startupService() + } +} +package com.iluwatar.slob { + class App { + + CLOB : String {static} + - LOGGER : Logger {static} + + App() + - createForest() : Forest {static} + - createLobSerializer(args : String[]) : LobSerializer {static} + - executeSerializer(forest : Forest, lobSerializer : LobSerializer) {static} + + main(args : String[]) {static} + } +} +Animal --> "-plantsEaten" Plant +LobSerializer --> "-databaseService" DatabaseService +Forest --> "-animals" Animal +Forest --> "-plants" Plant +Animal --> "-animalsEaten" Animal +BlobSerializer --|> LobSerializer +ClobSerializer --|> LobSerializer +@enduml \ No newline at end of file diff --git a/serialized-lob/etc/slob.urm.uml b/serialized-lob/etc/slob.urm.uml new file mode 100644 index 000000000000..1d9de1e46d3b --- /dev/null +++ b/serialized-lob/etc/slob.urm.uml @@ -0,0 +1,49 @@ +@startuml + +!theme plain +top to bottom direction +skinparam linetype ortho + +class Animal { + - animalsEaten: Set + - name: String + - plantsEaten: Set + animalsEaten: Set + name: String + plantsEaten: Set +} +class App +class BlobSerializer +class ClobSerializer +class DatabaseService +class Forest { + - animals: Set + - name: String + - plants: Set + animals: Set + name: String + plants: Set + xmlDoc: Document +} +class LobSerializer +class Plant { + - name: String + - type: String + name: String + type: String +} + +Animal -[#595959,dashed]-> Plant : "«create»" +Animal "1" *-[#595959,plain]-> "plantsEaten\n*" Plant +App -[#595959,dashed]-> Animal : "«create»" +App -[#595959,dashed]-> ClobSerializer : "«create»" +App -[#595959,dashed]-> Forest : "«create»" +App -[#595959,dashed]-> Plant : "«create»" +BlobSerializer -[#000082,plain]-^ LobSerializer +ClobSerializer -[#595959,dashed]-> Forest : "«create»" +ClobSerializer -[#000082,plain]-^ LobSerializer +Forest "1" *-[#595959,plain]-> "animals\n*" Animal +Forest "1" *-[#595959,plain]-> "plants\n*" Plant +LobSerializer "1" *-[#595959,plain]-> "databaseService\n1" DatabaseService +LobSerializer -[#595959,dashed]-> DatabaseService : "«create»" +@enduml diff --git a/serialized-lob/pom.xml b/serialized-lob/pom.xml new file mode 100644 index 000000000000..f705a848ad32 --- /dev/null +++ b/serialized-lob/pom.xml @@ -0,0 +1,80 @@ + + + + + serialized-lob + 4.0.0 + + java-design-patterns + com.iluwatar + 1.26.0-SNAPSHOT + + + + + + maven-assembly-plugin + + + + + + com.iluwatar.slob.App + + + + + + org.apache.maven.plugins + + + + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + junit-jupiter-engine + org.junit.jupiter + test + + + h2 + com.h2database + + + + diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/App.java b/serialized-lob/src/main/java/com/iluwatar/slob/App.java new file mode 100644 index 000000000000..25006726ea0c --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/App.java @@ -0,0 +1,146 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob; + +import com.iluwatar.slob.lob.Animal; +import com.iluwatar.slob.lob.Forest; +import com.iluwatar.slob.lob.Plant; +import com.iluwatar.slob.serializers.BlobSerializer; +import com.iluwatar.slob.serializers.ClobSerializer; +import com.iluwatar.slob.serializers.LobSerializer; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +/** SLOB Application using {@link LobSerializer} and H2 DB. */ +public class App { + + public static final String CLOB = "CLOB"; + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Main entry point to program. + * + *

    In the SLOB pattern, the object graph is serialized into a single large object (a BLOB or + * CLOB, for Binary Large Object or Character Large Object, respectively) and stored in the + * database. When the object graph needs to be retrieved, it is read from the database and + * deserialized back into the original object graph. + * + *

    A Forest is created using {@link #createForest()} with Animals and Plants along with their + * respective relationships. + * + *

    Creates a {@link LobSerializer} using the method {@link #createLobSerializer(String[])}. + * + *

    Once created the serializer is passed to the {@link #executeSerializer(Forest, + * LobSerializer)} which handles the serialization, deserialization and persisting and loading + * from DB. + * + * @param args if first arg is CLOB then ClobSerializer is used else BlobSerializer is used. + */ + public static void main(String[] args) throws SQLException { + Forest forest = createForest(); + LobSerializer serializer = createLobSerializer(args); + executeSerializer(forest, serializer); + } + + /** + * Creates a {@link LobSerializer} on the basis of input args. + * + *

    If input args are not empty and the value equals {@link App#CLOB} then a {@link + * ClobSerializer} is created else a {@link BlobSerializer} is created. + * + * @param args if first arg is {@link App#CLOB} then ClobSerializer is instantiated else + * BlobSerializer is instantiated. + */ + private static LobSerializer createLobSerializer(String[] args) throws SQLException { + LobSerializer serializer; + if (args.length > 0 && Objects.equals(args[0], CLOB)) { + serializer = new ClobSerializer(); + } else { + serializer = new BlobSerializer(); + } + return serializer; + } + + /** + * Creates a Forest with {@link Animal} and {@link Plant} along with their respective + * relationships. + * + *

    The method creates a {@link Forest} with 2 Plants Grass and Oak of type Herb and tree + * respectively. + * + *

    It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the + * Zebra and the Buffalo. + * + *

    With the above animals and plants and their relationships a forest object is created which + * represents the Object Graph. + * + * @return Forest Object + */ + private static Forest createForest() { + Plant grass = new Plant("Grass", "Herb"); + Plant oak = new Plant("Oak", "Tree"); + + Animal zebra = new Animal("Zebra", Set.of(grass), Collections.emptySet()); + Animal buffalo = new Animal("Buffalo", Set.of(grass), Collections.emptySet()); + Animal lion = new Animal("Lion", Collections.emptySet(), Set.of(zebra, buffalo)); + + return new Forest("Amazon", Set.of(lion, buffalo, zebra), Set.of(grass, oak)); + } + + /** + * Serialize the input object using the input serializer and persist to DB. After this it loads + * the same object back from DB and deserializes using the same serializer. + * + * @param forest Object to Serialize and Persist + * @param lobSerializer Serializer to Serialize and Deserialize Object + */ + private static void executeSerializer(Forest forest, LobSerializer lobSerializer) { + try (LobSerializer serializer = lobSerializer) { + + Object serialized = serializer.serialize(forest); + int id = serializer.persistToDb(1, forest.getName(), serialized); + + Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); + Forest forestFromDb = serializer.deSerialize(fromDb); + + LOGGER.info(forestFromDb.toString()); + } catch (SQLException + | IOException + | TransformerException + | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java b/serialized-lob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java new file mode 100644 index 000000000000..10f3b262d6b2 --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java @@ -0,0 +1,150 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.dbservice; + +import java.sql.ResultSet; +import java.sql.SQLException; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import org.h2.jdbcx.JdbcDataSource; + +/** Service to handle database operations. */ +@Slf4j +public class DatabaseService { + + public static final String CREATE_BINARY_SCHEMA_DDL = + "CREATE TABLE IF NOT EXISTS FORESTS (ID NUMBER UNIQUE, NAME VARCHAR(30),FOREST VARBINARY)"; + public static final String CREATE_TEXT_SCHEMA_DDL = + "CREATE TABLE IF NOT EXISTS FORESTS (ID NUMBER UNIQUE, NAME VARCHAR(30),FOREST VARCHAR)"; + public static final String DELETE_SCHEMA_SQL = "DROP TABLE FORESTS IF EXISTS"; + public static final String BINARY_DATA = "BINARY"; + private static final String DB_URL = "jdbc:h2:~/test"; + private static final String INSERT = "insert into FORESTS (id,name, forest) values (?,?,?)"; + private static final String SELECT = "select FOREST from FORESTS where id = ?"; + private static final DataSource dataSource = createDataSource(); + public String dataTypeDb; + + /** + * Constructor initializes {@link DatabaseService#dataTypeDb}. + * + * @param dataTypeDb Type of data that is to be stored in DB can be 'TEXT' or 'BINARY'. + */ + public DatabaseService(String dataTypeDb) { + this.dataTypeDb = dataTypeDb; + } + + /** + * Initiates Data source. + * + * @return created data source + */ + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + return dataSource; + } + + /** + * Shutdown Sequence executes Query {@link DatabaseService#DELETE_SCHEMA_SQL}. + * + * @throws SQLException if any issue occurs while executing DROP Query + */ + public void shutDownService() throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(DELETE_SCHEMA_SQL); + } + } + + /** + * Initaites startup sequence and executes the query {@link + * DatabaseService#CREATE_BINARY_SCHEMA_DDL} if {@link DatabaseService#dataTypeDb} is binary else + * will execute the query {@link DatabaseService#CREATE_TEXT_SCHEMA_DDL}. + * + * @throws SQLException if there are any issues during DDL execution + */ + public void startupService() throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + if (dataTypeDb.equals(BINARY_DATA)) { + statement.execute(CREATE_BINARY_SCHEMA_DDL); + } else { + statement.execute(CREATE_TEXT_SCHEMA_DDL); + } + } + } + + /** + * Executes the insert query {@link DatabaseService#INSERT}. + * + * @param id with which row is to be inserted + * @param name name to be added in the row + * @param data object data to be saved in the row + * @throws SQLException if there are any issues in executing insert query {@link + * DatabaseService#INSERT} + */ + public void insert(int id, String name, Object data) throws SQLException { + try (var connection = dataSource.getConnection(); + var insert = connection.prepareStatement(INSERT)) { + insert.setInt(1, id); + insert.setString(2, name); + insert.setObject(3, data); + insert.execute(); + } + } + + /** + * Runs the select query {@link DatabaseService#SELECT} form the result set returns an {@link + * java.io.InputStream} if {@link DatabaseService#dataTypeDb} is 'binary' else will return the + * object as a {@link String}. + * + * @param id with which row is to be selected + * @param columnsName column in which the object is stored + * @return object found from DB + * @throws SQLException if there are any issues in executing insert query * {@link + * DatabaseService#SELECT} + */ + public Object select(final long id, String columnsName) throws SQLException { + ResultSet resultSet = null; + try (var connection = dataSource.getConnection(); + var preparedStatement = connection.prepareStatement(SELECT)) { + Object result = null; + preparedStatement.setLong(1, id); + resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + if (dataTypeDb.equals(BINARY_DATA)) { + result = resultSet.getBinaryStream(columnsName); + } else { + result = resultSet.getString(columnsName); + } + } + return result; + } finally { + if (resultSet != null) { + resultSet.close(); + } + } + } +} diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Animal.java b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Animal.java new file mode 100644 index 000000000000..17caa41e96aa --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Animal.java @@ -0,0 +1,129 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.lob; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** Creates an object Animal with a list of animals and/or plants it consumes. */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Animal implements Serializable { + + private String name; + private Set plantsEaten = new HashSet<>(); + private Set animalsEaten = new HashSet<>(); + + /** + * Iterates over the input nodes recursively and adds new plants to {@link Animal#plantsEaten} or + * animals to {@link Animal#animalsEaten} found to input sets respectively. + * + * @param childNodes contains the XML Node containing the Forest + * @param animalsEaten set of Animals eaten + * @param plantsEaten set of Plants eaten + */ + protected static void iterateXmlForAnimalAndPlants( + NodeList childNodes, Set animalsEaten, Set plantsEaten) { + for (int i = 0; i < childNodes.getLength(); i++) { + Node child = childNodes.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + if (child.getNodeName().equals(Animal.class.getSimpleName())) { + Animal animalEaten = new Animal(); + animalEaten.createObjectFromXml(child); + animalsEaten.add(animalEaten); + } else if (child.getNodeName().equals(Plant.class.getSimpleName())) { + Plant plant = new Plant(); + plant.createObjectFromXml(child); + plantsEaten.add(plant); + } + } + } + } + + /** + * Provides XML Representation of the Animal. + * + * @param xmlDoc object to which the XML representation is to be written to + * @return XML Element contain the Animal representation + */ + public Element toXmlElement(Document xmlDoc) { + Element root = xmlDoc.createElement(Animal.class.getSimpleName()); + root.setAttribute("name", name); + for (Plant plant : plantsEaten) { + Element xmlElement = plant.toXmlElement(xmlDoc); + if (xmlElement != null) { + root.appendChild(xmlElement); + } + } + for (Animal animal : animalsEaten) { + Element xmlElement = animal.toXmlElement(xmlDoc); + if (xmlElement != null) { + root.appendChild(xmlElement); + } + } + xmlDoc.appendChild(root); + return (Element) xmlDoc.getFirstChild(); + } + + /** + * Parses the Animal Object from the input XML Node. + * + * @param node the XML Node from which the Animal Object is to be parsed + */ + public void createObjectFromXml(Node node) { + name = node.getAttributes().getNamedItem("name").getNodeValue(); + NodeList childNodes = node.getChildNodes(); + iterateXmlForAnimalAndPlants(childNodes, animalsEaten, plantsEaten); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("\nAnimal Name = ").append(name); + if (!animalsEaten.isEmpty()) { + sb.append("\n\tAnimals Eaten by ").append(name).append(": "); + } + for (Animal animal : animalsEaten) { + sb.append("\n\t\t").append(animal); + } + sb.append("\n"); + if (!plantsEaten.isEmpty()) { + sb.append("\n\tPlants Eaten by ").append(name).append(": "); + } + for (Plant plant : plantsEaten) { + sb.append("\n\t\t").append(plant); + } + return sb.toString(); + } +} diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Forest.java b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Forest.java new file mode 100644 index 000000000000..844e3c6bd84f --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Forest.java @@ -0,0 +1,121 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.lob; + +import static com.iluwatar.slob.lob.Animal.iterateXmlForAnimalAndPlants; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * Creates an object Forest which contains animals and plants as its constituents. Animals may eat + * plants or other animals in the forest. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Forest implements Serializable { + public static final String HORIZONTAL_DIVIDER = "\n--------------------------\n"; + private String name; + private Set animals = new HashSet<>(); + private Set plants = new HashSet<>(); + + /** + * Provides the representation of Forest in XML form. + * + * @return XML Element + */ + public Element toXmlElement() throws ParserConfigurationException { + Document xmlDoc = getXmlDoc(); + + Element forestXml = xmlDoc.createElement("Forest"); + forestXml.setAttribute("name", name); + + Element animalsXml = xmlDoc.createElement("Animals"); + for (Animal animal : animals) { + Element animalXml = animal.toXmlElement(xmlDoc); + animalsXml.appendChild(animalXml); + } + forestXml.appendChild(animalsXml); + + Element plantsXml = xmlDoc.createElement("Plants"); + for (Plant plant : plants) { + Element plantXml = plant.toXmlElement(xmlDoc); + plantsXml.appendChild(plantXml); + } + forestXml.appendChild(plantsXml); + return forestXml; + } + + /** + * Returns XMLDoc to use for XML creation. + * + * @return XML DOC Object + * @throws ParserConfigurationException {@inheritDoc} + */ + private Document getXmlDoc() throws ParserConfigurationException { + return DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().newDocument(); + } + + /** + * Parses the Forest Object from the input XML Document. + * + * @param document the XML document from which the Forest is to be parsed + */ + public void createObjectFromXml(Document document) { + name = document.getDocumentElement().getAttribute("name"); + NodeList nodeList = document.getElementsByTagName("*"); + iterateXmlForAnimalAndPlants(nodeList, animals, plants); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("\n"); + sb.append("Forest Name = ").append(name).append("\n"); + sb.append("Animals found in the ").append(name).append(" Forest: \n"); + for (Animal animal : animals) { + sb.append(HORIZONTAL_DIVIDER); + sb.append(animal.toString()); + sb.append(HORIZONTAL_DIVIDER); + } + sb.append("\n"); + sb.append("Plants in the ").append(name).append(" Forest: \n"); + for (Plant plant : plants) { + sb.append(HORIZONTAL_DIVIDER); + sb.append(plant.toString()); + sb.append(HORIZONTAL_DIVIDER); + } + return sb.toString(); + } +} diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Plant.java b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Plant.java new file mode 100644 index 000000000000..f41a8b67c525 --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Plant.java @@ -0,0 +1,78 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.lob; + +import java.io.Serializable; +import java.util.StringJoiner; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +/** Creates an object Plant which contains its name and type. */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Plant implements Serializable { + + private String name; + private String type; + + /** + * Provides XML Representation of the Plant. + * + * @param xmlDoc to which the XML representation is to be written to + * @return XML Element contain the Animal representation + */ + public Element toXmlElement(Document xmlDoc) { + Element root = xmlDoc.createElement(Plant.class.getSimpleName()); + root.setAttribute("name", name); + root.setAttribute("type", type); + xmlDoc.appendChild(root); + return xmlDoc.getDocumentElement(); + } + + /** + * Parses the Plant Object from the input XML Node. + * + * @param node the XML Node from which the Animal Object is to be parsed + */ + public void createObjectFromXml(Node node) { + NamedNodeMap attributes = node.getAttributes(); + name = attributes.getNamedItem("name").getNodeValue(); + type = attributes.getNamedItem("type").getNodeValue(); + } + + @Override + public String toString() { + StringJoiner stringJoiner = new StringJoiner(","); + stringJoiner.add("Name = " + name); + stringJoiner.add("Type = " + type); + return stringJoiner.toString(); + } +} diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java new file mode 100644 index 000000000000..f9fe3a7a7e70 --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java @@ -0,0 +1,83 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.serializers; + +import com.iluwatar.slob.lob.Forest; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.sql.SQLException; + +/** + * Creates a Serializer that uses Binary serialization and deserialization of objects graph to and + * from their Binary Representation. + */ +public class BlobSerializer extends LobSerializer { + + public static final String TYPE_OF_DATA_FOR_DB = "BINARY"; + + public BlobSerializer() throws SQLException { + super(TYPE_OF_DATA_FOR_DB); + } + + /** + * Serializes the input object graph to its Binary Representation using Object Stream. + * + * @param toSerialize Object which is to be serialized + * @return Serialized object + * @throws IOException {@inheritDoc} + */ + @Override + public Object serialize(Forest toSerialize) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(toSerialize); + oos.close(); + return new ByteArrayInputStream(baos.toByteArray()); + } + + /** + * Deserializes the input Byte Array Stream using Object Stream and return its Object Graph + * Representation. + * + * @param toDeserialize Input Object to De-serialize + * @return Deserialized Object + * @throws ClassNotFoundException {@inheritDoc} + * @throws IOException {@inheritDoc} + */ + @Override + public Forest deSerialize(Object toDeserialize) throws IOException, ClassNotFoundException { + InputStream bis = (InputStream) toDeserialize; + Forest forest; + try (ObjectInput in = new ObjectInputStream(bis)) { + forest = (Forest) in.readObject(); + } + return forest; + } +} diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java new file mode 100644 index 000000000000..827477c301b7 --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java @@ -0,0 +1,107 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.serializers; + +import com.iluwatar.slob.lob.Forest; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.sql.SQLException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +/** + * Creates a Serializer that uses Character based serialization and deserialization of objects graph + * to and from XML Representation. + */ +public class ClobSerializer extends LobSerializer { + + public static final String TYPE_OF_DATA_FOR_DB = "TEXT"; + + public ClobSerializer() throws SQLException { + super(TYPE_OF_DATA_FOR_DB); + } + + /** + * Converts the input node to its XML String Representation. + * + * @param node XML Node that is to be converted to string + * @return String representation of XML parsed from the Node + * @throws TransformerException If any issues occur in Transformation from Node to XML + */ + private static String elementToXmlString(Element node) throws TransformerException { + StringWriter sw = new StringWriter(); + Transformer t = TransformerFactory.newDefaultInstance().newTransformer(); + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + t.setOutputProperty(OutputKeys.INDENT, "yes"); + t.transform(new DOMSource(node), new StreamResult(sw)); + return sw.toString(); + } + + /** + * Serializes the input object graph to its XML Representation using DOM Elements. + * + * @param forest Object which is to be serialized + * @return Serialized object + * @throws ParserConfigurationException If any issues occur in parsing input object + * @throws TransformerException If any issues occur in Transformation from Node to XML + */ + @Override + public Object serialize(Forest forest) throws ParserConfigurationException, TransformerException { + Element xmlElement = forest.toXmlElement(); + return elementToXmlString(xmlElement); + } + + /** + * Deserializes the input XML string using DOM Parser and return its Object Graph Representation. + * + * @param toDeserialize Input Object to De-serialize + * @return Deserialized Object + * @throws ParserConfigurationException If any issues occur in parsing input object + * @throws IOException if any issues occur during reading object + * @throws SAXException If any issues occur in Transformation from Node to XML + */ + @Override + public Forest deSerialize(Object toDeserialize) + throws ParserConfigurationException, IOException, SAXException { + DocumentBuilder documentBuilder = + DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder(); + var stream = new ByteArrayInputStream(toDeserialize.toString().getBytes()); + Document parsed = documentBuilder.parse(stream); + Forest forest = new Forest(); + forest.createObjectFromXml(parsed); + return forest; + } +} diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java new file mode 100644 index 000000000000..c97246f332e4 --- /dev/null +++ b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java @@ -0,0 +1,115 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.serializers; + +import com.iluwatar.slob.dbservice.DatabaseService; +import com.iluwatar.slob.lob.Forest; +import java.io.Closeable; +import java.io.IOException; +import java.io.Serializable; +import java.sql.SQLException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import org.xml.sax.SAXException; + +/** + * A LobSerializer can be used to create an instance of a serializer which can serialize and + * deserialize an object and persist and load that object into a DB. from their Binary + * Representation. + */ +public abstract class LobSerializer implements Serializable, Closeable { + + private final transient DatabaseService databaseService; + + /** + * Constructor initializes {@link LobSerializer#databaseService}. + * + * @param dataTypeDb Input provides type of Data to be stored by the Data Base Service + * @throws SQLException If any issue occurs during instantiation of DB Service or during startup. + */ + protected LobSerializer(String dataTypeDb) throws SQLException { + databaseService = new DatabaseService(dataTypeDb); + databaseService.startupService(); + } + + /** + * Provides the specification to Serialize the input object. + * + * @param toSerialize Input Object to serialize + * @return Serialized Object + * @throws ParserConfigurationException if any issue occurs during parsing of input object + * @throws TransformerException if any issue occurs during Transformation + * @throws IOException if any issues occur during reading object + */ + public abstract Object serialize(Forest toSerialize) + throws ParserConfigurationException, TransformerException, IOException; + + /** + * Saves the object to DB with the provided ID. + * + * @param id key to be sent to DB service + * @param name Object name to store in DB + * @param object Object to store in DB + * @return ID with which the object is stored in DB + * @throws SQLException if any issue occurs while saving to DB + */ + public int persistToDb(int id, String name, Object object) throws SQLException { + databaseService.insert(id, name, object); + return id; + } + + /** + * Loads the object from db using the ID and column name. + * + * @param id to query the DB + * @param columnName column from which object is to be extracted + * @return Object from DB + * @throws SQLException if any issue occurs while loading from DB + */ + public Object loadFromDb(int id, String columnName) throws SQLException { + return databaseService.select(id, columnName); + } + + /** + * Provides the specification to Deserialize the input object. + * + * @param toDeserialize object to deserialize + * @return Deserialized Object + * @throws ParserConfigurationException If issue occurs during parsing of input object + * @throws IOException if any issues occur during reading object + * @throws SAXException if any issues occur during reading object for XML parsing + */ + public abstract Forest deSerialize(Object toDeserialize) + throws ParserConfigurationException, IOException, SAXException, ClassNotFoundException; + + @Override + public void close() { + try { + databaseService.shutDownService(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/serialized-lob/src/test/java/com/iluwatar/slob/AppTest.java b/serialized-lob/src/test/java/com/iluwatar/slob/AppTest.java new file mode 100644 index 000000000000..8d87608a27e0 --- /dev/null +++ b/serialized-lob/src/test/java/com/iluwatar/slob/AppTest.java @@ -0,0 +1,157 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.iluwatar.slob.lob.Animal; +import com.iluwatar.slob.lob.Forest; +import com.iluwatar.slob.lob.Plant; +import com.iluwatar.slob.serializers.BlobSerializer; +import com.iluwatar.slob.serializers.ClobSerializer; +import com.iluwatar.slob.serializers.LobSerializer; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Set; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +/** SLOB Application test */ +@Slf4j +class AppTest { + + /** + * Creates a Forest with Animals and Plants along with their respective relationships. + * + *

    The method creates a forest with 2 Plants Grass and Oak of type Herb and tree respectively. + * + *

    It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the + * Zebra and the Buffalo. + * + *

    With the above animals and plants and their relationships a forest object is created which + * represents the Object Graph. + * + * @return Forest Object + */ + private static Forest createForest() { + Plant grass = new Plant("Grass", "Herb"); + Plant oak = new Plant("Oak", "Tree"); + + Animal zebra = new Animal("Zebra", Set.of(grass), Collections.emptySet()); + Animal buffalo = new Animal("Buffalo", Set.of(grass), Collections.emptySet()); + Animal lion = new Animal("Lion", Collections.emptySet(), Set.of(zebra, buffalo)); + + return new Forest("Amazon", Set.of(lion, buffalo, zebra), Set.of(grass, oak)); + } + + /** + * Tests the {@link App} without passing any argument in the args to test the {@link + * ClobSerializer}. + */ + @Test + void shouldExecuteWithoutExceptionClob() { + assertDoesNotThrow(() -> App.main(new String[] {"CLOB"})); + } + + /** + * Tests the {@link App} without passing any argument in the args to test the {@link + * BlobSerializer}. + */ + @Test + void shouldExecuteWithoutExceptionBlob() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } + + /** + * Tests the serialization of the input object using the {@link ClobSerializer} and persists the + * serialized object to DB, then load the object back from DB and deserializes it using the + * provided {@link ClobSerializer}. + * + *

    After loading the object back from DB the test matches the hash of the input object with the + * hash of the object that was loaded from DB and deserialized. + */ + @Test + void clobSerializerTest() { + Forest forest = createForest(); + try (LobSerializer serializer = new ClobSerializer()) { + + Object serialized = serializer.serialize(forest); + int id = serializer.persistToDb(1, forest.getName(), serialized); + + Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); + Forest forestFromDb = serializer.deSerialize(fromDb); + + Assertions.assertEquals( + forest.hashCode(), + forestFromDb.hashCode(), + "Hashes of objects after Serializing and Deserializing are the same"); + } catch (SQLException + | IOException + | TransformerException + | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Tests the serialization of the input object using the {@link BlobSerializer} and persists the + * serialized object to DB, then loads the object back from DB and deserializes it using the + * {@link BlobSerializer}. + * + *

    After loading the object back from DB the test matches the hash of the input object with the + * hash of the object that was loaded from DB and deserialized. + */ + @Test + void blobSerializerTest() { + Forest forest = createForest(); + try (LobSerializer serializer = new BlobSerializer()) { + + Object serialized = serializer.serialize(forest); + int id = serializer.persistToDb(1, forest.getName(), serialized); + + Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); + Forest forestFromDb = serializer.deSerialize(fromDb); + + Assertions.assertEquals( + forest.hashCode(), + forestFromDb.hashCode(), + "Hashes of objects after Serializing and Deserializing are the same"); + } catch (SQLException + | IOException + | TransformerException + | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/servant/README.md b/servant/README.md index c6aaf414873d..67fb496abd15 100644 --- a/servant/README.md +++ b/servant/README.md @@ -1,173 +1,91 @@ --- -title: Servant -category: Behavioral +title: "Servant Pattern in Java: Facilitating Rich Interactions with Servant Helpers" +shortTitle: Servant +description: "Explore the Servant design pattern in Java, an essential technique for decoupling operations from object classes. Learn how this pattern facilitates reusable and efficient code management through our detailed examples and explanations." +category: Structural language: en tag: -- Decoupling + - Decoupling + - Interface + - Messaging + - Object composition + - Resource management --- -## Intent -Servant is used for providing some behavior to a group of classes. -Instead of defining that behavior in each class - or when we cannot factor out -this behavior in the common parent class - it is defined once in the Servant. +## Also known as -## Explanation +* Helper -Real-world example - -> King, Queen, and other royal member of palace need servant to service them for feeding, -> organizing drinks, and so on. - -In plain words - -> Ensures one servant object to give some specific services for a group of serviced classes. +## Intent of Servant Design Pattern -Wikipedia says +The Servant pattern is used to perform specific operations for a group of objects without changing the classes of the elements on which it operates. -> In software engineering, the servant pattern defines an object used to offer some functionality -> to a group of classes without defining that functionality in each of them. A Servant is a class -> whose instance (or even just class) provides methods that take care of a desired service, while -> objects for which (or with whom) the servant does something, are taken as parameters. +## Detailed Explanation of Servant Pattern with Real-World Examples -**Programmatic Example** +Real-world example -Servant class which can give services to other royal members of palace. +> In a restaurant, a waiter (Servant) serves multiple tables (objects) by taking orders, delivering food, and handling payments. The waiter provides these common services without altering the fundamental nature of the tables or customers, allowing the restaurant to efficiently manage customer service while keeping the roles of the tables and customers unchanged. -```java -/** - * Servant. - */ -public class Servant { +In plain words - public String name; - - /** - * Constructor. - */ - public Servant(String name) { - this.name = name; - } - - public void feed(Royalty r) { - r.getFed(); - } - - public void giveWine(Royalty r) { - r.getDrink(); - } - - public void giveCompliments(Royalty r) { - r.receiveCompliments(); - } - - /** - * Check if we will be hanged. - */ - public boolean checkIfYouWillBeHanged(List tableGuests) { - return tableGuests.stream().allMatch(Royalty::getMood); - } -} -``` +> The Java Servant pattern effectively centralizes common functionality for multiple classes, enabling decoupled and reusable operations in software development without altering the original classes, promoting efficient Java design practices. -Royalty is an interface. It is implemented by King, and Queen classes to get services from servant. +Wikipedia says -```java -interface Royalty { +> In software engineering, the servant pattern defines an object used to offer some functionality to a group of classes without defining that functionality in each of them. A Servant is a class whose instance (or even just class) provides methods that take care of a desired service, while objects for which (or with whom) the servant does something, are taken as parameters. - void getFed(); +## Programmatic Example of Servant Pattern in Java - void getDrink(); +The Servant design pattern is a behavioral design pattern that defines a class that provides some sort of service to a group of classes. This pattern is particularly useful when these classes lack some common functionality that can't be added to the superclass. The Servant class brings this common functionality to a group of classes. - void changeMood(); +In the provided code, we have a `Servant` class that provides services to the `Royalty` class. The `Servant` class has methods like `feed()`, `giveWine()`, and `giveCompliments()` which are services provided to the `Royalty` class. - void receiveCompliments(); +Here is the `Servant` class: - boolean getMood(); -} -``` -King, class is implementing Royalty interface. ```java -public class King implements Royalty { +public class Servant { - private boolean isDrunk; - private boolean isHungry = true; - private boolean isHappy; - private boolean complimentReceived; + public String name; - @Override - public void getFed() { - isHungry = false; + public Servant(String name) { + this.name = name; } - @Override - public void getDrink() { - isDrunk = true; + public void feed(Royalty r) { + r.getFed(); } - public void receiveCompliments() { - complimentReceived = true; + public void giveWine(Royalty r) { + r.getDrink(); } - @Override - public void changeMood() { - if (!isHungry && isDrunk) { - isHappy = true; - } - if (complimentReceived) { - isHappy = false; - } + public void giveCompliments(Royalty r) { + r.receiveCompliments(); } - @Override - public boolean getMood() { - return isHappy; + public boolean checkIfYouWillBeHanged(List tableGuests) { + return tableGuests.stream().allMatch(Royalty::getMood); } } ``` -Queen, class is implementing Royalty interface. -```java -public class Queen implements Royalty { - - private boolean isDrunk = true; - private boolean isHungry; - private boolean isHappy; - private boolean isFlirty = true; - private boolean complimentReceived; - - @Override - public void getFed() { - isHungry = false; - } - @Override - public void getDrink() { - isDrunk = true; - } +The `Royalty` class is an interface that is implemented by the classes that use the services of the `Servant`. Here is a simplified version of the `Royalty` interface: - public void receiveCompliments() { - complimentReceived = true; - } +```java +public interface Royalty { + void getFed(); - @Override - public void changeMood() { - if (complimentReceived && isFlirty && isDrunk && !isHungry) { - isHappy = true; - } - } + void getDrink(); - @Override - public boolean getMood() { - return isHappy; - } + void receiveCompliments(); - public void setFlirtiness(boolean f) { - this.isFlirty = f; - } + void changeMood(); + boolean getMood(); } ``` -Then in order to use: +The `App` class uses the `Servant` to provide services to the `Royalty` objects. Here is a simplified version of the `App` class: ```java public class App { @@ -175,36 +93,25 @@ public class App { private static final Servant jenkins = new Servant("Jenkins"); private static final Servant travis = new Servant("Travis"); - /** - * Program entry point. - */ public static void main(String[] args) { scenario(jenkins, 1); scenario(travis, 0); } - /** - * Can add a List with enum Actions for variable scenarios. - */ public static void scenario(Servant servant, int compliment) { var k = new King(); var q = new Queen(); var guests = List.of(k, q); - // feed servant.feed(k); servant.feed(q); - // serve drinks servant.giveWine(k); servant.giveWine(q); - // compliment servant.giveCompliments(guests.get(compliment)); - // outcome of the night guests.forEach(Royalty::changeMood); - // check your luck if (servant.checkIfYouWillBeHanged(guests)) { LOGGER.info("{} will live another day", servant.name); } else { @@ -214,22 +121,47 @@ public class App { } ``` -The console output +Running the application produces: ``` -Jenkins will live another day -Poor Travis. His days are numbered +09:10:38.795 [main] INFO com.iluwatar.servant.App -- Jenkins will live another day +09:10:38.797 [main] INFO com.iluwatar.servant.App -- Poor Travis. His days are numbered ``` +In this example, the `Servant` class provides services to the `Royalty` objects. The `Servant` class doesn't know about the specific implementation of the `Royalty` objects, it only knows that it can provide certain services to them. This is a good example of the Servant design pattern. + +## When to Use the Servant Pattern in Java + +* Use the Servant pattern when you need to provide a common functionality to a group of classes without polluding their class definitions, perfect for enhancing Java application architecture. +* Suitable when the operations performed on the objects are not the primary responsibility of the objects themselves. + +## Real-World Applications of Servant Pattern in Java + +* In GUI applications to handle operations like rendering or hit-testing which are common across different UI components. +* In games where various entities (like players, enemies, or items) need common behavior such as movement or collision detection. +* Logging or auditing functionalities that are required across multiple business objects. + +## Benefits and Trade-offs of Servant Pattern + +Benefits: + +* Promotes code reuse and separation of concerns by decoupling the operations from the objects they operate on. +* Reduces code duplication by centralizing the shared functionality. + +Trade-offs: -## Class diagram -![alt text](./etc/servant-pattern.png "Servant") +* Can lead to an increase in the number of classes, potentially making the system harder to understand. +* May introduce tight coupling between the Servant and the classes it serves if not designed carefully. -## Applicability -Use the Servant pattern when +## Related Java Design Patterns -* When we want some objects to perform a common action and don't want to define this action as a method in every class. +* [Adapter](https://java-design-patterns.com/patterns/adapter/): The Servant pattern is similar to the Adapter pattern in that both provide a way to work with classes without modifying them, but the Servant pattern focuses on providing additional behavior to multiple classes rather than adapting one interface to another. +* [Facade](https://java-design-patterns.com/patterns/facade/): Both patterns provide a simplified interface to a set of functionalities, but the Servant pattern is typically used for adding functionalities to a group of classes, while the Facade pattern hides the complexities of a subsystem. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The Servant pattern can be used in conjunction with the Strategy pattern to define operations that apply to multiple classes. +* View Helper: The View Helper pattern is related as it also centralizes common functionality, but it focuses on separating presentation logic from business logic in web applications. -## Credits +## References and Credits -* [Let's Modify the Objects-First Approach into Design-Patterns-First](http://edu.pecinovsky.cz/papers/2006_ITiCSE_Design_Patterns_First.pdf) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3xZ1ELU) diff --git a/servant/pom.xml b/servant/pom.xml index 65650364bd6b..9917acddb984 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -35,6 +35,14 @@ servant + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/servant/src/main/java/com/iluwatar/servant/App.java b/servant/src/main/java/com/iluwatar/servant/App.java index 9583823c8f29..122a4f0a4ce0 100644 --- a/servant/src/main/java/com/iluwatar/servant/App.java +++ b/servant/src/main/java/com/iluwatar/servant/App.java @@ -27,7 +27,6 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; - /** * Servant offers some functionality to a group of classes without defining that functionality in * each of them. A Servant is a class whose instance provides methods that take care of a desired @@ -41,17 +40,13 @@ public class App { private static final Servant jenkins = new Servant("Jenkins"); private static final Servant travis = new Servant("Travis"); - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { scenario(jenkins, 1); scenario(travis, 0); } - /** - * Can add a List with enum Actions for variable scenarios. - */ + /** Can add a List with enum Actions for variable scenarios. */ public static void scenario(Servant servant, int compliment) { var k = new King(); var q = new Queen(); diff --git a/servant/src/main/java/com/iluwatar/servant/King.java b/servant/src/main/java/com/iluwatar/servant/King.java index a2fb35e1a5b2..85150edf28d3 100644 --- a/servant/src/main/java/com/iluwatar/servant/King.java +++ b/servant/src/main/java/com/iluwatar/servant/King.java @@ -24,9 +24,7 @@ */ package com.iluwatar.servant; -/** - * King. - */ +/** King. */ public class King implements Royalty { private boolean isDrunk; diff --git a/servant/src/main/java/com/iluwatar/servant/Queen.java b/servant/src/main/java/com/iluwatar/servant/Queen.java index 50b2f97a7e31..e0de35938792 100644 --- a/servant/src/main/java/com/iluwatar/servant/Queen.java +++ b/servant/src/main/java/com/iluwatar/servant/Queen.java @@ -24,9 +24,7 @@ */ package com.iluwatar.servant; -/** - * Queen. - */ +/** Queen. */ public class Queen implements Royalty { private boolean isDrunk = true; @@ -64,5 +62,4 @@ public boolean getMood() { public void setFlirtiness(boolean f) { this.isFlirty = f; } - } diff --git a/servant/src/main/java/com/iluwatar/servant/Royalty.java b/servant/src/main/java/com/iluwatar/servant/Royalty.java index ae19d4e6b463..befc71246f7f 100644 --- a/servant/src/main/java/com/iluwatar/servant/Royalty.java +++ b/servant/src/main/java/com/iluwatar/servant/Royalty.java @@ -24,9 +24,7 @@ */ package com.iluwatar.servant; -/** - * Royalty. - */ +/** Royalty. */ interface Royalty { void getFed(); diff --git a/servant/src/main/java/com/iluwatar/servant/Servant.java b/servant/src/main/java/com/iluwatar/servant/Servant.java index e6d279b2f3eb..f572b5d4ab86 100644 --- a/servant/src/main/java/com/iluwatar/servant/Servant.java +++ b/servant/src/main/java/com/iluwatar/servant/Servant.java @@ -26,16 +26,12 @@ import java.util.List; -/** - * Servant. - */ +/** Servant. */ public class Servant { public String name; - /** - * Constructor. - */ + /** Constructor. */ public Servant(String name) { this.name = name; } @@ -52,9 +48,7 @@ public void giveCompliments(Royalty r) { r.receiveCompliments(); } - /** - * Check if we will be hanged. - */ + /** Check if we will be hanged. */ public boolean checkIfYouWillBeHanged(List tableGuests) { return tableGuests.stream().allMatch(Royalty::getMood); } diff --git a/servant/src/test/java/com/iluwatar/servant/AppTest.java b/servant/src/test/java/com/iluwatar/servant/AppTest.java index 63322030e91d..dea0950738da 100644 --- a/servant/src/test/java/com/iluwatar/servant/AppTest.java +++ b/servant/src/test/java/com/iluwatar/servant/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.servant; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/servant/src/test/java/com/iluwatar/servant/KingTest.java b/servant/src/test/java/com/iluwatar/servant/KingTest.java index d570c9d993bd..31d03ca1b3d0 100644 --- a/servant/src/test/java/com/iluwatar/servant/KingTest.java +++ b/servant/src/test/java/com/iluwatar/servant/KingTest.java @@ -29,11 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 9:40 PM - * - * @author Jeroen Meulemeester - */ +/** KingTest */ class KingTest { @Test @@ -103,5 +99,4 @@ void testHungryDrunkComplimentedKing() { king.changeMood(); assertFalse(king.getMood()); } - -} \ No newline at end of file +} diff --git a/servant/src/test/java/com/iluwatar/servant/QueenTest.java b/servant/src/test/java/com/iluwatar/servant/QueenTest.java index 3cb36bf48cdc..251e9934b75e 100644 --- a/servant/src/test/java/com/iluwatar/servant/QueenTest.java +++ b/servant/src/test/java/com/iluwatar/servant/QueenTest.java @@ -24,17 +24,12 @@ */ package com.iluwatar.servant; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 9:52 PM - * - * @author Jeroen Meulemeester - */ +/** QueenTest */ class QueenTest { @Test @@ -68,5 +63,4 @@ void testFlirtyComplemented() { queen.changeMood(); assertTrue(queen.getMood()); } - -} \ No newline at end of file +} diff --git a/servant/src/test/java/com/iluwatar/servant/ServantTest.java b/servant/src/test/java/com/iluwatar/servant/ServantTest.java index 450ccad8b689..f0f476835325 100644 --- a/servant/src/test/java/com/iluwatar/servant/ServantTest.java +++ b/servant/src/test/java/com/iluwatar/servant/ServantTest.java @@ -33,11 +33,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 10:02 PM - * - * @author Jeroen Meulemeester - */ +/** ServantTest */ class ServantTest { @Test @@ -81,7 +77,5 @@ void testCheckIfYouWillBeHanged() { assertTrue(new Servant("test").checkIfYouWillBeHanged(goodCompany)); assertTrue(new Servant("test").checkIfYouWillBeHanged(badCompany)); - } - -} \ No newline at end of file +} diff --git a/server-session/README.md b/server-session/README.md new file mode 100644 index 000000000000..4ce452b53f7d --- /dev/null +++ b/server-session/README.md @@ -0,0 +1,176 @@ +--- +title: "Server Session Pattern in Java: Managing User Sessions with Enhanced Security" +shortTitle: Server Session +description: "Explore the Server Session Pattern for Java applications. Learn how this design pattern helps manage user sessions securely and maintain state across multiple client requests with detailed examples and uses." +category: Resource management +language: en +tag: + - Client-server + - Cookies + - Session management + - State tracking + - Web development +--- + +## Also known as + +* Server-Side Session Management + +## Intent of Server Session Design Pattern + +Effectively manage user session data on the server-side with Java's Server Session pattern to maintain consistent state across multiple client interactions, enhancing both security and user experience. + +## Detailed Explanation of Server Session Pattern with Real-World Examples + +Real-world example + +> Imagine a hotel where each guest is given a unique room key card upon check-in. Similar to how a hotel key card stores a guest's personal preferences (such as preferred room temperature, wake-up call times, and minibar choices), the Server Session pattern in Java securely stores user preferences server-side, ensuring a personalized and secure user experience. Whenever the guest interacts with hotel services, such as ordering room service or accessing the gym, the system retrieves their preferences using the information on the key card. The hotel’s central server maintains these preferences, ensuring consistent and personalized service throughout the guest's stay. Similarly, the Server Session design pattern manages user data on the server, providing a seamless experience across multiple interactions within a web application. + +In plain words + +> The Server Session design pattern manages and stores user session data on the server to maintain state across multiple client requests in web applications. + +Wikipedia says + +> A session token is a unique identifier that is generated and sent from a server to a client to identify the current interaction session. The client usually stores and sends the token as an HTTP cookie and/or sends it as a parameter in GET or POST queries. The reason to use session tokens is that the client only has to handle the identifier—all session data is stored on the server (usually in a database, to which the client does not have direct access) linked to that identifier. + +## Programmatic Example of Server Session Pattern in Java + +The Server Session design pattern is a behavioral design pattern that assigns the responsibility of storing session data on the server side. This pattern is particularly useful in the context of stateless protocols like HTTP where all requests are isolated events independent of previous requests. + +In this pattern, when a user logs in, a session identifier is created and stored for future requests in a list. When a user logs out, the session identifier is deleted from the list along with the appropriate user session data. + +Let's take a look at a programmatic example of the Server Session design pattern. + +The `main` application starts a server and assigns handlers to manage login and logout requests. It also starts a background task to check for expired sessions. + +```java +public class App { + + private static Map sessions = new HashMap<>(); + private static Map sessionCreationTimes = new HashMap<>(); + private static final long SESSION_EXPIRATION_TIME = 10000; + + public static void main(String[] args) throws IOException { + HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); + + server.createContext("/login", new LoginHandler(sessions, sessionCreationTimes)); + server.createContext("/logout", new LogoutHandler(sessions, sessionCreationTimes)); + + server.start(); + + sessionExpirationTask(); + } + + private static void sessionExpirationTask() { + new Thread(() -> { + while (true) { + try { + Thread.sleep(SESSION_EXPIRATION_TIME); + Instant currentTime = Instant.now(); + synchronized (sessions) { + synchronized (sessionCreationTimes) { + Iterator> iterator = + sessionCreationTimes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) { + sessions.remove(entry.getKey()); + iterator.remove(); + } + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }).start(); + } +} +``` + +The `LoginHandler` is responsible for handling login requests. When a user logs in, a session identifier is created and stored for future requests in a list. + +```java +public class LoginHandler { + + private Map sessions; + private Map sessionCreationTimes; + + public LoginHandler(Map sessions, Map sessionCreationTimes) { + this.sessions = sessions; + this.sessionCreationTimes = sessionCreationTimes; + } + + public void handle(HttpExchange exchange) { + // Implementation of the handle method + } +} +``` + +The `LogoutHandler` is responsible for handling logout requests. When a user logs out, the session identifier is deleted from the list along with the appropriate user session data. + +```java +public class LogoutHandler { + + private Map sessions; + private Map sessionCreationTimes; + + public LogoutHandler(Map sessions, Map sessionCreationTimes) { + this.sessions = sessions; + this.sessionCreationTimes = sessionCreationTimes; + } + + public void handle(HttpExchange exchange) { + // Implementation of the handle method + } +} +``` + +Console output for starting the `App` class's `main` method: + +``` +12:09:50.998 [Thread-1] INFO com.iluwatar.sessionserver.App -- Session expiration checker started... +12:09:50.998 [main] INFO com.iluwatar.sessionserver.App -- Server started. Listening on port 8080... +``` + +This is a basic example of the Server Session design pattern. The actual implementation of the `handle` methods in the `LoginHandler` and `LogoutHandler` classes would depend on the specific requirements of your application. + +## When to Use the Server Session Pattern in Java + +* Use when building web applications that require maintaining user state information across multiple requests. +* Suitable for applications needing to track user interactions, preferences, or authentication state. +* Ideal for scenarios where client-side storage is insecure or insufficient. + +## Real-World Applications of Server Session Pattern in Java + +* Java EE applications using HttpSession for session management. +* Spring Framework's `@SessionAttributes` for handling user session data. +* Apache Tomcat's session management mechanism. + +## Benefits and Trade-offs of Server Session Pattern + +Benefits: + +* Simplifies client-side logic by offloading state management to the server. +* Enhances security by storing sensitive information server-side. +* Supports complex state management scenarios like multistep forms or shopping carts. + +Trade-offs: + +* Increases server memory usage due to storage of session data. +* Requires session management logic to handle session timeouts and data persistence. +* Potential scalability issues with high user concurrency. + +## Related Java Design Patterns + +* [State](https://java-design-patterns.com/patterns/state/): Manages state-specific behavior, which can be utilized within session management to handle different user states. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Can be used to add a layer of control over session data access. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used to create a single instance of a session manager. + +## References and Credits + +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/server-session/etc/server-session.urm.png b/server-session/etc/server-session.urm.png new file mode 100644 index 000000000000..2de3694cdb09 Binary files /dev/null and b/server-session/etc/server-session.urm.png differ diff --git a/server-session/etc/server-session.urm.puml b/server-session/etc/server-session.urm.puml new file mode 100644 index 000000000000..a33aa720a274 --- /dev/null +++ b/server-session/etc/server-session.urm.puml @@ -0,0 +1,27 @@ +@startuml +package com.iluwatar.sessionserver { + class App { + - LOGGER : Logger {static} + - SESSION_EXPIRATION_TIME : long {static} + - sessionCreationTimes : Map {static} + - sessions : Map {static} + + App() + + main(args : String[]) {static} + - sessionExpirationTask() {static} + } + class LoginHandler { + - LOGGER : Logger {static} + - sessionCreationTimes : Map + - sessions : Map + + LoginHandler(sessions : Map, sessionCreationTimes : Map) + + handle(exchange : HttpExchange) + } + class LogoutHandler { + - LOGGER : Logger {static} + - sessionCreationTimes : Map + - sessions : Map + + LogoutHandler(sessions : Map, sessionCreationTimes : Map) + + handle(exchange : HttpExchange) + } +} +@enduml \ No newline at end of file diff --git a/module/pom.xml b/server-session/pom.xml similarity index 53% rename from module/pom.xml rename to server-session/pom.xml index 4f6c0dcb86e3..50b52b405158 100644 --- a/module/pom.xml +++ b/server-session/pom.xml @@ -25,38 +25,37 @@ THE SOFTWARE. --> - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - module - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.module.App - - - - - - - - - + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + server-session + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/App.java b/server-session/src/main/java/com/iluwatar/sessionserver/App.java new file mode 100644 index 000000000000..512447b8a2d9 --- /dev/null +++ b/server-session/src/main/java/com/iluwatar/sessionserver/App.java @@ -0,0 +1,116 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionserver; + +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.time.Instant; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * The server session pattern is a behavioral design pattern concerned with assigning the + * responsibility of storing session data on the server side. Within the context of stateless + * protocols like HTTP all requests are isolated events independent of previous requests. In order + * to create sessions during user-access for a particular web application various methods can be + * used, such as cookies. Cookies are a small piece of data that can be sent between client and + * server on every request and response so that the server can "remember" the previous requests. In + * general cookies can either store the session data or the cookie can store a session identifier + * and be used to access appropriate data from a persistent storage. In the latter case the session + * data is stored on the server-side and appropriate data is identified by the cookie sent from a + * client's request. This project demonstrates the latter case. In the following example the ({@link + * App}) class starts a server and assigns ({@link LoginHandler}) class to handle login request. + * When a user logs in a session identifier is created and stored for future requests in a list. + * When a user logs out the session identifier is deleted from the list along with the appropriate + * user session data, which is handle by the ({@link LogoutHandler}) class. + */ +@Slf4j +public class App { + + // Map to store session data (simulated using a HashMap) + private static Map sessions = new HashMap<>(); + private static Map sessionCreationTimes = new HashMap<>(); + private static final long SESSION_EXPIRATION_TIME = 10000; + + /** + * Main entry point. + * + * @param args arguments + * @throws IOException ex + */ + public static void main(String[] args) throws IOException { + // Create HTTP server listening on port 8000 + HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); + + // Set up session management endpoints + server.createContext("/login", new LoginHandler(sessions, sessionCreationTimes)); + server.createContext("/logout", new LogoutHandler(sessions, sessionCreationTimes)); + + // Start the server + server.start(); + + // Start background task to check for expired sessions + sessionExpirationTask(); + + LOGGER.info("Server started. Listening on port 8080..."); + } + + private static void sessionExpirationTask() { + new Thread( + () -> { + while (true) { + try { + LOGGER.info("Session expiration checker started..."); + Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time + Instant currentTime = Instant.now(); + synchronized (sessions) { + synchronized (sessionCreationTimes) { + Iterator> iterator = + sessionCreationTimes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry + .getValue() + .plusMillis(SESSION_EXPIRATION_TIME) + .isBefore(currentTime)) { + sessions.remove(entry.getKey()); + iterator.remove(); + } + } + } + } + LOGGER.info("Session expiration checker finished!"); + } catch (InterruptedException e) { + LOGGER.error("An error occurred: ", e); + Thread.currentThread().interrupt(); + } + } + }) + .start(); + } +} diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java b/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java new file mode 100644 index 000000000000..fd19aa5b9e5f --- /dev/null +++ b/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java @@ -0,0 +1,75 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionserver; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import java.io.IOException; +import java.io.OutputStream; +import java.time.Instant; +import java.util.Map; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +/** LoginHandler. */ +@Slf4j +public class LoginHandler implements HttpHandler { + + private Map sessions; + private Map sessionCreationTimes; + + public LoginHandler(Map sessions, Map sessionCreationTimes) { + this.sessions = sessions; + this.sessionCreationTimes = sessionCreationTimes; + } + + @Override + public void handle(HttpExchange exchange) { + // Generate session ID + String sessionId = UUID.randomUUID().toString(); + + // Store session data (simulated) + int newUser = sessions.size() + 1; + sessions.put(sessionId, newUser); + sessionCreationTimes.put(sessionId, Instant.now()); + LOGGER.info("User " + newUser + " created at time " + sessionCreationTimes.get(sessionId)); + + // Set session ID as cookie + exchange.getResponseHeaders().add("Set-Cookie", "sessionID=" + sessionId); + + // Send response + String response = "Login successful!\n" + "Session ID: " + sessionId; + try { + exchange.sendResponseHeaders(200, response.length()); + } catch (IOException e) { + LOGGER.error("An error occurred: ", e); + } + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } catch (IOException e) { + LOGGER.error("An error occurred: ", e); + } + } +} diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java b/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java new file mode 100644 index 000000000000..3d98a7b604bd --- /dev/null +++ b/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java @@ -0,0 +1,83 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionserver; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import java.io.IOException; +import java.io.OutputStream; +import java.time.Instant; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** LogoutHandler. */ +@Slf4j +public class LogoutHandler implements HttpHandler { + + private Map sessions; + private Map sessionCreationTimes; + + public LogoutHandler(Map sessions, Map sessionCreationTimes) { + this.sessions = sessions; + this.sessionCreationTimes = sessionCreationTimes; + } + + @Override + public void handle(HttpExchange exchange) { + // Get session ID from cookie + String sessionId = exchange.getRequestHeaders().getFirst("Cookie").replace("sessionID=", ""); + String currentSessionId = sessions.get(sessionId) == null ? null : sessionId; + + // Send response + + String response = ""; + if (currentSessionId == null) { + response += "Session has already expired!"; + } else { + response = "Logout successful!\n" + "Session ID: " + currentSessionId; + } + + // Remove session + if (currentSessionId != null) { + LOGGER.info("User " + sessions.get(currentSessionId) + " deleted!"); + } else { + LOGGER.info("User already deleted!"); + } + sessions.remove(sessionId); + sessionCreationTimes.remove(sessionId); + + try { + exchange.sendResponseHeaders(200, response.length()); + } catch (IOException e) { + LOGGER.error("An error has occurred: ", e); + } + + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } catch (IOException e) { + LOGGER.error("An error has occurred: ", e); + } + } +} diff --git a/server-session/src/test/java/com/iluwatar/sessionserver/LoginHandlerTest.java b/server-session/src/test/java/com/iluwatar/sessionserver/LoginHandlerTest.java new file mode 100644 index 000000000000..1da0f3ab51f9 --- /dev/null +++ b/server-session/src/test/java/com/iluwatar/sessionserver/LoginHandlerTest.java @@ -0,0 +1,79 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionserver; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import java.io.ByteArrayOutputStream; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** LoginHandlerTest. */ +public class LoginHandlerTest { + + private LoginHandler loginHandler; + // private Headers headers; + private Map sessions; + private Map sessionCreationTimes; + + @Mock private HttpExchange exchange; + + /** Setup tests. */ + @BeforeEach + public void setUp() { + MockitoAnnotations.initMocks(this); + sessions = new HashMap<>(); + sessionCreationTimes = new HashMap<>(); + loginHandler = new LoginHandler(sessions, sessionCreationTimes); + } + + @Test + public void testHandle() { + + // assemble + ByteArrayOutputStream outputStream = + new ByteArrayOutputStream(); // Exchange object is mocked so OutputStream must be manually + // created + when(exchange.getResponseHeaders()) + .thenReturn( + new Headers()); // Exchange object is mocked so Header object must be manually created + when(exchange.getResponseBody()).thenReturn(outputStream); + + // act + loginHandler.handle(exchange); + + // assert + String[] response = outputStream.toString().split("Session ID: "); + assertEquals(sessions.entrySet().toArray()[0].toString().split("=1")[0], response[1]); + } +} diff --git a/server-session/src/test/java/com/iluwatar/sessionserver/LogoutHandlerTest.java b/server-session/src/test/java/com/iluwatar/sessionserver/LogoutHandlerTest.java new file mode 100644 index 000000000000..3929aeb4bc7f --- /dev/null +++ b/server-session/src/test/java/com/iluwatar/sessionserver/LogoutHandlerTest.java @@ -0,0 +1,101 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionserver; + +import static org.mockito.Mockito.when; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import java.io.ByteArrayOutputStream; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** LogoutHandlerTest. */ +public class LogoutHandlerTest { + + private LogoutHandler logoutHandler; + private Headers headers; + private Map sessions; + private Map sessionCreationTimes; + + @Mock private HttpExchange exchange; + + /** Setup tests. */ + @BeforeEach + public void setUp() { + MockitoAnnotations.initMocks(this); + sessions = new HashMap<>(); + sessionCreationTimes = new HashMap<>(); + logoutHandler = new LogoutHandler(sessions, sessionCreationTimes); + headers = new Headers(); + headers.add( + "Cookie", + "sessionID=1234"); // Exchange object methods return Header Object but Exchange is mocked so + // Headers must be manually created + } + + @Test + public void testHandler_SessionNotExpired() { + + // assemble + sessions.put("1234", 1); // Fake login details since LoginHandler isn't called + sessionCreationTimes.put( + "1234", Instant.now()); // Fake login details since LoginHandler isn't called + when(exchange.getRequestHeaders()).thenReturn(headers); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + when(exchange.getResponseBody()).thenReturn(outputStream); + + // act + logoutHandler.handle(exchange); + + // assert + String[] response = outputStream.toString().split("Session ID: "); + Assertions.assertEquals("1234", response[1]); + Assertions.assertFalse(sessions.containsKey(response[1])); + Assertions.assertFalse(sessionCreationTimes.containsKey(response[1])); + } + + @Test + public void testHandler_SessionExpired() { + + // assemble + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + when(exchange.getRequestHeaders()).thenReturn(headers); + when(exchange.getResponseBody()).thenReturn(outputStream); + + // act + logoutHandler.handle(exchange); + + // assert + String[] response = outputStream.toString().split("Session ID: "); + Assertions.assertEquals("Session has already expired!", response[0]); + } +} diff --git a/service-layer/README.md b/service-layer/README.md index aebd980b2eee..1d737980e541 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -1,106 +1,91 @@ --- -title: Service Layer +title: "Service Layer Pattern in Java: Enhancing Application Architecture with Robust Service Layers" +shortTitle: Service Layer +description: "Explore the Service Layer pattern for Java applications, a key design solution for separating business logic from presentation logic. Learn its uses, benefits, and implementation with real-world examples and class diagrams to optimize your architectural strategies." category: Architectural language: en tag: - - Data access + - API design + - Business + - Decoupling + - Enterprise patterns + - Layered architecture --- -## Intent +## Also known as -Service Layer is an abstraction over domain logic. It defines application's boundary with a layer of services that -establishes a set of available operations and coordinates the application's response in each operation. +* Application Facade -## Explanation +## Intent of Service Layer Design Pattern -Typically applications require different kinds of interfaces to the data they store and the logic they implement. -Despite their different purposes, these interfaces often need common interactions with the application to access and -manipulate its data and invoke its business logic. Encoding the logic of the interactions separately in each module -causes a lot of duplication. It's better to centralize building the business logic inside single Service Layer to avoid -these pitfalls. - -Real world example +The Service Layer pattern is crucial for Java developers focusing on building robust application architectures that separate business processes from user interface concerns. -> We are writing an application that tracks wizards, spellbooks and spells. Wizards may have spellbooks and spellbooks -may have spells. +The pattern encapsulate business logic in a distinct layer to promote separation of concerns and to provide a well-defined API for the presentation layer. + +## Detailed Explanation of Service Layer Pattern with Real-World Examples + +Real-world example + +> Imagine a complex restaurant system where orders are managed through a centralized 'service layer' to ensure efficient operation and clear communication between the front and back of the house. Each section specializes in a part of the meal, but the waitstaff don't interact directly with the kitchen staff. Instead, all orders go through a head chef who coordinates the workflow. The head chef acts like the service layer, handling the business logic (order coordination) and providing a unified interface for the waitstaff (presentation layer) to interact with the kitchen (data access layer). In plain words -> Service Layer is an abstraction over application's business logic. +> A pattern that encapsulates business logic into a distinct layer to promote separation of concerns and provide a clear API for the presentation layer. Wikipedia says -> Service layer is an architectural pattern, applied within the service-orientation design paradigm, which aims to -organize the services, within a service inventory, into a set of logical layers. Services that are categorized into -a particular layer share functionality. This helps to reduce the conceptual overhead related to managing the service -inventory, as the services belonging to the same layer address a smaller set of activities. +> Service layer is an architectural pattern, applied within the service-orientation design paradigm, which aims to organize the services, within a service inventory, into a set of logical layers. Services that are categorized into a particular layer share functionality. This helps to reduce the conceptual overhead related to managing the service inventory, as the services belonging to the same layer address a smaller set of activities. -**Programmatic Example** +Architecture diagram -The example application demonstrates interactions between a client `App` and a service `MagicService` that allows -interaction between wizards, spellbooks and spells. The service is implemented with 3-layer architecture -(entity, dao, service). +![Service Layer Architecture Diagram](./etc/service-layer-architecture-diagram.png) -For this explanation we are looking at one vertical slice of the system. Let's start from the entity layer and look at -`Wizard` class. Other entities not shown here are `Spellbook` and `Spell`. -```java -@Entity -@Table(name = "WIZARD") -public class Wizard extends BaseEntity { - - @Id - @GeneratedValue - @Column(name = "WIZARD_ID") - private Long id; +## Programmatic Example of Service Layer Pattern in Java - private String name; +Our Java implementation uses the Service Layer pattern to streamline interactions between data access objects (DAOs) and the business logic, ensuring a clean separation of concerns. - @ManyToMany(cascade = CascadeType.ALL) - private Set spellbooks; +The example application demonstrates interactions between a client `App` and a service `MagicService` that allows interaction between wizards, spellbooks and spells. The service is implemented with 3-layer architecture +(entity, dao, service). - public Wizard() { - spellbooks = new HashSet<>(); - } +For this explanation we are looking at one vertical slice of the system. Let's start from the entity layer and look at `Wizard` class. Other entities not shown here are `Spellbook` and `Spell`. - public Wizard(String name) { - this(); - this.name = name; - } +```java - public Long getId() { - return id; - } +@Entity +@Table(name = "WIZARD") +@Getter +@Setter +public class Wizard extends BaseEntity { - public void setId(Long id) { - this.id = id; - } + @Id + @GeneratedValue + @Column(name = "WIZARD_ID") + private Long id; - public String getName() { - return name; - } + private String name; - public void setName(String name) { - this.name = name; - } + @ManyToMany(cascade = CascadeType.ALL) + private Set spellbooks; - public Set getSpellbooks() { - return spellbooks; - } + public Wizard() { + spellbooks = new HashSet<>(); + } - public void setSpellbooks(Set spellbooks) { - this.spellbooks = spellbooks; - } + public Wizard(String name) { + this(); + this.name = name; + } - public void addSpellbook(Spellbook spellbook) { - spellbook.getWizards().add(this); - spellbooks.add(spellbook); - } + public void addSpellbook(Spellbook spellbook) { + spellbook.getWizards().add(this); + spellbooks.add(spellbook); + } - @Override - public String toString() { - return name; - } + @Override + public String toString() { + return name; + } } ``` @@ -109,29 +94,31 @@ Above the entity layer we have DAOs. For `Wizard` the DAO layer looks as follows ```java public interface WizardDao extends Dao { - Wizard findByName(String name); + Wizard findByName(String name); } +``` +```java public class WizardDaoImpl extends DaoBaseImpl implements WizardDao { - @Override - public Wizard findByName(String name) { - Transaction tx = null; - Wizard result; - try (var session = getSessionFactory().openSession()) { - tx = session.beginTransaction(); - var criteria = session.createCriteria(persistentClass); - criteria.add(Restrictions.eq("name", name)); - result = (Wizard) criteria.uniqueResult(); - tx.commit(); - } catch (Exception e) { - if (tx != null) { - tx.rollback(); - } - throw e; + @Override + public Wizard findByName(String name) { + Transaction tx = null; + Wizard result; + try (var session = getSessionFactory().openSession()) { + tx = session.beginTransaction(); + var criteria = session.createCriteria(persistentClass); + criteria.add(Restrictions.eq("name", name)); + result = (Wizard) criteria.uniqueResult(); + tx.commit(); + } catch (Exception e) { + if (tx != null) { + tx.rollback(); + } + throw e; + } + return result; } - return result; - } } ``` @@ -140,88 +127,277 @@ Next we can look at the Service Layer, which in our case consists of a single `M ```java public interface MagicService { - List findAllWizards(); + List findAllWizards(); - List findAllSpellbooks(); + List findAllSpellbooks(); - List findAllSpells(); + List findAllSpells(); - List findWizardsWithSpellbook(String name); + List findWizardsWithSpellbook(String name); - List findWizardsWithSpell(String name); + List findWizardsWithSpell(String name); } public class MagicServiceImpl implements MagicService { - private final WizardDao wizardDao; - private final SpellbookDao spellbookDao; - private final SpellDao spellDao; - - public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { - this.wizardDao = wizardDao; - this.spellbookDao = spellbookDao; - this.spellDao = spellDao; - } - - @Override - public List findAllWizards() { - return wizardDao.findAll(); - } - - @Override - public List findAllSpellbooks() { - return spellbookDao.findAll(); - } - - @Override - public List findAllSpells() { - return spellDao.findAll(); - } - - @Override - public List findWizardsWithSpellbook(String name) { - var spellbook = spellbookDao.findByName(name); - return new ArrayList<>(spellbook.getWizards()); - } - - @Override - public List findWizardsWithSpell(String name) { - var spell = spellDao.findByName(name); - var spellbook = spell.getSpellbook(); - return new ArrayList<>(spellbook.getWizards()); - } + private final WizardDao wizardDao; + private final SpellbookDao spellbookDao; + private final SpellDao spellDao; + + public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { + this.wizardDao = wizardDao; + this.spellbookDao = spellbookDao; + this.spellDao = spellDao; + } + + @Override + public List findAllWizards() { + return wizardDao.findAll(); + } + + @Override + public List findAllSpellbooks() { + return spellbookDao.findAll(); + } + + @Override + public List findAllSpells() { + return spellDao.findAll(); + } + + @Override + public List findWizardsWithSpellbook(String name) { + var spellbook = spellbookDao.findByName(name); + return new ArrayList<>(spellbook.getWizards()); + } + + @Override + public List findWizardsWithSpell(String name) { + var spell = spellDao.findByName(name); + var spellbook = spell.getSpellbook(); + return new ArrayList<>(spellbook.getWizards()); + } } ``` -And finally we can show how the client `App` interacts with `MagicService` in the Service Layer. +And finally, we can show how the client `App` interacts with `MagicService` in the Service Layer. ```java - var service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); - LOGGER.info("Enumerating all wizards"); - service.findAllWizards().stream().map(Wizard::getName).forEach(LOGGER::info); - LOGGER.info("Enumerating all spellbooks"); - service.findAllSpellbooks().stream().map(Spellbook::getName).forEach(LOGGER::info); - LOGGER.info("Enumerating all spells"); - service.findAllSpells().stream().map(Spell::getName).forEach(LOGGER::info); - LOGGER.info("Find wizards with spellbook 'Book of Idores'"); - var wizardsWithSpellbook = service.findWizardsWithSpellbook("Book of Idores"); - wizardsWithSpellbook.forEach(w -> LOGGER.info("{} has 'Book of Idores'", w.getName())); - LOGGER.info("Find wizards with spell 'Fireball'"); - var wizardsWithSpell = service.findWizardsWithSpell("Fireball"); - wizardsWithSpell.forEach(w -> LOGGER.info("{} has 'Fireball'", w.getName())); +@Slf4j +public class App { + + public static final String BOOK_OF_IDORES = "Book of Idores"; + + public static void main(String[] args) { + // populate the in-memory database + initData(); + // query the data using the service + queryData(); + } + + public static void initData() { + // spells + var spell1 = new Spell("Ice dart"); + var spell2 = new Spell("Invisibility"); + var spell3 = new Spell("Stun bolt"); + var spell4 = new Spell("Confusion"); + var spell5 = new Spell("Darkness"); + var spell6 = new Spell("Fireball"); + var spell7 = new Spell("Enchant weapon"); + var spell8 = new Spell("Rock armour"); + var spell9 = new Spell("Light"); + var spell10 = new Spell("Bee swarm"); + var spell11 = new Spell("Haste"); + var spell12 = new Spell("Levitation"); + var spell13 = new Spell("Magic lock"); + var spell14 = new Spell("Summon hell bat"); + var spell15 = new Spell("Water walking"); + var spell16 = new Spell("Magic storm"); + var spell17 = new Spell("Entangle"); + var spellDao = new SpellDaoImpl(); + spellDao.persist(spell1); + spellDao.persist(spell2); + spellDao.persist(spell3); + spellDao.persist(spell4); + spellDao.persist(spell5); + spellDao.persist(spell6); + spellDao.persist(spell7); + spellDao.persist(spell8); + spellDao.persist(spell9); + spellDao.persist(spell10); + spellDao.persist(spell11); + spellDao.persist(spell12); + spellDao.persist(spell13); + spellDao.persist(spell14); + spellDao.persist(spell15); + spellDao.persist(spell16); + spellDao.persist(spell17); + + // spellbooks + var spellbookDao = new SpellbookDaoImpl(); + var spellbook1 = new Spellbook("Book of Orgymon"); + spellbookDao.persist(spellbook1); + spellbook1.addSpell(spell1); + spellbook1.addSpell(spell2); + spellbook1.addSpell(spell3); + spellbook1.addSpell(spell4); + spellbookDao.merge(spellbook1); + var spellbook2 = new Spellbook("Book of Aras"); + spellbookDao.persist(spellbook2); + spellbook2.addSpell(spell5); + spellbook2.addSpell(spell6); + spellbookDao.merge(spellbook2); + var spellbook3 = new Spellbook("Book of Kritior"); + spellbookDao.persist(spellbook3); + spellbook3.addSpell(spell7); + spellbook3.addSpell(spell8); + spellbook3.addSpell(spell9); + spellbookDao.merge(spellbook3); + var spellbook4 = new Spellbook("Book of Tamaex"); + spellbookDao.persist(spellbook4); + spellbook4.addSpell(spell10); + spellbook4.addSpell(spell11); + spellbook4.addSpell(spell12); + spellbookDao.merge(spellbook4); + var spellbook5 = new Spellbook(BOOK_OF_IDORES); + spellbookDao.persist(spellbook5); + spellbook5.addSpell(spell13); + spellbookDao.merge(spellbook5); + var spellbook6 = new Spellbook("Book of Opaen"); + spellbookDao.persist(spellbook6); + spellbook6.addSpell(spell14); + spellbook6.addSpell(spell15); + spellbookDao.merge(spellbook6); + var spellbook7 = new Spellbook("Book of Kihione"); + spellbookDao.persist(spellbook7); + spellbook7.addSpell(spell16); + spellbook7.addSpell(spell17); + spellbookDao.merge(spellbook7); + + // wizards + var wizardDao = new WizardDaoImpl(); + var wizard1 = new Wizard("Aderlard Boud"); + wizardDao.persist(wizard1); + wizard1.addSpellbook(spellbookDao.findByName("Book of Orgymon")); + wizard1.addSpellbook(spellbookDao.findByName("Book of Aras")); + wizardDao.merge(wizard1); + var wizard2 = new Wizard("Anaxis Bajraktari"); + wizardDao.persist(wizard2); + wizard2.addSpellbook(spellbookDao.findByName("Book of Kritior")); + wizard2.addSpellbook(spellbookDao.findByName("Book of Tamaex")); + wizardDao.merge(wizard2); + var wizard3 = new Wizard("Xuban Munoa"); + wizardDao.persist(wizard3); + wizard3.addSpellbook(spellbookDao.findByName(BOOK_OF_IDORES)); + wizard3.addSpellbook(spellbookDao.findByName("Book of Opaen")); + wizardDao.merge(wizard3); + var wizard4 = new Wizard("Blasius Dehooge"); + wizardDao.persist(wizard4); + wizard4.addSpellbook(spellbookDao.findByName("Book of Kihione")); + wizardDao.merge(wizard4); + } + + public static void queryData() { + var wizardDao = new WizardDaoImpl(); + var spellbookDao = new SpellbookDaoImpl(); + var spellDao = new SpellDaoImpl(); + var service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + LOGGER.info("Enumerating all wizards"); + service.findAllWizards().stream().map(Wizard::getName).forEach(LOGGER::info); + LOGGER.info("Enumerating all spellbooks"); + service.findAllSpellbooks().stream().map(Spellbook::getName).forEach(LOGGER::info); + LOGGER.info("Enumerating all spells"); + service.findAllSpells().stream().map(Spell::getName).forEach(LOGGER::info); + LOGGER.info("Find wizards with spellbook 'Book of Idores'"); + var wizardsWithSpellbook = service.findWizardsWithSpellbook(BOOK_OF_IDORES); + wizardsWithSpellbook.forEach(w -> LOGGER.info("{} has 'Book of Idores'", w.getName())); + LOGGER.info("Find wizards with spell 'Fireball'"); + var wizardsWithSpell = service.findWizardsWithSpell("Fireball"); + wizardsWithSpell.forEach(w -> LOGGER.info("{} has 'Fireball'", w.getName())); + } +} +``` + +The program output: + ``` +INFO [2024-05-27 09:16:40,668] com.iluwatar.servicelayer.app.App: Enumerating all wizards +INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Aderlard Boud +INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Anaxis Bajraktari +INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Xuban Munoa +INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Blasius Dehooge +INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Enumerating all spellbooks +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Orgymon +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Aras +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Kritior +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Tamaex +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Idores +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Opaen +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Kihione +INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Enumerating all spells +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Ice dart +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Invisibility +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Stun bolt +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Confusion +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Darkness +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Fireball +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Enchant weapon +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Rock armour +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Light +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Bee swarm +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Haste +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Levitation +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Magic lock +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Summon hell bat +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Water walking +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Magic storm +INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Entangle +INFO [2024-05-27 09:16:40,680] com.iluwatar.servicelayer.app.App: Find wizards with spellbook 'Book of Idores' +INFO [2024-05-27 09:16:40,680] com.iluwatar.servicelayer.app.App: Xuban Munoa has 'Book of Idores' +INFO [2024-05-27 09:16:40,681] com.iluwatar.servicelayer.app.App: Find wizards with spell 'Fireball' +INFO [2024-05-27 09:16:40,683] com.iluwatar.servicelayer.app.App: Aderlard Boud has 'Fireball' +``` + +## Detailed Explanation of Service Layer Pattern with Real-World Examples + +![Service Layer](./etc/service-layer.png "Service Layer") + +## When to Use the Service Layer Pattern in Java + +* Use when you need to separate business logic from presentation logic. +* Ideal for applications with complex business rules and workflows. +* Suitable for projects requiring a clear API for the presentation layer. + +## Real-World Applications of Service Layer Pattern in Java + +* Java EE applications where Enterprise JavaBeans (EJB) are used to implement the service layer. +* Spring Framework applications using the @Service annotation to denote service layer classes. +* Web applications that need to separate business logic from controller logic. + +## Benefits and Trade-offs of Service Layer Pattern + +Benefits: + +Implementing a Service Layer in Java + +* Promotes code reuse by encapsulating business logic in one place. +* Enhances testability by isolating business logic. +* Improves maintainability and flexibility of enterprise applications. +Trade-offs: -## Class diagram -![alt text](./etc/service-layer.png "Service Layer") +* May introduce additional complexity by adding another layer to the application. +* Can result in performance overhead due to the extra layer of abstraction. -## Applicability -Use the Service Layer pattern when +## Related Java Design Patterns -* You want to encapsulate domain logic under API -* You need to implement multiple interfaces with common logic and data +* [Facade](https://java-design-patterns.com/patterns/facade/): Simplifies interactions with complex subsystems by providing a unified interface. +* [DAO (Data Access Object)](https://java-design-patterns.com/patterns/dao/): Often used together with the Service Layer to handle data persistence. +* [MVC (Model-View-Controller)](https://java-design-patterns.com/patterns/model-view-controller/): The Service Layer can be used to encapsulate business logic in the model component. -## Credits +## References and Credits -* [Martin Fowler - Service Layer](http://martinfowler.com/eaaCatalog/serviceLayer.html) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Spring in Action](https://amzn.to/4asnpSG) +* [Service Layer (Martin Fowler)](http://martinfowler.com/eaaCatalog/serviceLayer.html) diff --git a/service-layer/etc/service-layer-architecture-diagram.png b/service-layer/etc/service-layer-architecture-diagram.png new file mode 100644 index 000000000000..f7e6438d0ad5 Binary files /dev/null and b/service-layer/etc/service-layer-architecture-diagram.png differ diff --git a/service-layer/pom.xml b/service-layer/pom.xml index 19e8fd377ecb..37795dae301a 100644 --- a/service-layer/pom.xml +++ b/service-layer/pom.xml @@ -34,9 +34,18 @@ service-layer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.hibernate hibernate-core + 6.6.11.Final com.h2database @@ -45,10 +54,17 @@ org.glassfish.jaxb jaxb-runtime + 4.0.5 javax.xml.bind jaxb-api + 2.4.0-b180830.0359 + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 org.junit.jupiter diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java index 947d1ea2ee06..9c37968e5b62 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java @@ -34,22 +34,21 @@ import com.iluwatar.servicelayer.wizard.WizardDaoImpl; import lombok.extern.slf4j.Slf4j; - /** * Service layer defines an application's boundary with a layer of services that establishes a set * of available operations and coordinates the application's response in each operation. * - *

    Enterprise applications typically require different kinds of interfaces to the data they - * store and the logic they implement: data loaders, user interfaces, integration gateways, and - * others. Despite their different purposes, these interfaces often need common interactions with - * the application to access and manipulate its data and invoke its business logic. The interactions - * may be complex, involving transactions across multiple resources and the coordination of several + *

    Enterprise applications typically require different kinds of interfaces to the data they store + * and the logic they implement: data loaders, user interfaces, integration gateways, and others. + * Despite their different purposes, these interfaces often need common interactions with the + * application to access and manipulate its data and invoke its business logic. The interactions may + * be complex, involving transactions across multiple resources and the coordination of several * responses to an action. Encoding the logic of the interactions separately in each interface * causes a lot of duplication. * - *

    The example application demonstrates interactions between a client ({@link App}) and a - * service ({@link MagicService}). The service is implemented with 3-layer architecture (entity, - * dao, service). For persistence the example uses in-memory H2 database which is populated on each + *

    The example application demonstrates interactions between a client ({@link App}) and a service + * ({@link MagicService}). The service is implemented with 3-layer architecture (entity, dao, + * service). For persistence the example uses in-memory H2 database which is populated on each * application startup. */ @Slf4j @@ -68,9 +67,7 @@ public static void main(String[] args) { queryData(); } - /** - * Initialize data. - */ + /** Initialize data. */ public static void initData() { // spells var spell1 = new Spell("Ice dart"); @@ -173,9 +170,7 @@ public static void initData() { wizardDao.merge(wizard4); } - /** - * Query the data. - */ + /** Query the data. */ public static void queryData() { var wizardDao = new WizardDaoImpl(); var spellbookDao = new SpellbookDaoImpl(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java index 6f3f662aa929..89b614d1b8b1 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java @@ -24,15 +24,10 @@ */ package com.iluwatar.servicelayer.common; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.MappedSuperclass; +import jakarta.persistence.MappedSuperclass; -/** - * Base class for entities. - */ +/** Base class for entities. */ @MappedSuperclass -@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class BaseEntity { /** @@ -62,5 +57,4 @@ public abstract class BaseEntity { * @param name The new name */ public abstract void setName(final String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java index 46981c76ddc2..e842d7b542b0 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java @@ -25,11 +25,11 @@ package com.iluwatar.servicelayer.common; import com.iluwatar.servicelayer.hibernate.HibernateUtil; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import java.lang.reflect.ParameterizedType; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.query.Query; @@ -42,8 +42,9 @@ public abstract class DaoBaseImpl implements Dao { @SuppressWarnings("unchecked") - protected Class persistentClass = (Class) ((ParameterizedType) getClass() - .getGenericSuperclass()).getActualTypeArguments()[0]; + protected Class persistentClass = + (Class) + ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; /* * Making this getSessionFactory() instead of getSession() so that it is the responsibility @@ -93,7 +94,7 @@ public void persist(E entity) { @Override public E merge(E entity) { Transaction tx = null; - E result = null; + E result; try (var session = getSessionFactory().openSession()) { tx = session.beginTransaction(); result = (E) session.merge(entity); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java index b1ef9f77f92d..b68ebc06b0ac 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java @@ -31,19 +31,14 @@ import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -/** - * Produces the Hibernate {@link SessionFactory}. - */ +/** Produces the Hibernate {@link SessionFactory}. */ @Slf4j public final class HibernateUtil { - /** - * The cached session factory. - */ + /** The cached session factory. */ private static volatile SessionFactory sessionFactory; - private HibernateUtil() { - } + private HibernateUtil() {} /** * Create the current session factory instance, create a new one when there is none yet. @@ -53,15 +48,17 @@ private HibernateUtil() { public static synchronized SessionFactory getSessionFactory() { if (sessionFactory == null) { try { - sessionFactory = new Configuration() - .addAnnotatedClass(Wizard.class) - .addAnnotatedClass(Spellbook.class) - .addAnnotatedClass(Spell.class) - .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") - .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") - .setProperty("hibernate.current_session_context_class", "thread") - .setProperty("hibernate.show_sql", "false") - .setProperty("hibernate.hbm2ddl.auto", "create-drop").buildSessionFactory(); + sessionFactory = + new Configuration() + .addAnnotatedClass(Wizard.class) + .addAnnotatedClass(Spellbook.class) + .addAnnotatedClass(Spell.class) + .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") + .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") + .setProperty("hibernate.current_session_context_class", "thread") + .setProperty("hibernate.show_sql", "false") + .setProperty("hibernate.hbm2ddl.auto", "create-drop") + .buildSessionFactory(); } catch (Throwable ex) { LOGGER.error("Initial SessionFactory creation failed.", ex); throw new ExceptionInInitializerError(ex); @@ -78,5 +75,4 @@ public static void dropSession() { getSessionFactory().close(); sessionFactory = null; } - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java index a5132286604e..33657f562d59 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java @@ -29,10 +29,7 @@ import com.iluwatar.servicelayer.wizard.Wizard; import java.util.List; - -/** - * Service interface. - */ +/** Service interface. */ public interface MagicService { List findAllWizards(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java index af527963ad11..0c0c3bd1fd67 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java @@ -33,18 +33,14 @@ import java.util.ArrayList; import java.util.List; -/** - * Service implementation. - */ +/** Service implementation. */ public class MagicServiceImpl implements MagicService { private final WizardDao wizardDao; private final SpellbookDao spellbookDao; private final SpellDao spellDao; - /** - * Constructor. - */ + /** Constructor. */ public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { this.wizardDao = wizardDao; this.spellbookDao = spellbookDao; diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java index da9837468b84..c806530dc028 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java @@ -26,19 +26,24 @@ import com.iluwatar.servicelayer.common.BaseEntity; import com.iluwatar.servicelayer.spellbook.Spellbook; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; -/** - * Spell entity. - */ +/** Spell entity. */ @Entity @Table(name = "SPELL") +@Getter +@Setter +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Spell extends BaseEntity { private String name; @@ -52,38 +57,13 @@ public class Spell extends BaseEntity { @JoinColumn(name = "SPELLBOOK_ID_FK", referencedColumnName = "SPELLBOOK_ID") private Spellbook spellbook; - public Spell() { - } + public Spell() {} public Spell(String name) { this(); this.name = name; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Spellbook getSpellbook() { - return spellbook; - } - - public void setSpellbook(Spellbook spellbook) { - this.spellbook = spellbook; - } - @Override public String toString() { return name; diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java index 08dda10486a2..675db02adddc 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java @@ -26,11 +26,8 @@ import com.iluwatar.servicelayer.common.Dao; -/** - * SpellDao interface. - */ +/** SpellDao interface. */ public interface SpellDao extends Dao { Spell findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java index 5ee39f4be733..0e238ccf8319 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java @@ -25,16 +25,13 @@ package com.iluwatar.servicelayer.spell; import com.iluwatar.servicelayer.common.DaoBaseImpl; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.hibernate.Transaction; import org.hibernate.query.Query; - -/** - * SpellDao implementation. - */ +/** SpellDao implementation. */ public class SpellDaoImpl extends DaoBaseImpl implements SpellDao { @Override diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java index 996d6018f784..8a893724c63a 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java @@ -27,23 +27,28 @@ import com.iluwatar.servicelayer.common.BaseEntity; import com.iluwatar.servicelayer.spell.Spell; import com.iluwatar.servicelayer.wizard.Wizard; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import java.util.HashSet; import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.ManyToMany; -import javax.persistence.OneToMany; -import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; -/** - * Spellbook entity. - */ +/** Spellbook entity. */ @Entity @Table(name = "SPELLBOOK") +@Getter +@Setter +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Spellbook extends BaseEntity { @Id @@ -69,38 +74,6 @@ public Spellbook(String name) { this.name = name; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Set getWizards() { - return wizards; - } - - public void setWizards(Set wizards) { - this.wizards = wizards; - } - - public Set getSpells() { - return spells; - } - - public void setSpells(Set spells) { - this.spells = spells; - } - public void addSpell(Spell spell) { spell.setSpellbook(this); spells.add(spell); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java index 84fd5dfb756f..c1c27ddf5b1a 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java @@ -26,11 +26,8 @@ import com.iluwatar.servicelayer.common.Dao; -/** - * SpellbookDao interface. - */ +/** SpellbookDao interface. */ public interface SpellbookDao extends Dao { Spellbook findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java index 463b06cdd369..9169f6ca80b5 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java @@ -25,16 +25,13 @@ package com.iluwatar.servicelayer.spellbook; import com.iluwatar.servicelayer.common.DaoBaseImpl; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.hibernate.Transaction; import org.hibernate.query.Query; - -/** - * SpellbookDao implementation. - */ +/** SpellbookDao implementation. */ public class SpellbookDaoImpl extends DaoBaseImpl implements SpellbookDao { @Override @@ -58,5 +55,4 @@ public Spellbook findByName(String name) { } return result; } - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java index 81db836fca62..44c3c4b4ad07 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java @@ -26,21 +26,26 @@ import com.iluwatar.servicelayer.common.BaseEntity; import com.iluwatar.servicelayer.spellbook.Spellbook; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; import java.util.HashSet; import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.ManyToMany; -import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; -/** - * Wizard entity. - */ +/** Wizard entity. */ @Entity @Table(name = "WIZARD") +@Getter +@Setter +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Wizard extends BaseEntity { @Id @@ -62,30 +67,6 @@ public Wizard(String name) { this.name = name; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Set getSpellbooks() { - return spellbooks; - } - - public void setSpellbooks(Set spellbooks) { - this.spellbooks = spellbooks; - } - public void addSpellbook(Spellbook spellbook) { spellbook.getWizards().add(this); spellbooks.add(spellbook); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java index 33dd3d3432fb..e8ed2229f62b 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java @@ -26,11 +26,8 @@ import com.iluwatar.servicelayer.common.Dao; -/** - * WizardDao interface. - */ +/** WizardDao interface. */ public interface WizardDao extends Dao { Wizard findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java index d35c9b0ca21b..e1dd3069d5fc 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java @@ -25,15 +25,13 @@ package com.iluwatar.servicelayer.wizard; import com.iluwatar.servicelayer.common.DaoBaseImpl; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.hibernate.Transaction; import org.hibernate.query.Query; -/** - * WizardDao implementation. - */ +/** WizardDao implementation. */ public class WizardDaoImpl extends DaoBaseImpl implements WizardDao { @Override diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java index c7d0b9139d99..11af9138d6f0 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.servicelayer.app; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.servicelayer.hibernate.HibernateUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } @AfterEach void tearDown() { HibernateUtil.dropSession(); } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java index f3f8a237751c..1bfeeb79aff3 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java @@ -36,39 +36,30 @@ import org.junit.jupiter.api.Test; /** - * Date: 12/28/15 - 10:53 PM Test for Base Data Access Objects + * Test for Base Data Access Objects * * @param Type of Base Entity * @param Type of Dao Base Implementation - * @author Jeroen Meulemeester */ public abstract class BaseDaoTest> { - /** - * The number of entities stored before each test - */ + /** The number of entities stored before each test */ private static final int INITIAL_COUNT = 5; - /** - * The unique id generator, shared between all entities - */ + /** The unique id generator, shared between all entities */ private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); - /** - * Factory, used to create new entity instances with the given name - */ + /** Factory, used to create new entity instances with the given name */ private final Function factory; - /** - * The tested data access object - */ + /** The tested data access object */ private final D dao; /** * Create a new test using the given factory and dao * * @param factory The factory, used to create new entity instances with the given name - * @param dao The tested data access object + * @param dao The tested data access object */ public BaseDaoTest(final Function factory, final D dao) { this.factory = factory; @@ -142,5 +133,4 @@ void testSetName() { assertEquals(expectedName, entity.getName()); assertEquals(expectedName, entity.toString()); } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java index c145f8e5f978..22794ea8d727 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java @@ -41,11 +41,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 12:06 AM - * - * @author Jeroen Meulemeester - */ +/** MagicServiceImplTest */ class MagicServiceImplTest { @Test @@ -94,11 +90,7 @@ void testFindAllSpells() { void testFindWizardsWithSpellbook() { final var bookname = "bookname"; final var spellbook = mock(Spellbook.class); - final var wizards = Set.of( - mock(Wizard.class), - mock(Wizard.class), - mock(Wizard.class) - ); + final var wizards = Set.of(mock(Wizard.class), mock(Wizard.class), mock(Wizard.class)); when(spellbook.getWizards()).thenReturn(wizards); final var spellbookDao = mock(SpellbookDao.class); @@ -107,7 +99,6 @@ void testFindWizardsWithSpellbook() { final var wizardDao = mock(WizardDao.class); final var spellDao = mock(SpellDao.class); - final var service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); verifyNoInteractions(wizardDao, spellbookDao, spellDao, spellbook); @@ -122,12 +113,8 @@ void testFindWizardsWithSpellbook() { } @Test - void testFindWizardsWithSpell() throws Exception { - final var wizards = Set.of( - mock(Wizard.class), - mock(Wizard.class), - mock(Wizard.class) - ); + void testFindWizardsWithSpell() { + final var wizards = Set.of(mock(Wizard.class), mock(Wizard.class), mock(Wizard.class)); final var spellbook = mock(Spellbook.class); when(spellbook.getWizards()).thenReturn(wizards); @@ -153,5 +140,4 @@ void testFindWizardsWithSpell() throws Exception { verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java index 751866c98a4a..616d80c12e15 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java @@ -30,11 +30,7 @@ import com.iluwatar.servicelayer.common.BaseDaoTest; import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 11:02 PM - * - * @author Jeroen Meulemeester - */ +/** SpellDaoImplTest */ class SpellDaoImplTest extends BaseDaoTest { public SpellDaoImplTest() { @@ -52,5 +48,4 @@ void testFindByName() { assertEquals(spell.getName(), spellByName.getName()); } } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java index 811756362ea7..5d7b208601e5 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java @@ -30,11 +30,7 @@ import com.iluwatar.servicelayer.common.BaseDaoTest; import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 11:44 PM - * - * @author Jeroen Meulemeester - */ +/** SpellbookDaoImplTest */ class SpellbookDaoImplTest extends BaseDaoTest { public SpellbookDaoImplTest() { @@ -52,5 +48,4 @@ void testFindByName() { assertEquals(book.getName(), spellByName.getName()); } } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java index 58bb772a606d..f21380f50f82 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java @@ -30,11 +30,7 @@ import com.iluwatar.servicelayer.common.BaseDaoTest; import org.junit.jupiter.api.Test; -/** - * Date: 12/28/15 - 11:46 PM - * - * @author Jeroen Meulemeester - */ +/** WizardDaoImplTest */ class WizardDaoImplTest extends BaseDaoTest { public WizardDaoImplTest() { @@ -52,5 +48,4 @@ void testFindByName() { assertEquals(spell.getName(), byName.getName()); } } - } diff --git a/service-locator/README.md b/service-locator/README.md index 7c3eaeee185d..68c803e70a14 100644 --- a/service-locator/README.md +++ b/service-locator/README.md @@ -1,41 +1,133 @@ --- -title: Service Locator -category: Architectural +title: "Service Locator Pattern in Java: Simplifying Service Access in Complex Systems" +shortTitle: Service Locator +description: "Master the Service Locator pattern in Java with our comprehensive guide. Learn how it simplifies dependency management in large-scale applications, promoting cleaner code and reusability." +category: Structural language: en tag: - - Game programming - - Performance + - Decoupling + - Dependency management + - Enterprise patterns + - Instantiation --- -## Intent -Encapsulate the processes involved in obtaining a service with a -strong abstraction layer. +## Also known as -## Class diagram -![alt text](./etc/service-locator.png "Service Locator") +* Service Registry -## Applicability -The service locator pattern is applicable whenever we want -to locate/fetch various services using JNDI which, typically, is a redundant -and expensive lookup. The service Locator pattern addresses this expensive -lookup by making use of caching techniques ie. for the very first time a -particular service is requested, the service Locator looks up in JNDI, fetched -the relevant service and then finally caches this service object. Now, further -lookups of the same service via Service Locator is done in its cache which -improves the performance of application to great extent. +## Intent of Service Locator Design Pattern -## Typical Use Case +The Java Service Locator pattern provides a method to decouple Java clients and services by using a central service registry. -* When network hits are expensive and time consuming -* Lookups of services are done quite frequently -* Large number of services are being used +## Detailed Explanation of Service Locator Pattern with Real-World Examples -## Consequences +Real-world example -* Violates Interface Segregation Principle (ISP) by providing pattern consumers with an access -to a number of services that they don't potentially need. -* Creates hidden dependencies that can break the clients at runtime. +> In a large hotel, the concierge desk acts as a Service Locator. When guests need a service, such as booking a restaurant reservation, finding transportation, or arranging a city tour, they do not directly seek out each service themselves. Instead, they go to the concierge desk, which locates and arranges the required services. This way, the guests are decoupled from the service providers and can rely on a central point to handle their requests, ensuring convenience and efficiency. -## Credits +In plain words -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +> The Service Locator pattern centralizes the logic for locating services, thereby decoupling clients from the concrete implementations of these services. + +Wikipedia says + +> The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task. Proponents of the pattern say the approach simplifies component-based applications where all dependencies are cleanly listed at the beginning of the whole application design, consequently making traditional dependency injection a more complex way of connecting objects. Critics of the pattern argue that it is an antipattern which obscures dependencies and makes software harder to test. + +## Programmatic Example of Service Locator Pattern in Java + +The Service Locator design pattern is used to abstract the processes involved in obtaining a service. It uses a central registry, the "service locator", which returns the necessary information to perform a task upon request. This Java design pattern is particularly useful in enterprise Java applications where services need centralized management. + +In this example, we have a `Service` interface and a `ServiceLocator` class. The `Service` interface defines the methods that all services must implement. The `ServiceLocator` class is responsible for retrieving and caching these services. + +```java +public interface Service { + String getName(); + + int getId(); + + void execute(); +} +``` + +The `Service` interface defines three methods: `getName()`, `getId()`, and `execute()`. Any class that implements this interface must provide an implementation for these methods. + +```java +public class App { + + public static final String JNDI_SERVICE_A = "jndi/serviceA"; + public static final String JNDI_SERVICE_B = "jndi/serviceB"; + + public static void main(String[] args) { + // Get the service from the ServiceLocator + var service = ServiceLocator.getService(JNDI_SERVICE_A); + // Execute the service + service.execute(); + // Get another service from the ServiceLocator + service = ServiceLocator.getService(JNDI_SERVICE_B); + // Execute the service + service.execute(); + // Get the service from the ServiceLocator again + service = ServiceLocator.getService(JNDI_SERVICE_A); + // Execute the service + service.execute(); + // Get the service from the ServiceLocator again + service = ServiceLocator.getService(JNDI_SERVICE_A); + // Execute the service + service.execute(); + } +} +``` + +In the `App` class, we use the `ServiceLocator` to get services by their names and then execute them. The `ServiceLocator` handles the details of looking up and caching the services. This way, the `App` class is decoupled from the concrete implementations of the services. + +Here is the output from running the example: + +``` +15:39:51.417 [main] INFO com.iluwatar.servicelocator.InitContext -- Looking up service A and creating new service for A +15:39:51.419 [main] INFO com.iluwatar.servicelocator.ServiceImpl -- Service jndi/serviceA is now executing with id 56 +15:39:51.420 [main] INFO com.iluwatar.servicelocator.InitContext -- Looking up service B and creating new service for B +15:39:51.420 [main] INFO com.iluwatar.servicelocator.ServiceImpl -- Service jndi/serviceB is now executing with id 196 +15:39:51.420 [main] INFO com.iluwatar.servicelocator.ServiceCache -- (cache call) Fetched service jndi/serviceA(56) from cache... ! +15:39:51.420 [main] INFO com.iluwatar.servicelocator.ServiceImpl -- Service jndi/serviceA is now executing with id 56 +15:39:51.420 [main] INFO com.iluwatar.servicelocator.ServiceCache -- (cache call) Fetched service jndi/serviceA(56) from cache... ! +15:39:51.420 [main] INFO com.iluwatar.servicelocator.ServiceImpl -- Service jndi/serviceA is now executing with id 56 +``` + +## When to Use the Service Locator Pattern in Java + +* Use when you want to decouple service creation from client classes to reduce dependencies and improve code maintainability. +* Applicable in large-scale enterprise applications where multiple services are used and dependencies need to be managed centrally. +* Suitable when service instances need to be reused or shared among multiple clients. + +## Real-World Applications of Service Locator Pattern in Java + +* Enterprise Java applications often use Service Locator to manage business services. +* Spring Framework uses a similar concept with its BeanFactory and ApplicationContext for dependency injection. +* EJB (Enterprise JavaBeans) uses the Service Locator pattern to find and access EJB components. + +## Benefits and Trade-offs of Service Locator Pattern + +Benefits: + +* Decouples client and service classes, reducing code dependencies. +* Centralizes service management, making it easier to configure and manage services. +* Promotes reuse of service instances, which can improve performance and resource utilization. + +Trade-offs: + +* Can introduce a single point of failure if the Service Locator itself fails. +* May add complexity to the codebase, especially in terms of configuration and maintenance. +* Potential performance overhead due to the lookup mechanism. + +## Related Java Design Patterns + +* [Factory](https://java-design-patterns.com/patterns/factory/): Both patterns deal with object creation but Service Locator focuses on locating services while Factory focuses on creating them. +* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): An alternative to Service Locator that injects dependencies directly into clients rather than having clients request them from a locator. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Service Locator often uses Singleton pattern to ensure a single instance of the locator. + +## References and Credits + +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Pattern-Oriented Software Architecture Volume 3: Patterns for Resource Management](https://amzn.to/4bnBcKZ) diff --git a/service-locator/pom.xml b/service-locator/pom.xml index 515f5eff7356..52fb0b932ba6 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -34,6 +34,14 @@ service-locator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/App.java b/service-locator/src/main/java/com/iluwatar/servicelocator/App.java index 633529b776fa..061cee97bdba 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/App.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/App.java @@ -31,10 +31,7 @@ * necessary to perform a certain task. * *

    In this example we use the Service locator pattern to lookup JNDI-services and cache them for - * subsequent requests. - *
    - * - * @author saifasif + * subsequent requests.
    */ public class App { diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java b/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java index c9bd7cac7a8b..2082a066b124 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java @@ -29,14 +29,12 @@ /** * For JNDI lookup of services from the web.xml. Will match name of the service name that is being * requested and return a newly created service object with the name - * - * @author saifasif */ @Slf4j public class InitContext { /** - * Perform the lookup based on the service name. The returned object will need to be casted into a + * Perform the lookup based on the service name. The returned object will need to be cast into a * {@link Service} * * @param serviceName a string diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java b/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java index 893f4bb5e8c1..876a6f727f01 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java @@ -26,15 +26,18 @@ /** * This is going to be the parent service interface which we will use to create our services. All - * services will have a

    • service name
    • unique id
    • execution work - * flow
    + * services will have a * - * @author saifasif + *
      + *
    • service name + *
    • unique id + *
    • execution work flow + *
    */ public interface Service { /* - * The human readable name of the service + * The human-readable name of the service */ String getName(); diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java index 64285522101c..112abe8d5f20 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java @@ -33,8 +33,6 @@ * the cache will be empty and thus any service that is being requested, will be created fresh and * then placed into the cache map. On next hit, if same service name will be requested, it will be * returned from the cache - * - * @author saifasif */ @Slf4j public class ServiceCache { diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java index c1c3cc6bed8a..12b6359018ae 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java @@ -30,8 +30,6 @@ * This is a single service implementation of a sample service. This is the actual service that will * process the request. The reference for this service is to be looked upon in the JNDI server that * can be set in the web.xml deployment descriptor - * - * @author saifasif */ @Slf4j public class ServiceImpl implements Service { @@ -39,9 +37,7 @@ public class ServiceImpl implements Service { private final String serviceName; private final int id; - /** - * Constructor. - */ + /** Constructor. */ public ServiceImpl(String serviceName) { // set the service name this.serviceName = serviceName; diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java index 781ec8277da0..53da47e89b2a 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java @@ -27,15 +27,12 @@ /** * The service locator module. Will fetch service from cache, otherwise creates a fresh service and * update cache - * - * @author saifasif */ public final class ServiceLocator { private static final ServiceCache serviceCache = new ServiceCache(); - private ServiceLocator() { - } + private ServiceLocator() {} /** * Fetch the service with the name param from the cache first, if no service is found, lookup the @@ -47,9 +44,7 @@ private ServiceLocator() { */ public static Service getService(String serviceJndiName) { var serviceObj = serviceCache.getService(serviceJndiName); - if (serviceObj != null) { - return serviceObj; - } else { + if (serviceObj == null) { /* * If we are unable to retrieve anything from cache, then lookup the service and add it in the * cache map @@ -59,7 +54,7 @@ public static Service getService(String serviceJndiName) { if (serviceObj != null) { // Only cache a service if it actually exists serviceCache.addService(serviceObj); } - return serviceObj; } + return serviceObj; } } diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java index 3622e4117208..282ad2e410a6 100644 --- a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.servicelocator; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java index 9c0c107d6ee9..90d6966b987b 100644 --- a/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java @@ -33,25 +33,17 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 19:07 PM - * - * @author Jeroen Meulemeester - */ +/** ServiceLocatorTest */ class ServiceLocatorTest { - /** - * Verify if we just receive 'null' when requesting a non-existing service - */ + /** Verify if we just receive 'null' when requesting a non-existing service */ @Test void testGetNonExistentService() { assertNull(ServiceLocator.getService("fantastic/unicorn/service")); assertNull(ServiceLocator.getService("another/fantastic/unicorn/service")); } - /** - * Verify if we get the same cached instance when requesting the same service twice - */ + /** Verify if we get the same cached instance when requesting the same service twice */ @Test void testServiceCache() { final var serviceNames = List.of("jndi/serviceA", "jndi/serviceB"); @@ -63,7 +55,5 @@ void testServiceCache() { assertTrue(service.getId() > 0); // The id is generated randomly, but the minimum value is '1' assertSame(service, ServiceLocator.getService(serviceName)); } - } - -} \ No newline at end of file +} diff --git a/service-stub/README.md b/service-stub/README.md new file mode 100644 index 000000000000..4058b96257d6 --- /dev/null +++ b/service-stub/README.md @@ -0,0 +1,153 @@ +--- +title: "Service Stub Pattern in Java: Simplifying Testing with Stub Implementations" +shortTitle: Service Stub +description: "Explore the Service Stub design pattern in Java using a Sentiment Analysis example. Learn how stub implementations provide dummy services to facilitate testing and development." +category: Structural +language: en +tag: + - Testing + - Decoupling + - Dummy Services + - Dependency Injection +--- + +## Also known as + +* Dummy Service +* Fake Service + +## Intent of Service Stub Pattern + +The Service Stub pattern provides a lightweight, dummy implementation of an external service to allow testing or development without relying on the real service, which may be unavailable, slow, or resource-intensive. + +## Detailed Explanation of Service Stub Pattern with Real-World Example + +Real-world example + +> In this example, we simulate a **Sentiment Analysis Service**. The real implementation delays execution and randomly decides the sentiment. The stub implementation, on the other hand, quickly returns predefined responses based on input text ("good", "bad", or neutral), making it ideal for testing. + +In plain words + +> Use a fake service to return predictable results without relying on external systems. + +Wikipedia says + +> A test stub is a dummy component used during testing to isolate behavior. + +## Programmatic Example of Service Stub Pattern in Java + +We define a `SentimentAnalysisService` interface and provide two implementations: + +1. **RealSentimentAnalysisServer**: Simulates a slow, random sentiment analysis system. +2. **StubSentimentAnalysisServer**: Returns a deterministic result based on input keywords. + +### Example Implementation +Both the real service and the stub implement the interface below. +```java +public interface SentimentAnalysisServer { + String analyzeSentiment(String text); +} +``` +The real sentiment analysis class returns a random response for a given input and simulates the runtime by sleeping +the Thread for 5 seconds. The Supplier\ allows injecting controlled sentiment values during testing, ensuring +deterministic outputs. +```java +public class RealSentimentAnalysisServer implements SentimentAnalysisServer { + + private final Supplier sentimentSupplier; + + public RealSentimentAnalysisServer(Supplier sentimentSupplier) { + this.sentimentSupplier = sentimentSupplier; + } + + public RealSentimentAnalysisServer() { + this(() -> new Random().nextInt(3)); + } + + @Override + public String analyzeSentiment(String text) { + int sentiment = sentimentSupplier.get(); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return sentiment == 0 ? "Positive" : sentiment == 1 ? "Negative" : "Neutral"; + } +} +``` +The stub implementation simulates the real sentiment analysis class and provides a deterministic output +for a given input. Additionally, its runtime is almost zero. +```java +public class StubSentimentAnalysisServer implements SentimentAnalysisServer { + + @Override + public String analyzeSentiment(String text) { + if (text.toLowerCase().contains("good")) { + return "Positive"; + } + else if (text.toLowerCase().contains("bad")) { + return "Negative"; + } + else { + return "Neutral"; + } + } +} + +``` +Here is the main function of the App class (entry point to the program) +```java +@Slf4j + public static void main(String[] args) { + LOGGER.info("Setting up the real sentiment analysis server."); + RealSentimentAnalysisServer realSentimentAnalysisServer = new RealSentimentAnalysisServer(); + String text = "This movie is soso"; + LOGGER.info("Analyzing input: {}", text); + String sentiment = realSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + + LOGGER.info("Setting up the stub sentiment analysis server."); + StubSentimentAnalysisServer stubSentimentAnalysisServer = new StubSentimentAnalysisServer(); + text = "This movie is so bad"; + LOGGER.info("Analyzing input: {}", text); + sentiment = stubSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + } +``` +## When to Use the Service Stub Pattern in Java + +Use the Service Stub pattern when: + +* Testing components that depend on external services. +* The real service is slow, unreliable, or unavailable. +* You need predictable, predefined responses. +* Developing offline without real service access. + +## Real-World Applications of Service Stub Pattern in Java + +* Simulating APIs (payments, recommendation systems) during testing. +* Bypassing external AI/ML models in tests. +* Simplifying integration testing. + +## Benefits and Trade-offs of Service Stub Pattern + +Benefits: + +* Reduces dependencies. +* Provides predictable behavior. +* Speeds up testing. + +Trade-offs: + +* Requires maintaining stub logic. +* May not fully represent real service behavior. + +## Related Java Design Patterns + +* [Proxy](https://java-design-patterns.com/patterns/proxy/) +* [Strategy](https://java-design-patterns.com/patterns/strategy/) + +## References and Credits + +* [Martin Fowler: Test Stubs](https://martinfowler.com/articles/mocksArentStubs.html) diff --git a/service-stub/pom.xml b/service-stub/pom.xml new file mode 100644 index 000000000000..1289578678c6 --- /dev/null +++ b/service-stub/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + service-stub + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + + \ No newline at end of file diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/App.java b/service-stub/src/main/java/com/iluwatar/servicestub/App.java new file mode 100644 index 000000000000..34227bc54614 --- /dev/null +++ b/service-stub/src/main/java/com/iluwatar/servicestub/App.java @@ -0,0 +1,66 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import lombok.extern.slf4j.Slf4j; + +/** + * A Service Stub is a dummy implementation of an external service used during development or + * testing. The purpose is to provide a lightweight "stub" when the real service may not always be + * available (or too slow to use during testing). + * + *

    This implementation simulates a simple sentiment analysis program, where a text is analyzed to + * deduce whether it is a positive, negative or neutral sentiment. The stub returns a response based + * on whether the analyzed text contains the words "good" or "bad", not accounting for stopwords or + * the underlying semantic of the text. + * + *

    The "real" sentiment analysis class simulates the processing time for the request by pausing + * the execution of the thread for 5 seconds. In the stub sentiment analysis class the response is + * immediate. In addition, the stub returns a deterministic output with regard to the input. This is + * extra useful for testing purposes. + */ +@Slf4j +public class App { + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + LOGGER.info("Setting up the real sentiment analysis server."); + RealSentimentAnalysisServer realSentimentAnalysisServer = new RealSentimentAnalysisServer(); + String text = "This movie is soso"; + LOGGER.info("Analyzing input: {}", text); + String sentiment = realSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + + LOGGER.info("Setting up the stub sentiment analysis server."); + StubSentimentAnalysisServer stubSentimentAnalysisServer = new StubSentimentAnalysisServer(); + text = "This movie is so bad"; + LOGGER.info("Analyzing input: {}", text); + sentiment = stubSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + } +} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java b/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java similarity index 51% rename from reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java rename to service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java index a652b8d5fd0f..dc7c174cbe54 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java +++ b/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java @@ -22,65 +22,45 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.reader.writer.lock; +package com.iluwatar.servicestub; -import java.util.concurrent.locks.Lock; -import lombok.extern.slf4j.Slf4j; +import java.util.Random; +import java.util.function.Supplier; /** - * Reader class, read when it acquired the read lock. + * Real implementation of SentimentAnalysisServer. Simulates random sentiment classification with + * processing delay. */ -@Slf4j -public class Reader implements Runnable { - - private final Lock readLock; - - private final String name; - - private final long readingTime; - +public class RealSentimentAnalysisServer implements SentimentAnalysisServer { /** - * Create new Reader. + * A real sentiment analysis implementation would analyze the input string using, e.g., NLP and + * determine whether the sentiment is positive, negative or neutral. Here we simply choose a + * random number to simulate this. The "model" may take some time to process the input and we + * simulate this by delaying the execution 5 seconds. * - * @param name - Name of the thread owning the reader - * @param readLock - Lock for this reader - * @param readingTime - amount of time (in milliseconds) for this reader to engage reading + * @param text the input string to analyze + * @return sentiment classification result (Positive, Negative, or Neutral) */ - public Reader(String name, Lock readLock, long readingTime) { - this.name = name; - this.readLock = readLock; - this.readingTime = readingTime; + private final Supplier sentimentSupplier; + + // Constructor + public RealSentimentAnalysisServer(Supplier sentimentSupplier) { + this.sentimentSupplier = sentimentSupplier; } - /** - * Create new Reader who reads for 250ms. - * - * @param name - Name of the thread owning the reader - * @param readLock - Lock for this reader - */ - public Reader(String name, Lock readLock) { - this(name, readLock, 250L); + @SuppressWarnings("java:S2245") // Safe use: Randomness is for simulation/testing only + public RealSentimentAnalysisServer() { + this(() -> new Random().nextInt(3)); } @Override - public void run() { - readLock.lock(); + public String analyzeSentiment(String text) { + int sentiment = sentimentSupplier.get(); try { - read(); + Thread.sleep(5000); } catch (InterruptedException e) { - LOGGER.info("InterruptedException when reading", e); Thread.currentThread().interrupt(); - } finally { - readLock.unlock(); } - } - - /** - * Simulate the read operation. - */ - public void read() throws InterruptedException { - LOGGER.info("{} begin", name); - Thread.sleep(readingTime); - LOGGER.info("{} finish after reading {}ms", name, readingTime); + return sentiment == 0 ? "Positive" : sentiment == 1 ? "Negative" : "Neutral"; } } diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/SentimentAnalysisServer.java b/service-stub/src/main/java/com/iluwatar/servicestub/SentimentAnalysisServer.java new file mode 100644 index 000000000000..51bd6394f30f --- /dev/null +++ b/service-stub/src/main/java/com/iluwatar/servicestub/SentimentAnalysisServer.java @@ -0,0 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +/** Sentiment analysis server interface to be implemented by sentiment analysis services. */ +public interface SentimentAnalysisServer { + /** + * Analyzes the sentiment of the input text and returns the result. + * + * @param text the input text to analyze + * @return sentiment classification result + */ + String analyzeSentiment(String text); +} diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/StubSentimentAnalysisServer.java b/service-stub/src/main/java/com/iluwatar/servicestub/StubSentimentAnalysisServer.java new file mode 100644 index 000000000000..95949c74fff9 --- /dev/null +++ b/service-stub/src/main/java/com/iluwatar/servicestub/StubSentimentAnalysisServer.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +/** + * Stub implementation of SentimentAnalysisServer. Returns deterministic sentiment based on input + * keywords. + */ +public class StubSentimentAnalysisServer implements SentimentAnalysisServer { + + /** + * Fake sentiment analyzer, always returns "Positive" if input string contains the word "good", + * "Negative" if the string contains "bad" and "Neutral" otherwise. + * + * @param text the input string to analyze + * @return sentiment classification result (Positive, Negative, or Neutral) + */ + @Override + public String analyzeSentiment(String text) { + if (text.toLowerCase().contains("good")) { + return "Positive"; + } else if (text.toLowerCase().contains("bad")) { + return "Negative"; + } else { + return "Neutral"; + } + } +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java b/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java similarity index 91% rename from reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java rename to service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java index 9dbaced75b5a..13d2d190dad2 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java +++ b/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java @@ -22,20 +22,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.reader.writer.lock; - -import org.junit.jupiter.api.Test; +package com.iluwatar.servicestub; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ -class AppTest { +import org.junit.jupiter.api.Test; +public class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/service-stub/src/test/java/com/iluwatar/servicestub/RealSentimentAnalysisServerTest.java b/service-stub/src/test/java/com/iluwatar/servicestub/RealSentimentAnalysisServerTest.java new file mode 100644 index 000000000000..0ae02182634c --- /dev/null +++ b/service-stub/src/test/java/com/iluwatar/servicestub/RealSentimentAnalysisServerTest.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class RealSentimentAnalysisServerTest { + + @Test + void testPositiveSentiment() { + RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 0); + assertEquals("Positive", server.analyzeSentiment("Test")); + } + + @Test + void testNegativeSentiment() { + RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 1); + assertEquals("Negative", server.analyzeSentiment("Test")); + } + + @Test + void testNeutralSentiment() { + RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 2); + assertEquals("Neutral", server.analyzeSentiment("Test")); + } +} diff --git a/service-stub/src/test/java/com/iluwatar/servicestub/StubSentimentAnalysisServerTest.java b/service-stub/src/test/java/com/iluwatar/servicestub/StubSentimentAnalysisServerTest.java new file mode 100644 index 000000000000..5a136633d656 --- /dev/null +++ b/service-stub/src/test/java/com/iluwatar/servicestub/StubSentimentAnalysisServerTest.java @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class StubSentimentAnalysisServerTest { + + private final StubSentimentAnalysisServer stub = new StubSentimentAnalysisServer(); + + @Test + void testPositiveSentiment() { + String result = stub.analyzeSentiment("This is a good product"); + assertEquals("Positive", result); + } + + @Test + void testNegativeSentiment() { + String result = stub.analyzeSentiment("This is a bad product"); + assertEquals("Negative", result); + } + + @Test + void testNeutralSentiment() { + String result = stub.analyzeSentiment("This product is average"); + assertEquals("Neutral", result); + } +} diff --git a/service-to-worker/README.md b/service-to-worker/README.md index bf37a2b9e93d..fb234342c79f 100644 --- a/service-to-worker/README.md +++ b/service-to-worker/README.md @@ -1,115 +1,133 @@ --- -title: Service to Worker +title: "Service to Worker Pattern in Java: Enhancing UI and Business Logic Integration" +shortTitle: Service to Worker +description: "Discover the Service to Worker design pattern in Java: an essential strategy for separating control flow and view management to enhance web application maintainability and scalability." category: Architectural language: en tag: -- Decoupling + - Business + - Decoupling + - Layered architecture + - Presentation + - Web development --- -## Intent +## Intent of Service to Worker Design Pattern -Combine a controller and dispatcher with views and helpers to handle client requests and prepare a dynamic presentation as the response. Controllers delegate content retrieval to helpers, which manage the population of the intermediate model for the view. A dispatcher is responsible for view management and navigation and can be encapsulated either within a controller or a separate component. +The Service to Worker design pattern in Java combines the Dispatcher View and Service Locator patterns to facilitate the separation of processing, control flow, and view management in web applications. -## Explanation +## Detailed Explanation of Service to Worker Pattern with Real-World Examples -Real world example +Real-world example -> In the classic MVC pattern, M refers to the business model, V refers to the user interface, and C is the controller. The purpose of using MVC is to separate the implementation code of M and V, so that the same program can use different forms of expression. In the Service to Worker pattern, the C directly controls the display of the V and can receive commands to control the dispatcher indirectly. The dispatcher stores different commands that can be used to modify the `model` with `action`s or to modify the display in the `view`s. +> Imagine a large restaurant chain with a central kitchen and multiple waitstaff. When a customer places an order, the waitstaff (Controller) takes the order and hands it over to the kitchen (Service). The kitchen then processes the order, prepares the dish, and hands it back to the waitstaff. The waitstaff finally delivers the dish to the customer (View). This scenario mirrors Java web applications using the Service to Worker pattern, where backend logic (like the kitchen) is separated from frontend interactions (like the waitstaff), improving focus and efficiency in design pattern implementation. In plain words -> Service to Worker Pattern uses Dispatcher to combine the controller and the view to handle client requests and prepare a dynamic presentation as the response. +> Separates the processing logic from the view in web applications to improve maintainability and scalability. -**Programmatic Example** +## Programmatic Example of Service to Worker Pattern in Java -We modified this pattern based on a classic design patterns [Model View Controller Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/model-view-controller) as the Class Diagram and two main classes `Dispatcher` and `Action` have been added. +The Service to Worker design pattern separates the processing logic from the view in web applications to improve maintainability and scalability. It combines the Dispatcher View and Service Locator patterns to facilitate the separation of processing, control flow, and view management in web applications. -The Dispatcher, which encapsulates worker and view selection based on request information and/or an internal navigation model. +In our example, we have a `GiantController` class, which acts as the controller in the Service to Worker pattern. It takes commands and updates the view. The `Dispatcher` class is responsible for performing actions and updating the view. + +Here is the `GiantController` class: ```java -public class Dispatcher { - - private final GiantView giantView; - private final List actions; - - /** - * Instantiates a new Dispatcher. - * - * @param giantView the giant view - */ - public Dispatcher(GiantView giantView) { - this.giantView = giantView; - this.actions = new ArrayList<>(); - } +public class GiantController { + + public Dispatcher dispatcher; - /** - * Add an action. - * - * @param action the action - */ - void addAction(Action action) { - actions.add(action); + public GiantController(Dispatcher dispatcher) { + this.dispatcher = dispatcher; } - /** - * Perform an action. - * - * @param s the s - * @param actionIndex the action index - */ - public void performAction(Command s, int actionIndex) { - actions.get(actionIndex).updateModel(s); + public void setCommand(Command s, int index) { + dispatcher.performAction(s, index); } - /** - * Update view. - * - * @param giantModel the giant model - */ public void updateView(GiantModel giantModel) { - giantView.displayGiant(giantModel); + dispatcher.updateView(giantModel); } } ``` -The Action (Worker), which can process user input and perform a specific update on the model. +In the `GiantController` class, we have a `setCommand` method that takes a `Command` and an index. This method is used to control the dispatcher. The `updateView` method is used to update the view with the provided `GiantModel`. + +The `App` class is the entry point of our application: ```java -public class Action { +public class App { + + public static void main(String[] args) { + var giant1 = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + var giant2 = new GiantModel("giant2", Health.DEAD, Fatigue.SLEEPING, Nourishment.STARVING); + var action1 = new Action(giant1); + var action2 = new Action(giant2); + var view = new GiantView(); + var dispatcher = new Dispatcher(view); + dispatcher.addAction(action1); + dispatcher.addAction(action2); + var controller = new GiantController(dispatcher); + + controller.updateView(giant1); + controller.updateView(giant2); + + controller.setCommand(new Command(Fatigue.SLEEPING, Health.HEALTHY, Nourishment.STARVING), 0); + controller.setCommand(new Command(Fatigue.ALERT, Health.HEALTHY, Nourishment.HUNGRY), 1); + + controller.updateView(giant1); + controller.updateView(giant2); + } +} +``` - private final GiantModel giant; +In the `main` method, we create two `GiantModel` instances, `giant1` and `giant2`, and two `Action` instances, `action1` and `action2`. We then create a `GiantView` instance and a `Dispatcher` instance. We add `action1` and `action2` to the `Dispatcher` and create a `GiantController` with the `Dispatcher`. We then update the view with `giant1` and `giant2`, set some commands, and update the view again. - /** - * Instantiates a new Action. - * - * @param giant the giant - */ - public Action(GiantModel giant) { - this.giant = giant; - } +Console output: - /** - * Update model based on command. - * - * @param command the command - */ - public void updateModel(Command command) { - setFatigue(command.getFatigue()); - setHealth(command.getHealth()); - setNourishment(command.getNourishment()); - } -} ``` +12:23:10.895 [main] INFO com.iluwatar.servicetoworker.GiantView -- Giant giant1, The giant looks healthy, alert and saturated. +12:23:10.897 [main] INFO com.iluwatar.servicetoworker.GiantView -- Giant giant2, The giant looks dead, sleeping and starving. +12:23:10.897 [main] INFO com.iluwatar.servicetoworker.GiantView -- Giant giant1, The giant looks healthy, sleeping and starving. +12:23:10.897 [main] INFO com.iluwatar.servicetoworker.GiantView -- Giant giant2, The giant looks healthy, alert and hungry. +``` + +This is a simple example of how the Service to Worker pattern can be implemented in a Java application. + +## When to Use the Service to Worker Pattern in Java + +* Use when you need to separate the controller logic from the view to improve code maintainability and enable team members to work on different parts of the application independently. +* Suitable for Java web applications that utilize MVC architecture. +* Appropriate for scenarios requiring complex request processing before displaying a view. + +## Real-World Applications of Service to Worker Pattern in Java + +* Java-based web frameworks like Struts and Spring MVC. +* Enterprise web applications requiring a clean separation between presentation logic and business logic. + +## Benefits and Trade-offs of Service to Worker Pattern + +Benefits: + +* Enhances code maintainability by separating concerns. +* Facilitates team collaboration by decoupling the controller and view components. +* Simplifies the addition of new views and modifications to existing ones. + +Trade-offs: -Therefore, this example leverages the Service to Worker pattern to increase functionality cohesion and improve the business logic. +* Increases the complexity of the application structure. +* May introduce additional overhead due to the layered architecture. +## Related Java Design Patterns -## Class diagram -![alt text](./etc/service-to-worker.png "Service to Worker") +* [Model-View-Controller (MVC)](https://java-design-patterns.com/patterns/model-view-controller/): Service to Worker is a specialized form of MVC, focusing on separating request handling and view management. +* [Front Controller](https://java-design-patterns.com/patterns/front-controller/): Often used in conjunction with Service to Worker to centralize request handling and routing. -## Applicability -- For the business logic of web development, the responsibility of a dispatcher component may be to translate the logical name login into the resource name of an appropriate view, such as login.jsp, and dispatch to that view. To accomplish this translation, the dispatcher may access resources such as an XML configuration file that specifies the appropriate view to display. +## References and Credits -## Credits -* [J2EE Design Patterns](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re05.html) -* [Core J2EE Patterns](http://corej2eepatterns.com/Patterns/ServiceToWorker.htm) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java index df6d80eb422a..3c033e8f8466 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java @@ -29,12 +29,10 @@ import com.iluwatar.model.view.controller.Nourishment; /** - * The type Command. - * Instantiates a new Command. + * The type Command. Instantiates a new Command. * - * @param fatigue the fatigue - * @param health the health + * @param fatigue the fatigue + * @param health the health * @param nourishment the nourishment */ -public record Command(Fatigue fatigue, Health health, Nourishment nourishment) { -} +public record Command(Fatigue fatigue, Health health, Nourishment nourishment) {} diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java index 784784478af2..3d5d957e89c5 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java @@ -34,8 +34,7 @@ */ public class Dispatcher { - @Getter - private final GiantView giantView; + @Getter private final GiantView giantView; private final List actions; /** @@ -60,7 +59,7 @@ void addAction(Action action) { /** * Perform an action. * - * @param s the s + * @param s the s * @param actionIndex the action index */ public void performAction(Command s, int actionIndex) { @@ -75,5 +74,4 @@ public void performAction(Command s, int actionIndex) { public void updateView(GiantModel giantModel) { giantView.displayGiant(giantModel); } - } diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java index 1b830b467e02..78add64f8616 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java @@ -24,8 +24,6 @@ */ package com.iluwatar.servicetoworker; -import lombok.Getter; - /** * GiantController can update the giant data and redraw it using the view. Singleton object that * intercepts all requests and performs common functions. @@ -46,7 +44,7 @@ public GiantController(Dispatcher dispatcher) { /** * Sets command to control the dispatcher. * - * @param s the s + * @param s the s * @param index the index */ public void setCommand(Command s, int index) { diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java index 202c25e5650c..69e4b834a56a 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java @@ -29,27 +29,23 @@ import com.iluwatar.model.view.controller.Nourishment; import lombok.Getter; -/** - * GiantModel contains the giant data. - */ +/** GiantModel contains the giant data. */ public class GiantModel { private final com.iluwatar.model.view.controller.GiantModel model; - @Getter - private final String name; + @Getter private final String name; /** * Instantiates a new Giant model. * - * @param name the name - * @param health the health - * @param fatigue the fatigue + * @param name the name + * @param health the health + * @param fatigue the fatigue * @param nourishment the nourishment */ GiantModel(String name, Health health, Fatigue fatigue, Nourishment nourishment) { this.name = name; - this.model = new com.iluwatar.model.view.controller.GiantModel(health, fatigue, - nourishment); + this.model = new com.iluwatar.model.view.controller.GiantModel(health, fatigue, nourishment); } /** @@ -103,8 +99,8 @@ void setNourishment(Nourishment nourishment) { @Override public String toString() { - return String - .format("Giant %s, The giant looks %s, %s and %s.", name, - model.getHealth(), model.getFatigue(), model.getNourishment()); + return String.format( + "Giant %s, The giant looks %s, %s and %s.", + name, model.getHealth(), model.getFatigue(), model.getNourishment()); } } diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java index 44d256cdbf13..667f4c889dbf 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * GiantView displays the giant. - */ +/** GiantView displays the giant. */ @Slf4j public class GiantView { diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java index 400010dd0759..66bfc625113b 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java @@ -31,18 +31,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Action test. - */ +/** The type Action test. */ class ActionTest { - /** - * Verify if the health value is set properly though the constructor and setter - */ + /** Verify if the health value is set properly though the constructor and setter */ @Test void testSetHealth() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Health.HEALTHY, model.getHealth()); var messageFormat = "Giant giant1, The giant looks %s, alert and saturated."; @@ -53,13 +49,11 @@ void testSetHealth() { } } - /** - * Verify if the fatigue level is set properly though the constructor and setter - */ + /** Verify if the fatigue level is set properly though the constructor and setter */ @Test void testSetFatigue() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Fatigue.ALERT, model.getFatigue()); var messageFormat = "Giant giant1, The giant looks healthy, %s and saturated."; @@ -70,13 +64,11 @@ void testSetFatigue() { } } - /** - * Verify if the nourishment level is set properly though the constructor and setter - */ + /** Verify if the nourishment level is set properly though the constructor and setter */ @Test void testSetNourishment() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Nourishment.SATURATED, model.getNourishment()); var messageFormat = "Giant giant1, The giant looks healthy, alert and %s."; @@ -87,13 +79,11 @@ void testSetNourishment() { } } - /** - * Test update model. - */ + /** Test update model. */ @Test void testUpdateModel() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Nourishment.SATURATED, model.getNourishment()); for (final var nourishment : Nourishment.values()) { diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java index 0dce37fca1f3..93b39ec28da4 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.servicetoworker; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java index 0551f9b96973..dafb12f12512 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java @@ -32,18 +32,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Dispatcher test. - */ +/** The type Dispatcher test. */ class DispatcherTest { - /** - * Test perform action. - */ + /** Test perform action. */ @Test void testPerformAction() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); @@ -64,13 +60,10 @@ void testPerformAction() { @Test void testUpdateView() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); assertDoesNotThrow(() -> dispatcher.updateView(model)); } } - - - diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java index ce7334b97b16..a4b9c35eb1e0 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java @@ -32,19 +32,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; - -/** - * The type Giant controller test. - */ +/** The type Giant controller test. */ class GiantControllerTest { - /** - * Test set command. - */ + /** Test set command. */ @Test void testSetCommand() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); @@ -57,17 +52,14 @@ void testSetCommand() { assertEquals(Nourishment.HUNGRY, model.getNourishment()); } - /** - * Test update view. - */ + /** Test update view. */ @Test void testUpdateView() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); GiantController giantController = new GiantController(dispatcher); assertDoesNotThrow(() -> giantController.updateView(model)); } - -} \ No newline at end of file +} diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java index b45ba99d4922..c6053e608166 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java @@ -31,18 +31,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Giant model test. - */ +/** The type Giant model test. */ class GiantModelTest { - /** - * Verify if the health value is set properly though the constructor and setter - */ + /** Verify if the health value is set properly though the constructor and setter */ @Test void testSetHealth() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Health.HEALTHY, model.getHealth()); var messageFormat = "Giant giant1, The giant looks %s, alert and saturated."; for (final var health : Health.values()) { @@ -52,13 +48,11 @@ void testSetHealth() { } } - /** - * Verify if the fatigue level is set properly though the constructor and setter - */ + /** Verify if the fatigue level is set properly though the constructor and setter */ @Test void testSetFatigue() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Fatigue.ALERT, model.getFatigue()); var messageFormat = "Giant giant1, The giant looks healthy, %s and saturated."; for (final var fatigue : Fatigue.values()) { @@ -68,13 +62,11 @@ void testSetFatigue() { } } - /** - * Verify if the nourishment level is set properly though the constructor and setter - */ + /** Verify if the nourishment level is set properly though the constructor and setter */ @Test void testSetNourishment() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Nourishment.SATURATED, model.getNourishment()); var messageFormat = "Giant giant1, The giant looks healthy, alert and %s."; for (final var nourishment : Nourishment.values()) { diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java index 8f5f3bb359e9..fd4f39be9e4c 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java @@ -31,18 +31,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Giant view test. - */ +/** The type Giant view test. */ class GiantViewTest { - /** - * Test display giant. - */ + /** Test display giant. */ @Test void testDispalyGiant() { - GiantModel giantModel = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + GiantModel giantModel = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); GiantView giantView = new GiantView(); assertDoesNotThrow(() -> giantView.displayGiant(giantModel)); } diff --git a/session-facade/README.md b/session-facade/README.md new file mode 100644 index 000000000000..20a093334e92 --- /dev/null +++ b/session-facade/README.md @@ -0,0 +1,157 @@ +--- +title: "Session Facade Pattern in Java: Simplifying Complex System Interfaces" +shortTitle: Session Facade +description: "Learn how to implement the Session Facade Design Pattern in Java to create a unified interface for complex subsystems. Simplify your code and enhance maintainability with practical examples and use cases." +category: Structural +language: en +tag: + - Abstraction + - API design + - Code simplification + - Decoupling + - Encapsulation + - Gang Of Four + - Interface +--- + +## Also known as + +* Session Facade + +## Intent of Session Facade Design Pattern + +Abstracting the underlying business object interactions by providing a service layer that exposes only the required interfaces + +## Detailed Explanation of Session Facade Pattern with Real-World Examples + +Real-world example + +> In an e-commerce website, users interact with several subsystems like product catalogs, shopping carts, +> payment services, and order management. The Session Facade pattern provides a simplified, centralized interface for these subsystems, +> allowing the client to interact with just a few high-level methods (e.g., addToCart(), placeOrder(), selectPaymentMethod()), instead of directly communicating with each subsystem, using a facade supports low coupling between classes and high cohesion within each service, allowing them to focus on their specific responsibilities. + +In plain words + +> The Session Facade design pattern is an excellent choice for decoupling complex components of the system that need to be interacting frequently. + +## Programmatic Example of Session Facade Pattern in Java + +The Session Facade design pattern is a structural design pattern that provides a simplified interface to a set of complex subsystems, reducing the complexity for the client. This pattern is particularly useful in situations where the client needs to interact with multiple services or systems but doesn’t need to know the internal workings of each service. + +In the context of an e-commerce website, imagine a system where users can browse products, add items to the shopping cart, process payments, and place orders. Instead of the client directly interacting with each individual service (cart, order, payment), the Session Facade provides a single, unified interface for these operations. + +Example Scenario: +In this example, the ShoppingFacade class manages interactions with three subsystems: the `CartService`, `OrderService`, and `PaymentService`. The client interacts with the facade to perform high-level operations like adding items to the cart, placing an order, and selecting a payment method. + +Here’s a simplified programmatic example: +```java +public class App { + public static void main(String[] args) { + ShoppingFacade shoppingFacade = new ShoppingFacade(); + shoppingFacade.addToCart(1); + shoppingFacade.order(); + shoppingFacade.selectPaymentMethod("cash"); + } +} +``` + +The `ShoppingFacade` acts as an intermediary that facilitates interaction between different services promoting low coupling between these services. +```java +public class ShoppingFacade { + + private final CartService cartService; + private final OrderService orderService; + private final PaymentService paymentService; + + public ShoppingFacade() { + Map productCatalog = new HashMap<>(); + productCatalog.put(1, new Product(1, "Wireless Mouse", 25.99, "Ergonomic wireless mouse with USB receiver.")); + productCatalog.put(2, new Product(2, "Gaming Keyboard", 79.99, "RGB mechanical gaming keyboard with programmable keys.")); + Map cart = new HashMap<>(); + cartService = new CartService(cart, productCatalog); + orderService = new OrderService(cart); + paymentService = new PaymentService(); + } + + public Map getCart() { + return this.cartService.getCart(); + } + + public void addToCart(int productId) { + this.cartService.addToCart(productId); + } + + + public void removeFromCart(int productId) { + this.cartService.removeFromCart(productId); + } + + public void order() { + this.orderService.order(); + } + + public Boolean isPaymentRequired() { + double total = this.orderService.getTotal(); + if (total == 0.0) { + LOGGER.info("No payment required"); + return false; + } + return true; + } + + public void processPayment(String method) { + Boolean isPaymentRequired = isPaymentRequired(); + if (Boolean.TRUE.equals(isPaymentRequired)) { + paymentService.selectPaymentMethod(method); + } + } +``` + +Console output for starting the `App` class's `main` method: + +``` +19:43:17.883 [main] INFO com.iluwatar.sessionfacade.CartService -- ID: 1 +Name: Wireless Mouse +Price: $25.99 +Description: Ergonomic wireless mouse with USB receiver. successfully added to the cart +19:43:17.910 [main] INFO com.iluwatar.sessionfacade.OrderService -- Client has chosen to order [ID: 1 +``` + +This is a basic example of the Session Facade design pattern. The actual implementation would depend on specific requirements of your application. + +## When to Use the Session Facade Pattern in Java + +* Use when building complex applications with multiple interacting services, where you want to simplify the interaction between various subsystems. +* Ideal for decoupling complex systems that need to interact but should not be tightly coupled. +* Suitable for applications where you need a single point of entry to interact with multiple backend services, like ecommerce platforms, booking systems, or order management systems. + +## Real-World Applications of Server Session Pattern in Java + +* Enterprise JavaBeans (EJB) +* Java EE (Jakarta EE) Applications + +## Benefits and Trade-offs of Server Session Pattern + + +* Simplifies client-side logic by providing a single entry point for complex operations across multiple services. +* Decouples components of the application, making them easier to maintain, test, and modify without affecting other parts of the system. +* Improves modularity by isolating the implementation details of subsystems from the client. +* Centralizes business logic in one place, making the code easier to manage and update. + +## Trade-offs: + +* Potential performance bottleneck: Since all requests pass through the facade, it can become a bottleneck if not optimized. +* Increased complexity: If the facade becomes too large or complex, it could counteract the modularity it aims to achieve. +* Single point of failure: If the facade encounters issues, it could affect the entire system's operation, making it crucial to handle errors and exceptions properly. + +## Related Java Design Patterns + +* [Facade](https://java-design-patterns.com/patterns/facade/): The Session Facade pattern is a specific application of the more general Facade pattern, which simplifies access to complex subsystems. +* [Command](https://java-design-patterns.com/patterns/command/): Useful for encapsulating requests and passing them to the session facade, which could then manage the execution order. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used to create a single instance of the session facade for managing the entire workflow of a subsystem. + +## References and Credits + +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/session-facade/etc/session-facade.urm.png b/session-facade/etc/session-facade.urm.png new file mode 100644 index 000000000000..71ab9a0481e0 Binary files /dev/null and b/session-facade/etc/session-facade.urm.png differ diff --git a/session-facade/etc/session-facade.urm.puml b/session-facade/etc/session-facade.urm.puml new file mode 100644 index 000000000000..83eed750ed0c --- /dev/null +++ b/session-facade/etc/session-facade.urm.puml @@ -0,0 +1,48 @@ +@startuml +package com.iluwatar.sessionfacade { + class App { + + App() + + main(args : String[]) {static} + } + class CartService { + - LOGGER : Logger {static} + - cart : List + - productCatalog : List + + CartService(cart : List, productCatalog : List) + + addToCart(productId : int) + + removeFromCart(productId : int) + } + class OrderService { + - LOGGER : Logger {static} + - cart : List + + OrderService(cart : List) + + order() + } + class PaymentService { + + LOGGER : Logger {static} + + PaymentService() + + cashPayment() + + creditCardPayment() + + selectPaymentMethod(method : String) + } + class ProductCatalogService { + - products : List + + ProductCatalogService(products : List) + } + class ShoppingFacade { + ~ cart : List + ~ cartService : CartService + ~ orderService : OrderService + ~ paymentService : PaymentService + ~ productCatalog : List + + ShoppingFacade() + + addToCart(productId : int) + + order() + + removeFromCart(productId : int) + + selectPaymentMethod(method : String) + } +} +ShoppingFacade --> "-cartService" CartService +ShoppingFacade --> "-paymentService" PaymentService +ShoppingFacade --> "-orderService" OrderService +@enduml \ No newline at end of file diff --git a/session-facade/pom.xml b/session-facade/pom.xml new file mode 100644 index 000000000000..6dada70f5f75 --- /dev/null +++ b/session-facade/pom.xml @@ -0,0 +1,78 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + session-facade + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.sessionfacade.App + + + + + + + + + \ No newline at end of file diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/App.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/App.java new file mode 100644 index 000000000000..cd9194aad20f --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/App.java @@ -0,0 +1,58 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +/** + * The main entry point of the application that demonstrates the usage of the ShoppingFacade to + * manage the shopping process using the Session Facade pattern. This class serves as a client that + * interacts with the simplified interface provided by the ShoppingFacade, which encapsulates + * complex interactions with the underlying business services. The ShoppingFacade acts as a session + * bean that coordinates the communication between multiple services, hiding their complexity and + * providing a single, unified API. + */ +public class App { + /** + * The entry point of the application. This method demonstrates how the ShoppingFacade, acting as + * a Session Facade, is used to: - Add items to the shopping cart - Process a payment - Place the + * order The session facade manages the communication between the individual services and + * simplifies the interactions for the client. + * + * @param args the input arguments + */ + public static void main(String[] args) { + ShoppingFacade shoppingFacade = new ShoppingFacade(); + + // Adding items to the shopping cart + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + + // Processing the payment with the chosen method + shoppingFacade.processPayment("cash"); + + // Finalizing the order + shoppingFacade.order(); + } +} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/CartService.java similarity index 50% rename from reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java rename to session-facade/src/main/java/com/iluwatar/sessionfacade/CartService.java index 762f8f281963..00cfbb540aeb 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/CartService.java @@ -22,66 +22,60 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.reader.writer.lock; -import java.util.concurrent.locks.Lock; +package com.iluwatar.sessionfacade; + +import java.util.Map; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** - * Writer class, write when it acquired the write lock. + * The type Cart service. Represents the cart entity, has add to cart and remove from cart methods */ @Slf4j -public class Writer implements Runnable { - - private final Lock writeLock; +public class CartService { + /** -- GETTER -- Gets cart. */ + @Getter private final Map cart; - private final String name; - - private final long writingTime; + private final Map productCatalog; /** - * Create new Writer who writes for 250ms. + * Instantiates a new Cart service. * - * @param name - Name of the thread owning the writer - * @param writeLock - Lock for this writer + * @param cart the cart + * @param productCatalog the product catalog */ - public Writer(String name, Lock writeLock) { - this(name, writeLock, 250L); + public CartService(Map cart, Map productCatalog) { + this.cart = cart; + this.productCatalog = productCatalog; } /** - * Create new Writer. + * Add to cart. * - * @param name - Name of the thread owning the writer - * @param writeLock - Lock for this writer - * @param writingTime - amount of time (in milliseconds) for this reader to engage writing + * @param productId the product id */ - public Writer(String name, Lock writeLock, long writingTime) { - this.name = name; - this.writeLock = writeLock; - this.writingTime = writingTime; - } - - - @Override - public void run() { - writeLock.lock(); - try { - write(); - } catch (InterruptedException e) { - LOGGER.info("InterruptedException when writing", e); - Thread.currentThread().interrupt(); - } finally { - writeLock.unlock(); + public void addToCart(int productId) { + Product product = productCatalog.get(productId); + if (product != null) { + cart.put(productId, product); + LOGGER.info("{} successfully added to the cart", product); + } else { + LOGGER.info("No product is found in catalog with id {}", productId); } } /** - * Simulate the write operation. + * Remove from cart. + * + * @param productId the product id */ - public void write() throws InterruptedException { - LOGGER.info("{} begin", name); - Thread.sleep(writingTime); - LOGGER.info("{} finished after writing {}ms", name, writingTime); + public void removeFromCart(int productId) { + Product product = cart.remove(productId); // Remove product from cart + if (product != null) { + LOGGER.info("{} successfully removed from the cart", product); + } else { + LOGGER.info("No product is found in cart with id {}", productId); + } } } diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/OrderService.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/OrderService.java new file mode 100644 index 000000000000..a67dda0aae5d --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/OrderService.java @@ -0,0 +1,78 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * The OrderService class is responsible for finalizing a customer's order. It includes a method to + * calculate the total cost of the order, which follows the information expert principle from GRASP + * by assigning the responsibility of total calculation to this service. Additionally, it provides a + * method to complete the order, which empties the client's shopping cart once the order is + * finalized. + */ +@Slf4j +public class OrderService { + private final Map cart; + + /** + * Instantiates a new Order service. + * + * @param cart the cart + */ + public OrderService(Map cart) { + this.cart = cart; + } + + /** Order. */ + public void order() { + Double total = getTotal(); + if (!this.cart.isEmpty()) { + LOGGER.info( + "Client has chosen to order {} with total {}", cart, String.format("%.2f", total)); + this.completeOrder(); + } else { + LOGGER.info("Client's shopping cart is empty"); + } + } + + /** + * Gets total. + * + * @return the total + */ + public double getTotal() { + final double[] total = {0.0}; + this.cart.forEach((key, product) -> total[0] += product.price()); + return total[0]; + } + + /** Complete order. */ + public void completeOrder() { + this.cart.clear(); + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/PaymentService.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/PaymentService.java new file mode 100644 index 000000000000..92b6bf5fa5bf --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/PaymentService.java @@ -0,0 +1,67 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The PaymentService class is responsible for handling the selection and processing of different + * payment methods. It provides functionality to select a payment method (cash or credit card) and + * process the corresponding payment option. The class uses logging to inform the client of the + * selected payment method. It includes methods to: - Select the payment method based on the + * client's choice. - Process cash payments through the `cashPayment()` method. - Process credit + * card payments through the `creditCardPayment()` method. + */ +public class PaymentService { + /** The constant LOGGER. */ + public static Logger LOGGER = LoggerFactory.getLogger(PaymentService.class); + + /** + * Select payment method. + * + * @param method the method + */ + public void selectPaymentMethod(String method) { + if (method.equals("cash")) { + cashPayment(); + } else if (method.equals("credit")) { + creditCardPayment(); + } else { + LOGGER.info("Unspecified payment method type"); + } + } + + /** Cash payment. */ + public void cashPayment() { + LOGGER.info("Client have chosen cash payment option"); + } + + /** Credit card payment. */ + public void creditCardPayment() { + LOGGER.info("Client have chosen credit card payment option"); + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/Product.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/Product.java new file mode 100644 index 000000000000..ff4b8afd1868 --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/Product.java @@ -0,0 +1,34 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +/** The type Product. */ +public record Product(int id, String name, double price, String description) { + @Override + public String toString() { + return "ID: " + id + "\nName: " + name + "\nPrice: $" + price + "\nDescription: " + description; + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/ProductCatalogService.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/ProductCatalogService.java new file mode 100644 index 000000000000..b892c32e9050 --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/ProductCatalogService.java @@ -0,0 +1,59 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import java.util.Map; + +/** + * The type ProductCatalogService. This class manages a catalog of products. It holds a map of + * products, where each product is identified by a unique ID. The class provides functionality to + * access and manage the products in the catalog. + */ +public class ProductCatalogService { + + private final Map products; + + /** + * Instantiates a new ProductCatalogService. + * + * @param products the map of products to be used by this service + */ + public ProductCatalogService(Map products) { + this.products = products; + } + + // Additional methods to interact with products can be added here, for example: + + /** + * Retrieves a product by its ID. + * + * @param id the product ID + * @return the product corresponding to the ID + */ + public Product getProductById(int id) { + return products.get(id); + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/ShoppingFacade.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/ShoppingFacade.java new file mode 100644 index 000000000000..cdfe44a7c292 --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/ShoppingFacade.java @@ -0,0 +1,121 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * The ShoppingFacade class provides a simplified interface for clients to interact with the + * shopping system. It acts as a facade to handle operations related to a shopping cart, order + * processing, and payment. Responsibilities: - Add products to the shopping cart. - Remove products + * from the shopping cart. - Retrieve the current shopping cart. - Finalize an order by calling the + * order service. - Check if a payment is required based on the order total. - Process payment using + * different payment methods (e.g., cash, credit card). The ShoppingFacade class delegates + * operations to the following services: - CartService: Manages the cart and product catalog. - + * OrderService: Handles the order finalization process and calculation of the total. - + * PaymentService: Handles the payment processing based on the selected payment method. + */ +@Slf4j +public class ShoppingFacade { + private final CartService cartService; + private final OrderService orderService; + private final PaymentService paymentService; + + /** Instantiates a new Shopping facade. */ + public ShoppingFacade() { + Map productCatalog = new HashMap<>(); + productCatalog.put( + 1, new Product(1, "Wireless Mouse", 25.99, "Ergonomic wireless mouse with USB receiver.")); + productCatalog.put( + 2, + new Product( + 2, "Gaming Keyboard", 79.99, "RGB mechanical gaming keyboard with programmable keys.")); + Map cart = new HashMap<>(); + cartService = new CartService(cart, productCatalog); + orderService = new OrderService(cart); + paymentService = new PaymentService(); + } + + /** + * Gets cart. + * + * @return the cart + */ + public Map getCart() { + return this.cartService.getCart(); + } + + /** + * Add to cart. + * + * @param productId the product id + */ + public void addToCart(int productId) { + this.cartService.addToCart(productId); + } + + /** + * Remove from cart. + * + * @param productId the product id + */ + public void removeFromCart(int productId) { + this.cartService.removeFromCart(productId); + } + + /** Order. */ + public void order() { + this.orderService.order(); + } + + /** + * Is payment required boolean. + * + * @return the boolean + */ + public Boolean isPaymentRequired() { + double total = this.orderService.getTotal(); + if (total == 0.0) { + LOGGER.info("No payment required"); + return false; + } + return true; + } + + /** + * Process payment. + * + * @param method the method + */ + public void processPayment(String method) { + Boolean isPaymentRequired = isPaymentRequired(); + if (Boolean.TRUE.equals(isPaymentRequired)) { + paymentService.selectPaymentMethod(method); + } + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java new file mode 100644 index 000000000000..707fd944efb8 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** The type App test. */ +public class AppTest { + + /** Should execute application without exception. */ + @org.junit.jupiter.api.Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/CartServiceTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/CartServiceTest.java new file mode 100644 index 000000000000..ef42fcabd963 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/CartServiceTest.java @@ -0,0 +1,84 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +/** The type Cart service test. */ +@Slf4j +class CartServiceTest { + + private CartService cartService; + private Map cart; + + /** Sets up. */ + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + cart = new HashMap<>(); + Map productCatalog = new HashMap<>(); + productCatalog.put(1, new Product(1, "Product A", 2.0, "any description")); + productCatalog.put(2, new Product(2, "Product B", 300.0, "a watch")); + cartService = new CartService(cart, productCatalog); + } + + /** Test add to cart. */ + @Test + void testAddToCart() { + cartService.addToCart(1); + assertEquals(1, cart.size()); + assertEquals("Product A", cart.get(1).name()); + } + + /** Test remove from cart. */ + @Test + void testRemoveFromCart() { + cartService.addToCart(1); + assertEquals(1, cart.size()); + cartService.removeFromCart(1); + assertTrue(cart.isEmpty()); + } + + /** Test add to cart with invalid product id. */ + @Test + void testAddToCartWithInvalidProductId() { + cartService.addToCart(999); + assertTrue(cart.isEmpty()); + } + + /** Test remove from cart with invalid product id. */ + @Test + void testRemoveFromCartWithInvalidProductId() { + cartService.removeFromCart(999); + assertTrue(cart.isEmpty()); + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java new file mode 100644 index 000000000000..609cc3559bd2 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java @@ -0,0 +1,70 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +/** The type Payment service test. */ +class PaymentServiceTest { + private PaymentService paymentService; + private OrderService orderService; + private Logger mockLogger; + + /** Sets up. */ + @BeforeEach + void setUp() { + paymentService = new PaymentService(); + mockLogger = mock(Logger.class); + paymentService.LOGGER = mockLogger; + } + + /** Test select cash payment method. */ + @Test + void testSelectCashPaymentMethod() { + String method = "cash"; + paymentService.selectPaymentMethod(method); + verify(mockLogger).info("Client have chosen cash payment option"); + } + + /** Test select credit card payment method. */ + @Test + void testSelectCreditCardPaymentMethod() { + String method = "credit"; + paymentService.selectPaymentMethod(method); + verify(mockLogger).info("Client have chosen credit card payment option"); + } + + /** Test select unspecified payment method. */ + @Test + void testSelectUnspecifiedPaymentMethod() { + String method = "cheque"; + paymentService.selectPaymentMethod(method); + verify(mockLogger).info("Unspecified payment method type"); + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/ProductTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/ProductTest.java new file mode 100644 index 000000000000..3da660ca8f73 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/ProductTest.java @@ -0,0 +1,68 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** The type Product test. */ +public class ProductTest { + /** Test product creation. */ + @Test + public void testProductCreation() { + int id = 1; + String name = "Product A"; + double price = 200.0; + String description = "a description"; + Product product = new Product(id, name, price, description); + assertEquals(id, product.id()); + assertEquals(name, product.name()); + assertEquals(price, product.price()); + assertEquals(description, product.description()); + } + + /** Test equals and hash code. */ + @Test + public void testEqualsAndHashCode() { + Product product1 = new Product(1, "Product A", 99.99, "a description"); + Product product2 = new Product(1, "Product A", 99.99, "a description"); + Product product3 = new Product(2, "Product B", 199.99, "a description"); + + assertEquals(product1, product2); + assertNotEquals(product1, product3); + assertEquals(product1.hashCode(), product2.hashCode()); + assertNotEquals(product1.hashCode(), product3.hashCode()); + } + + /** Test to string. */ + @Test + public void testToString() { + Product product = new Product(1, "Product A", 99.99, "a description"); + String toStringResult = product.toString(); + assertTrue(toStringResult.contains("Product A")); + assertTrue(toStringResult.contains("99.99")); + } +} diff --git a/priority-queue/src/test/java/com/iluwatar/priority/queue/PriorityMessageQueueTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/ShoppingFacadeTest.java similarity index 51% rename from priority-queue/src/test/java/com/iluwatar/priority/queue/PriorityMessageQueueTest.java rename to session-facade/src/test/java/com/iluwatar/sessionfacade/ShoppingFacadeTest.java index d539e4e8ad31..b77e60296ce0 100644 --- a/priority-queue/src/test/java/com/iluwatar/priority/queue/PriorityMessageQueueTest.java +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/ShoppingFacadeTest.java @@ -22,54 +22,56 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.priority.queue; +package com.iluwatar.sessionfacade; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test case for order of messages - */ -class PriorityMessageQueueTest { +/** Unit tests for ShoppingFacade. */ +class ShoppingFacadeTest { + private ShoppingFacade shoppingFacade; - @Test - void remove() { - var stringPriorityMessageQueue = new PriorityMessageQueue<>(new String[2]); - var pushMessage = "test"; - stringPriorityMessageQueue.add(pushMessage); - assertEquals(stringPriorityMessageQueue.remove(), pushMessage); + @BeforeEach + void setUp() { + shoppingFacade = new ShoppingFacade(); } @Test - void add() { - var stringPriorityMessageQueue = new PriorityMessageQueue<>(new Integer[2]); - stringPriorityMessageQueue.add(1); - stringPriorityMessageQueue.add(5); - stringPriorityMessageQueue.add(10); - stringPriorityMessageQueue.add(3); - assertEquals(10, (int) stringPriorityMessageQueue.remove()); + void testAddToCart() { + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + Map cart = shoppingFacade.getCart(); + assertEquals(2, cart.size(), "Cart should contain two items."); + assertEquals( + "Wireless Mouse", cart.get(1).name(), "First item in the cart should be 'Wireless Mouse'."); + assertEquals( + "Gaming Keyboard", + cart.get(2).name(), + "Second item in the cart should be 'Gaming Keyboard'."); } @Test - void isEmpty() { - var stringPriorityMessageQueue = new PriorityMessageQueue<>(new Integer[2]); - assertTrue(stringPriorityMessageQueue.isEmpty()); - stringPriorityMessageQueue.add(1); - stringPriorityMessageQueue.remove(); - assertTrue(stringPriorityMessageQueue.isEmpty()); + void testRemoveFromCart() { + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + shoppingFacade.removeFromCart(1); + Map cart = shoppingFacade.getCart(); + assertEquals(1, cart.size(), "Cart should contain one item after removal."); + assertEquals( + "Gaming Keyboard", cart.get(2).name(), "Remaining item should be 'Gaming Keyboard'."); } @Test - void testEnsureSize() { - var stringPriorityMessageQueue = new PriorityMessageQueue<>(new Integer[2]); - assertTrue(stringPriorityMessageQueue.isEmpty()); - stringPriorityMessageQueue.add(1); - stringPriorityMessageQueue.add(2); - stringPriorityMessageQueue.add(2); - stringPriorityMessageQueue.add(3); - assertEquals(3, (int) stringPriorityMessageQueue.remove()); + void testOrder() { + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + shoppingFacade.processPayment("cash"); + shoppingFacade.order(); + assertTrue(shoppingFacade.getCart().isEmpty(), "Cart should be empty after placing the order."); } -} \ No newline at end of file +} diff --git a/sharding/README.md b/sharding/README.md index fffc53f6a08f..fef24abee7b6 100644 --- a/sharding/README.md +++ b/sharding/README.md @@ -1,27 +1,247 @@ --- -title: Sharding -category: Behavioral +title: "Sharding Pattern in Java: Mastering Horizontal Partitioning to Boost Application Throughput" +shortTitle: Sharding +description: "Explore how Sharding, or horizontal partitioning, enhances database scalability and performance. This guide covers the Sharding pattern's intent, implementation, and benefits for Java developers." +category: Data access language: en -tag: - - Performance - - Cloud distributed ---- - -## Intent -Sharding pattern means divide the data store into horizontal partitions or shards. Each shard has the same schema, but holds its own distinct subset of the data. -A shard is a data store in its own right (it can contain the data for many entities of different types), running on a server acting as a storage node. - -## Class diagram -![alt text](./etc/sharding.urm.png "Sharding pattern class diagram") - -## Applicability -This pattern offers the following benefits: - -- You can scale the system out by adding further shards running on additional storage nodes. -- A system can use off the shelf commodity hardware rather than specialized (and expensive) computers for each storage node. -- You can reduce contention and improved performance by balancing the workload across shards. -- In the cloud, shards can be located physically close to the users that will access the data. - -## Credits - -* [Sharding pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/sharding) +tag: + - Data access + - Optimization + - Scalability +--- + +## Also known as + +* Data Partitioning +* Horizontal Partitioning + +## Intent of Sharding Design Pattern + +Sharding, a pivotal Java design pattern, significantly boosts database scalability and performance through horizontal partitioning. + +## Detailed Explanation of Sharding Pattern with Real-World Examples + +Real-world example + +> Consider a large e-commerce website with millions of users and transactions. To handle the immense amount of data and ensure the system remains responsive, the user data is sharded across multiple database servers. For instance, users with IDs ending in 0-4 might be stored on one server, and those ending in 5-9 on another. This distribution allows the system to handle a higher load by parallelizing read and write operations across multiple servers. + +In plain words + +> Separates the processing logic from the view in web applications to improve maintainability and scalability. + +Wikipedia says + +> Horizontal partitioning is a database design principle whereby rows of a database table are held separately, rather than being split into columns (which is what normalization and vertical partitioning do, to differing extents). Each partition forms part of a shard, which may in turn be located on a separate database server or physical location. +> +> There are numerous advantages to the horizontal partitioning approach. Since the tables are divided and distributed into multiple servers, the total number of rows in each table in each database is reduced. This reduces index size, which generally improves search performance. A database shard can be placed on separate hardware, and multiple shards can be placed on multiple machines. This enables a distribution of the database over a large number of machines, greatly improving performance. In addition, if the database shard is based on some real-world segmentation of the data (e.g., European customers v. American customers) then it may be possible to infer the appropriate shard membership easily and automatically, and query only the relevant shard. + +## Programmatic Example of Sharding Pattern in Java + +Sharding is a type of database partitioning that separates very large databases into smaller, faster, more easily managed parts called data shards. The word shard means a small part of a whole. In software architecture, it refers to a horizontal partition in a database or search engine. Each individual partition is referred to as a shard or database shard. + +In the given code, we have a `ShardManager` class that manages the shards. It has two subclasses `HashShardManager` and `RangeShardManager` that implement different sharding strategies. The `Shard` class represents a shard that stores data. The `Data` class represents the data to be stored in the shards. + +The `ShardManager` is an abstract class that provides the basic structure for managing shards. It has a `storeData` method that stores data in a shard and an `allocateShard` method that determines which shard to store the data in. The `allocateShard` method is abstract and must be implemented by subclasses. + +```java +public abstract class ShardManager { + protected Map shardMap = new HashMap<>(); + + public abstract int storeData(Data data); + + protected abstract int allocateShard(Data data); +} +``` + +The `HashShardManager` is a subclass of `ShardManager` that implements a hash-based sharding strategy. In the `allocateShard` method, it calculates a hash of the data key and uses it to determine the shard to store the data in. + +```java +public class HashShardManager extends ShardManager { + + @Override + protected int allocateShard(Data data) { + var shardCount = shardMap.size(); + var hash = data.getKey() % shardCount; + return hash == 0 ? hash + shardCount : hash; + } +} +``` + +The `RangeShardManager` is another subclass of `ShardManager` that implements a range-based sharding strategy. In the `allocateShard` method, it uses the data type to determine the shard to store the data in. + +```java +public class RangeShardManager extends ShardManager { + + @Override + protected int allocateShard(Data data) { + var type = data.getType(); + return switch (type) { + case TYPE_1 -> 1; + case TYPE_2 -> 2; + case TYPE_3 -> 3; + }; + } +} +``` + +The `Shard` class represents a shard. It has a `storeData` method that stores data in the shard and a `getDataById` method that retrieves data from the shard by its id. + +```java +public class Shard { + + @Getter + private final int id; + + private final Map dataStore; + + public Shard(final int id) { + this.id = id; + this.dataStore = new HashMap<>(); + } + + public void storeData(Data data) { + dataStore.put(data.getKey(), data); + } + + public Data getDataById(final int id) { + return dataStore.get(id); + } +} +``` + +The `Data` class represents the data to be stored in the shards. It has a key, a value, and a type. + +```java +@Getter +@Setter +public class Data { + + private int key; + + private String value; + + private DataType type; + + public Data(final int key, final String value, final DataType type) { + this.key = key; + this.value = value; + this.type = type; + } + + enum DataType { + TYPE_1, TYPE_2, TYPE_3 + } +} +``` + +This is the `main` function of the example demonstrating three different sharding strategies: lookup, range, and hash. Each strategy determines which shard to store the data in a different way. The lookup strategy uses a lookup table, the range strategy uses the data type, and the hash strategy uses a hash of the data key. + +```java +public static void main(String[] args) { + + var data1 = new Data(1, "data1", Data.DataType.TYPE_1); + var data2 = new Data(2, "data2", Data.DataType.TYPE_2); + var data3 = new Data(3, "data3", Data.DataType.TYPE_3); + var data4 = new Data(4, "data4", Data.DataType.TYPE_1); + + var shard1 = new Shard(1); + var shard2 = new Shard(2); + var shard3 = new Shard(3); + + var manager = new LookupShardManager(); + manager.addNewShard(shard1); + manager.addNewShard(shard2); + manager.addNewShard(shard3); + manager.storeData(data1); + manager.storeData(data2); + manager.storeData(data3); + manager.storeData(data4); + + shard1.clearData(); + shard2.clearData(); + shard3.clearData(); + + var rangeShardManager = new RangeShardManager(); + rangeShardManager.addNewShard(shard1); + rangeShardManager.addNewShard(shard2); + rangeShardManager.addNewShard(shard3); + rangeShardManager.storeData(data1); + rangeShardManager.storeData(data2); + rangeShardManager.storeData(data3); + rangeShardManager.storeData(data4); + + shard1.clearData(); + shard2.clearData(); + shard3.clearData(); + + var hashShardManager = new HashShardManager(); + hashShardManager.addNewShard(shard1); + hashShardManager.addNewShard(shard2); + hashShardManager.addNewShard(shard3); + hashShardManager.storeData(data1); + hashShardManager.storeData(data2); + hashShardManager.storeData(data3); + hashShardManager.storeData(data4); + + shard1.clearData(); + shard2.clearData(); + shard3.clearData(); +} +``` + +Finally, here is the program output: + +``` +18:32:26.503 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=1, value='data1', type=TYPE_1} is stored in Shard 2 +18:32:26.505 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=2, value='data2', type=TYPE_2} is stored in Shard 2 +18:32:26.505 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=3, value='data3', type=TYPE_3} is stored in Shard 1 +18:32:26.505 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=4, value='data4', type=TYPE_1} is stored in Shard 1 +18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=1, value='data1', type=TYPE_1} is stored in Shard 1 +18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=2, value='data2', type=TYPE_2} is stored in Shard 2 +18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=3, value='data3', type=TYPE_3} is stored in Shard 3 +18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=4, value='data4', type=TYPE_1} is stored in Shard 1 +18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=1, value='data1', type=TYPE_1} is stored in Shard 1 +18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=2, value='data2', type=TYPE_2} is stored in Shard 2 +18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=3, value='data3', type=TYPE_3} is stored in Shard 3 +18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=4, value='data4', type=TYPE_1} is stored in Shard 1 +``` + +## When to Use the Sharding Pattern in Java + +* Use when dealing with large datasets that exceed the capacity of a single database. +* Ideal for Java applications requiring robust scalability, sharding improves performance by distributing database loads effectively. +* Useful for applications requiring high availability and fault tolerance. +* Effective in environments where read and write operations can be parallelized across shards. + +## Real-World Applications of Sharding Pattern in Java + +* Distributed databases such as Apache Cassandra, MongoDB, and Amazon DynamoDB. +* Large-scale web applications like social networks, e-commerce platforms, and SaaS products. + +## Benefits and Trade-offs of Sharding Pattern + +Benefits: + +* Enhances performance by distributing load. +* Improves scalability by allowing horizontal scaling. +* Increases availability and fault tolerance by isolating failures to individual shards. + +Trade-offs: + +* Complexity in managing and maintaining multiple shards. +* Potential challenges in rebalancing shards as data grows. +* Increased latency for cross-shard queries. + +## Related Java Design Patterns + +* [Caching](https://java-design-patterns.com/patterns/caching/): Can be used in conjunction with sharding to further improve performance. +* [Data Mapper](https://java-design-patterns.com/patterns/data-mapper/): Helps in abstracting and encapsulating the details of database interactions, which can be complex in a sharded environment. +* [Repository](https://java-design-patterns.com/patterns/repository/): Provides a way to manage data access logic centrally, which is useful when dealing with multiple shards. +* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): Can be used to find and interact with different shards in a distributed system. + +## References and Credits + +* [Building Scalable Web Sites: Building, Scaling, and Optimizing the Next Generation of Web Applications](https://amzn.to/4bqpejJ) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3y6yv1z) +* [NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence](https://amzn.to/3UWvdpw) +* [Sharding pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/sharding) diff --git a/sharding/pom.xml b/sharding/pom.xml index 2edd3a458ba7..8461ccf5fad0 100644 --- a/sharding/pom.xml +++ b/sharding/pom.xml @@ -34,6 +34,14 @@ 4.0.0 sharding + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/sharding/src/main/java/com/iluwatar/sharding/App.java b/sharding/src/main/java/com/iluwatar/sharding/App.java index f8c9ce2f85b9..56b0edb2f2db 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/App.java +++ b/sharding/src/main/java/com/iluwatar/sharding/App.java @@ -85,5 +85,4 @@ public static void main(String[] args) { shard2.clearData(); shard3.clearData(); } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/Data.java b/sharding/src/main/java/com/iluwatar/sharding/Data.java index 699e58108dc2..c0b4188d182a 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/Data.java +++ b/sharding/src/main/java/com/iluwatar/sharding/Data.java @@ -24,9 +24,12 @@ */ package com.iluwatar.sharding; -/** - * Basic data structure for each tuple stored in data shards. - */ +import lombok.Getter; +import lombok.Setter; + +/** Basic data structure for each tuple stored in data shards. */ +@Getter +@Setter public class Data { private int key; @@ -37,8 +40,9 @@ public class Data { /** * Constructor of Data class. + * * @param key data key - * @param value data vlue + * @param value data value * @param type data type */ public Data(final int key, final String value, final DataType type) { @@ -47,39 +51,14 @@ public Data(final int key, final String value, final DataType type) { this.type = type; } - public int getKey() { - return key; - } - - public void setKey(final int key) { - this.key = key; - } - - public String getValue() { - return value; - } - - public void setValue(final String value) { - this.value = value; - } - - public DataType getType() { - return type; - } - - public void setType(DataType type) { - this.type = type; - } - enum DataType { - TYPE_1, TYPE_2, TYPE_3 + TYPE_1, + TYPE_2, + TYPE_3 } @Override public String toString() { - return "Data {" + "key=" - + key + ", value='" + value - + '\'' + ", type=" + type + '}'; + return "Data {" + "key=" + key + ", value='" + value + '\'' + ", type=" + type + '}'; } } - diff --git a/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java index 090ee2037856..c0d481a0d950 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java @@ -27,10 +27,9 @@ import lombok.extern.slf4j.Slf4j; /** - * ShardManager with hash strategy. The purpose of this strategy is to reduce the - * chance of hot-spots in the data. It aims to distribute the data across the shards - * in a way that achieves a balance between the size of each shard and the average - * load that each shard will encounter. + * ShardManager with hash strategy. The purpose of this strategy is to reduce the chance of + * hot-spots in the data. It aims to distribute the data across the shards in a way that achieves a + * balance between the size of each shard and the average load that each shard will encounter. */ @Slf4j public class HashShardManager extends ShardManager { @@ -40,7 +39,7 @@ public int storeData(Data data) { var shardId = allocateShard(data); var shard = shardMap.get(shardId); shard.storeData(data); - LOGGER.info(data.toString() + " is stored in Shard " + shardId); + LOGGER.info(data + " is stored in Shard " + shardId); return shardId; } @@ -50,5 +49,4 @@ protected int allocateShard(Data data) { var hash = data.getKey() % shardCount; return hash == 0 ? hash + shardCount : hash; } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java index 73ae966699a3..f9a5c05d6d37 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java @@ -27,13 +27,11 @@ import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; -import java.util.Random; import lombok.extern.slf4j.Slf4j; /** - * ShardManager with lookup strategy. In this strategy the sharding logic implements - * a map that routes a request for data to the shard that contains that data by using - * the shard key. + * ShardManager with lookup strategy. In this strategy the sharding logic implements a map that + * routes a request for data to the shard that contains that data by using the shard key. */ @Slf4j public class LookupShardManager extends ShardManager { @@ -46,7 +44,7 @@ public int storeData(Data data) { lookupMap.put(data.getKey(), shardId); var shard = shardMap.get(shardId); shard.storeData(data); - LOGGER.info(data.toString() + " is stored in Shard " + shardId); + LOGGER.info(data + " is stored in Shard " + shardId); return shardId; } @@ -60,5 +58,4 @@ protected int allocateShard(Data data) { return new SecureRandom().nextInt(shardCount - 1) + 1; } } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java index 3cf665ca9e42..093b29910f73 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java @@ -38,7 +38,7 @@ public int storeData(Data data) { var shardId = allocateShard(data); var shard = shardMap.get(shardId); shard.storeData(data); - LOGGER.info(data.toString() + " is stored in Shard " + shardId); + LOGGER.info(data + " is stored in Shard " + shardId); return shardId; } @@ -49,8 +49,6 @@ protected int allocateShard(Data data) { case TYPE_1 -> 1; case TYPE_2 -> 2; case TYPE_3 -> 3; - default -> -1; }; } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/Shard.java b/sharding/src/main/java/com/iluwatar/sharding/Shard.java index 67fba94426a5..7ed2cda14e31 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/Shard.java +++ b/sharding/src/main/java/com/iluwatar/sharding/Shard.java @@ -26,13 +26,12 @@ import java.util.HashMap; import java.util.Map; +import lombok.Getter; -/** - * The Shard class stored data in a HashMap. - */ +/** The Shard class stored data in a HashMap. */ public class Shard { - private final int id; + @Getter private final int id; private final Map dataStore; @@ -52,9 +51,4 @@ public void clearData() { public Data getDataById(final int id) { return dataStore.get(id); } - - public int getId() { - return id; - } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java index a87b58d04918..8283b2c6781e 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java @@ -28,9 +28,7 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; -/** - * Abstract class for ShardManager. - */ +/** Abstract class for ShardManager. */ @Slf4j public abstract class ShardManager { @@ -44,8 +42,8 @@ public ShardManager() { * Add a provided shard instance to shardMap. * * @param shard new shard instance. - * @return {@code true} if succeed to add the new instance. - * {@code false} if the shardId is already existed. + * @return {@code true} if succeed to add the new instance. {@code false} if the shardId is + * already existed. */ public boolean addNewShard(final Shard shard) { var shardId = shard.getId(); @@ -97,5 +95,4 @@ public Shard getShardById(final int shardId) { * @return id of shard that the data should be stored */ protected abstract int allocateShard(final Data data); - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/AppTest.java b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java index fc3c59073beb..c6d68aad17ab 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/AppTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for App class. - */ +/** Unit tests for App class. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java index b5b5202dc2c3..d01d3406e153 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java @@ -29,16 +29,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for HashShardManager class. - */ +/** Unit tests for HashShardManager class. */ class HashShardManagerTest { private HashShardManager hashShardManager; - /** - * Initialize hashShardManager instance. - */ + /** Initialize hashShardManager instance. */ @BeforeEach void setup() { hashShardManager = new HashShardManager(); @@ -56,5 +52,4 @@ void testStoreData() { hashShardManager.storeData(data); assertEquals(data, hashShardManager.getShardById(1).getDataById(1)); } - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java index a8b8ab1da55d..d4b3c02dc34d 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java @@ -31,16 +31,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for LookupShardManager class. - */ +/** Unit tests for LookupShardManager class. */ class LookupShardManagerTest { private LookupShardManager lookupShardManager; - /** - * Initialize lookupShardManager instance. - */ + /** Initialize lookupShardManager instance. */ @BeforeEach void setup() { lookupShardManager = new LookupShardManager(); diff --git a/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java index 299adc622bac..dbc8a68ba292 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java @@ -29,16 +29,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for RangeShardManager class. - */ +/** Unit tests for RangeShardManager class. */ class RangeShardManagerTest { private RangeShardManager rangeShardManager; - /** - * Initialize rangeShardManager instance. - */ + /** Initialize rangeShardManager instance. */ @BeforeEach void setup() { rangeShardManager = new RangeShardManager(); @@ -56,5 +52,4 @@ void testStoreData() { rangeShardManager.storeData(data); assertEquals(data, rangeShardManager.getShardById(1).getDataById(1)); } - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java index f5da39ddb34a..fe7cf09c046d 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java @@ -32,16 +32,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for ShardManager class. - */ +/** Unit tests for ShardManager class. */ class ShardManagerTest { private ShardManager shardManager; - /** - * Initialize shardManager instance. - */ + /** Initialize shardManager instance. */ @BeforeEach void setup() { shardManager = new TestShardManager(); diff --git a/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java index 7b18440a8c8c..81312e9e0270 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java @@ -32,10 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -/** - * Unit tests for Shard class. - */ +/** Unit tests for Shard class. */ class ShardTest { private Data data; @@ -60,7 +57,6 @@ void testStoreData() { } catch (NoSuchFieldException | IllegalAccessException e) { fail("Fail to modify field access."); } - } @Test diff --git a/single-table-inheritance/README.md b/single-table-inheritance/README.md index 541ad1423e24..0ed1f6eadae1 100644 --- a/single-table-inheritance/README.md +++ b/single-table-inheritance/README.md @@ -1,157 +1,261 @@ --- -title: Single Table Inheritance Pattern -category: Structural +title: "Single Table Inheritance Pattern in Java: Streamlining Object Mapping with Unified Table Structures" +shortTitle: Single Table Inheritance +description: "Discover how the Single Table Inheritance pattern simplifies database schema in Java applications. Learn its use, benefits, and implementation in our comprehensive guide." +category: Data access language: en tag: - Data access + - Encapsulation + - Persistence + - Polymorphism --- -## Single Table Inheritance(STI) +## Also known as -## Intent +* Class Table Inheritance +* STI -Represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes. +## Intent of Single Table Inheritance Design Pattern -## Explanation +Single Table Inheritance pattern simplifies database schema in Java applications. + +Streamline the storage of an inheritance hierarchy in a single database table, where rows represent objects of different classes and columns represent the union of all attributes. + +## Detailed Explanation of Single Table Inheritance Pattern with Real-World Examples Real-world example -> There can be many different types of vehicles in this world but all of them -> come under the single umbrella of Vehicle +> Imagine a library system where books, magazines, and DVDs are all stored in a single inventory table. This table includes columns for attributes common to all items, such as `Title`, `Author`, `PublicationDate`, and `ItemType`, as well as columns specific to certain types, like `ISBN` for books, `IssueNumber` for magazines, and `Duration` for DVDs. Each row represents an item in the library, with some columns left null depending on the item type. This setup simplifies the database schema by keeping all inventory items in one table, akin to Single Table Inheritance in a database context. In plain words -> It maps each instance of class in an inheritance tree into a single table. +> Single Table Inheritance stores an entire class hierarchy in a single database table, using a type discriminator column to distinguish between different subclasses. Wikipedia says -> Single table inheritance is a way to emulate object-oriented inheritance in a relational database. -> When mapping from a database table to an object in an object-oriented language, -> a field in the database identifies what class in the hierarchy the object belongs to. -> All fields of all the classes are stored in the same table, hence the name "Single Table Inheritance". +> Single table inheritance is a way to emulate object-oriented inheritance in a relational database. When mapping from a database table to an object in an object-oriented language, a field in the database identifies what class in the hierarchy the object belongs to. All fields of all the classes are stored in the same table, hence the name "Single Table Inheritance". -**Programmatic Example** +## Programmatic Example of Single Table Inheritance Pattern in Java -Baeldung - Hibernate Inheritance +Single Table Inheritance is a design pattern that maps an inheritance hierarchy of classes to a single database table. Each row in the table represents an instance of a class in the hierarchy. A special discriminator column is used to identify the class to which each row belongs. -> We can define the strategy we want to use by adding the @Inheritance annotation to the superclass: +This pattern is useful when classes in an inheritance hierarchy are not significantly different in terms of fields and behavior. It simplifies the database schema and can improve performance by avoiding joins. -```java -@Entity -@Inheritance(strategy = InheritanceType.SINGLE_TABLE) -public class MyProduct { - @Id - private long productId; - private String name; +Let's see how this pattern is implemented in the provided code. + +The base class in our hierarchy is `Vehicle`. This class has common properties that all vehicles share, such as `manufacturer` and `model`. - // constructor, getters, setters +```java +public abstract class Vehicle { + private String manufacturer; + private String model; + // other common properties... } ``` -The identifier of the entities is also defined in the superclass. +We have two subclasses, `PassengerVehicle` and `TransportVehicle`, which extend `Vehicle` and add additional properties. + +```java +public abstract class PassengerVehicle extends Vehicle { + private int noOfPassengers; + // other properties specific to passenger vehicles... +} -Then we can add the subclass entities: +public abstract class TransportVehicle extends Vehicle { + private int loadCapacity; + // other properties specific to transport vehicles... +} +``` + +Finally, we have concrete classes like `Car` and `Truck` that extend `PassengerVehicle` and `TransportVehicle` respectively. ```java -@Entity -public class Book extends MyProduct { - private String author; +public class Car extends PassengerVehicle { + private int trunkCapacity; + // other properties specific to cars... +} + +public class Truck extends TransportVehicle { + private int towingCapacity; + // other properties specific to trucks... } ``` +In this example, we're using Hibernate as our ORM. We map our class hierarchy to a single table using the `@Entity` and `@Inheritance` annotations. + ```java @Entity -public class Pen extends MyProduct { - private String color; +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +public abstract class Vehicle { + // properties... } ``` -Discriminator Values -- Since the records for all entities will be in the same table, Hibernate needs a way to differentiate between them. +The `@DiscriminatorColumn` annotation is used to specify the discriminator column in the table. This column will hold the class name for each row. -- By default, this is done through a discriminator column called DTYPE that has the name of the entity as a value. +```java +@DiscriminatorColumn(name = "vehicle_type") +public abstract class Vehicle { + // properties... +} +``` -- To customize the discriminator column, we can use the @DiscriminatorColumn annotation: +Each subclass specifies its discriminator value using the `@DiscriminatorValue` annotation. ```java -@Entity(name="products") -@Inheritance(strategy = InheritanceType.SINGLE_TABLE) -@DiscriminatorColumn(name="product_type", - discriminatorType = DiscriminatorType.INTEGER) -public class MyProduct { - // ... +@DiscriminatorValue("CAR") +public class Car extends PassengerVehicle { + // properties... +} + +@DiscriminatorValue("TRUCK") +public class Truck extends TransportVehicle { + // properties... } ``` -- Here we’ve chosen to differentiate MyProduct subclass entities by an integer column called product_type. -- Next, we need to tell Hibernate what value each subclass record will have for the product_type column: +The `VehicleService` class provides methods for saving and retrieving vehicles. When we save a `Car` or `Truck` object, Hibernate will automatically set the discriminator column to the appropriate value. When we retrieve a vehicle, Hibernate will use the discriminator column to instantiate the correct class. ```java -@Entity -@DiscriminatorValue("1") -public class Book extends MyProduct { - // ... +public class VehicleService { + public Vehicle saveVehicle(Vehicle vehicle) { + // save vehicle to database... + } + + public Vehicle getVehicle(Long id) { + // retrieve vehicle from database... + } + + public List getAllVehicles() { + // retrieve all vehicles from database... + } } ``` + +Finally, here is the Spring Boot application that runs our example. + ```java -@Entity -@DiscriminatorValue("2") -public class Pen extends MyProduct { - // ... +@SpringBootApplication +@AllArgsConstructor +public class SingleTableInheritance implements CommandLineRunner { + + //Autowiring the VehicleService class to execute the business logic methods + private final VehicleService vehicleService; + + public static void main(String[] args) { + SpringApplication.run(SingleTableInheritance.class, args); + } + + @Override + public void run(String... args) { + + Logger log = LoggerFactory.getLogger(SingleTableInheritance.class); + + log.info("Saving Vehicles :- "); + + // Saving Car to DB as a Vehicle + Vehicle vehicle1 = new Car("Tesla", "Model S", 4, 825); + Vehicle car1 = vehicleService.saveVehicle(vehicle1); + log.info("Vehicle 1 saved : {}", car1); + + // Saving Truck to DB as a Vehicle + Vehicle vehicle2 = new Truck("Ford", "F-150", 3325, 14000); + Vehicle truck1 = vehicleService.saveVehicle(vehicle2); + log.info("Vehicle 2 saved : {}\n", truck1); + + + log.info("Fetching Vehicles :- "); + + // Fetching the Car from DB + Car savedCar1 = (Car) vehicleService.getVehicle(vehicle1.getVehicleId()); + log.info("Fetching Car1 from DB : {}", savedCar1); + + // Fetching the Truck from DB + Truck savedTruck1 = (Truck) vehicleService.getVehicle(vehicle2.getVehicleId()); + log.info("Fetching Truck1 from DB : {}\n", savedTruck1); + + log.info("Fetching All Vehicles :- "); + + // Fetching the Vehicles present in the DB + List allVehiclesFromDb = vehicleService.getAllVehicles(); + allVehiclesFromDb.forEach(s -> log.info(s.toString())); + } } ``` -- Hibernate adds two other predefined values that the annotation can take — null and not null: +Console output: + +``` +2024-05-27T12:29:49.949+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Starting SingleTableInheritance using Java 17.0.4.1 with PID 56372 (/Users/ilkka.seppala/git/java-design-patterns/single-table-inheritance/target/classes started by ilkka.seppala in /Users/ilkka.seppala/git/java-design-patterns) +2024-05-27T12:29:49.951+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : No active profile set, falling back to 1 default profile: "default" +2024-05-27T12:29:50.154+03:00 INFO 56372 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. +2024-05-27T12:29:50.176+03:00 INFO 56372 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 19 ms. Found 1 JPA repository interface. +2024-05-27T12:29:50.315+03:00 INFO 56372 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] +2024-05-27T12:29:50.345+03:00 INFO 56372 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.4.4.Final +2024-05-27T12:29:50.360+03:00 INFO 56372 --- [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled +2024-05-27T12:29:50.457+03:00 INFO 56372 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer +2024-05-27T12:29:50.468+03:00 INFO 56372 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... +2024-05-27T12:29:50.541+03:00 INFO 56372 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:sti user=SA +2024-05-27T12:29:50.542+03:00 INFO 56372 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. +2024-05-27T12:29:50.930+03:00 INFO 56372 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2024-05-27T12:29:50.953+03:00 INFO 56372 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' +2024-05-27T12:29:51.094+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Started SingleTableInheritance in 1.435 seconds (process running for 1.678) +2024-05-27T12:29:51.095+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Saving Vehicles :- +2024-05-27T12:29:51.114+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Vehicle 1 saved : Car{PassengerVehicle(noOfPassengers=4)} +2024-05-27T12:29:51.115+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Vehicle 2 saved : Truck{ TransportVehicle(loadCapacity=3325), towingCapacity=14000} + +2024-05-27T12:29:51.115+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching Vehicles :- +2024-05-27T12:29:51.129+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching Car1 from DB : Car{PassengerVehicle(noOfPassengers=0)} +2024-05-27T12:29:51.130+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching Truck1 from DB : Truck{ TransportVehicle(loadCapacity=0), towingCapacity=14000} + +2024-05-27T12:29:51.130+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Fetching All Vehicles :- +2024-05-27T12:29:51.169+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Car{PassengerVehicle(noOfPassengers=0)} +2024-05-27T12:29:51.169+03:00 INFO 56372 --- [ main] com.iluwatar.SingleTableInheritance : Truck{ TransportVehicle(loadCapacity=0), towingCapacity=14000} +2024-05-27T12:29:51.172+03:00 INFO 56372 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' +2024-05-27T12:29:51.173+03:00 INFO 56372 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... +2024-05-27T12:29:51.174+03:00 INFO 56372 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. +``` + +The Single Table Inheritance pattern is a simple and efficient way to map an inheritance hierarchy to a relational database. However, it can lead to sparse tables if subclasses have many unique fields. In such cases, other patterns like Class Table Inheritance or Concrete Table Inheritance might be more appropriate. - - @DiscriminatorValue(“null”) means that any row without a discriminator value will be mapped to the entity class with this annotation; this can be applied to the root class of the hierarchy. - - @DiscriminatorValue(“not null”) – Any row with a discriminator value not matching any of the ones associated with entity definitions will be mapped to the class with this annotation. +## When to Use the Single Table Inheritance Pattern in Java +* Use when you have a class hierarchy with subclasses that share a common base class and you want to store all instances of the hierarchy in a single table. +* Ideal for small to medium-sized applications where the simplicity of a single table outweighs the performance cost of null fields for some subclasses. -## Class diagram +## Single Table Inheritance Pattern Tutorials -![alt text](./etc/single-table-inheritance.urm.png "Singleton pattern class diagram") +* [Hibernate Tutorial 18 - Implementing Inheritance - Single Table Strategy (Java Brains)](https://www.youtube.com/watch?v=M5YrLtAHtOo) -## Applicability +## Real-World Applications of Single Table Inheritance Pattern in Java -Use the Singleton pattern when +* Hibernate and JPA implementations in Java applications often use Single Table Inheritance for ORM mapping. +* Rails ActiveRecord supports Single Table Inheritance out of the box. -* Use STI When The Subclasses Have The Same Fields/Columns But Different Behavior - - A good indication that STI is right is when the different subclasses have the same fields/columns but different methods. In the accounts example above, we expect all the columns in the database to be used by each subclass. Otherwise, there will be a lot of null columns in the database. -

    -* Use STI When We Expect To Perform Queries Across All Subclasses - - Another good indication STI is right is if we expect to perform queries across all classes. For example, if we want to find the top 10 accounts with the highest balances across all types, STI allows lets us use just one query, whereas MTI will require in memory manipulation. +## Benefits and Trade-offs of Single Table Inheritance Pattern +Benefits: -### Tutorials +Using the Single Table Inheritance pattern in Java ORM -- Java Brains - Single Table Inheritance +* Simplifies database schema by reducing the number of tables. +* Easier to manage relationships and queries since all data is in one table. -## Consequences +Trade-offs: -* Fields are sometimes relevant and sometimes not, which can be confusing - to people using the tables directly. -* Columns used only by some subclasses lead to wasted space in the database. - How much this is actually a problem depends on the specific data - characteristics and how well the database compresses empty columns. - Oracle, for example, is very efficient in trimming wasted space, particularly - if you keep your optional columns to the right side of the database - table. Each database has its own tricks for this. -* The single table may end up being too large, with many indexes and frequent - locking, which may hurt performance. You can avoid this by having - separate index tables that either list keys of rows that have a certain property - or that copy a subset of fields relevant to an index. -* You only have a single namespace for fields, so you have to be sure that - you don’t use the same name for different fields. Compound names with - the name of the class as a prefix or suffix help here. +* Can lead to sparsely populated tables with many null values. +* May cause performance issues for large hierarchies due to table size and the need to filter by type. +* Changes in the inheritance hierarchy require schema changes. -## Related patterns +## Related Java Design Patterns -* MappedSuperclass -* Single Table -* Joined Table -* Table per Class +* Class Table Inheritance: Uses separate tables for each class in the hierarchy, reducing null values but increasing complexity in joins. +* Concrete Table Inheritance: Each class in the hierarchy has its own table, reducing redundancy but increasing the number of tables. -## Credits +## References and Credits -* [Single Table Inheritance - martinFowler.com](https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html) -* [Patterns of Enterprise Application Architecture](https://books.google.co.in/books?id=vqTfNFDzzdIC&pg=PA278&redir_esc=y#v=onepage&q&f=false) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Single Table Inheritance (Martin Fowler)](https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html) diff --git a/single-table-inheritance/pom.xml b/single-table-inheritance/pom.xml index 159a683bea21..6e3afcf38d69 100644 --- a/single-table-inheritance/pom.xml +++ b/single-table-inheritance/pom.xml @@ -1,4 +1,30 @@ + @@ -10,22 +36,25 @@ single-table-inheritance - + + org.springframework.boot + spring-boot-starter + org.springframework.boot spring-boot-starter-data-jpa + + jakarta.xml.bind + jakarta.xml.bind-api + 4.0.2 + com.h2database h2 runtime - - org.projectlombok - lombok - true - org.springframework.boot spring-boot-starter-test diff --git a/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java b/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java index 01fbb37b828f..f27b71213a09 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar; import com.iluwatar.entity.Car; @@ -13,32 +37,28 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * Single Table Inheritance pattern : + * Single Table Inheritance pattern :
    + * It maps each instance of class in an inheritance tree into a single table.
    + * + *

    In case of current project, in order to specify the Single Table Inheritance to Hibernate we + * annotate the main Vehicle root class with @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + * due to which a single root Vehicle class table will be created in the database and it will + * have columns for all the fields of it's subclasses(Car, Freighter, Train, Truck).
    + * Additional to that, a new separate "vehicle_id" column would be added to the Vehicle table + * to save the type of the subclass object that is being stored in the database. This value is + * specified by the @DiscriminatorValue annotation value for each subclass in case of Hibernate. *
    - * It maps each instance of class in an inheritance tree into a single table. *
    - *

    - * In case of current project, in order to specify the Single Table Inheritance to Hibernate - * we annotate the main Vehicle root class with @Inheritance(strategy = InheritanceType.SINGLE_TABLE) - * due to which a single root Vehicle class table will be created - * in the database and it will have columns for all the fields of - * it's subclasses(Car, Freighter, Train, Truck).
    - * Additional to that, a new separate "vehicle_id" column would be added - * to the Vehicle table to save the type of the subclass object that - * is being stored in the database. This value is specified by the @DiscriminatorValue annotation - * value for each subclass in case of Hibernate.
    - *


    * Below is the main Spring Boot Application class from where the Program Runs. - *

    - * It implements the CommandLineRunner to run the statements at the - * start of the application program. - *

    + * + *

    It implements the CommandLineRunner to run the statements at the start of the application + * program. */ @SpringBootApplication @AllArgsConstructor public class SingleTableInheritance implements CommandLineRunner { - //Autowiring the VehicleService class to execute the business logic methods + // Autowiring the VehicleService class to execute the business logic methods private final VehicleService vehicleService; /** @@ -51,13 +71,12 @@ public static void main(String[] args) { } /** - * The starting point of the CommandLineRunner - * where the main program is run. + * The starting point of the CommandLineRunner where the main program is run. * * @param args program runtime arguments */ @Override - public void run(String... args) throws Exception { + public void run(String... args) { Logger log = LoggerFactory.getLogger(SingleTableInheritance.class); @@ -73,7 +92,6 @@ public void run(String... args) throws Exception { Vehicle truck1 = vehicleService.saveVehicle(vehicle2); log.info("Vehicle 2 saved : {}\n", truck1); - log.info("Fetching Vehicles :- "); // Fetching the Car from DB @@ -90,4 +108,4 @@ public void run(String... args) throws Exception { List allVehiclesFromDb = vehicleService.getAllVehicles(); allVehiclesFromDb.forEach(s -> log.info(s.toString())); } -} \ No newline at end of file +} diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java index c8d3185fdce9..b934767e1b30 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java @@ -1,19 +1,42 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.entity; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** - * A class that extends the PassengerVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the PassengerVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see PassengerVehicle PassengerVehicle * @see Vehicle Vehicle */ - @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @@ -31,9 +54,6 @@ public Car(String manufacturer, String model, int noOfPassengers, int engineCapa // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Car{" - + super.toString() - + '}'; + return "Car{" + super.toString() + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java index ce35f3df37df..f97343b71db4 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java @@ -1,14 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.entity; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** - * A class that extends the TransportVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the TransportVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see TransportVehicle TransportVehicle * @see Vehicle Vehicle @@ -30,12 +54,6 @@ public Freighter(String manufacturer, String model, int loadCapacity, double fli // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Freighter{ " - + super.toString() - + " ," - + "flightLength=" - + flightLength - + '}'; + return "Freighter{ " + super.toString() + " ," + "flightLength=" + flightLength + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java index c27818a9a1d9..b189bbd56dbc 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.entity; import lombok.Data; @@ -5,8 +29,8 @@ import lombok.NoArgsConstructor; /** - * An abstract class that extends the Vehicle class - * and provides properties for the Passenger type of Vehicles. + * An abstract class that extends the Vehicle class and provides properties for the Passenger type + * of Vehicles. * * @see Vehicle */ @@ -21,10 +45,4 @@ protected PassengerVehicle(String manufacturer, String model, int noOfPassengers super(manufacturer, model); this.noOfPassengers = noOfPassengers; } - - @Override - public String toString() { - return super.toString(); - } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java index 10a74f36ac5a..6d220475a6c2 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java @@ -1,14 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.entity; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** - * A class that extends the PassengerVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the PassengerVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see PassengerVehicle PassengerVehicle * @see Vehicle Vehicle @@ -30,9 +54,6 @@ public Train(String manufacturer, String model, int noOfPassengers, int noOfCarr // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Train{" - + super.toString() - + '}'; + return "Train{" + super.toString() + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java index 9f9c60516e88..a996a9bb7b06 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.entity; import lombok.Data; @@ -5,8 +29,8 @@ import lombok.NoArgsConstructor; /** - * An abstract class that extends the Vehicle class - * and provides properties for the Transport type of Vehicles. + * An abstract class that extends the Vehicle class and provides properties for the Transport type + * of Vehicles. * * @see Vehicle */ @@ -21,5 +45,4 @@ protected TransportVehicle(String manufacturer, String model, int loadCapacity) super(manufacturer, model); this.loadCapacity = loadCapacity; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java index e389666e4dcc..46ccc29ca710 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java @@ -1,13 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.entity; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; import lombok.Data; import lombok.NoArgsConstructor; /** - * A class that extends the PassengerVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the PassengerVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see TransportVehicle TransportVehicle * @see Vehicle Vehicle @@ -28,11 +52,6 @@ public Truck(String manufacturer, String model, int loadCapacity, int towingCapa // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Truck{ " - + super.toString() - + ", " - + "towingCapacity=" - + towingCapacity - + '}'; + return "Truck{ " + super.toString() + ", " + "towingCapacity=" + towingCapacity + '}'; } } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java index 901a37705455..ed3cf4ae9f08 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java @@ -1,20 +1,44 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.entity; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** - * An abstract class that is the root of the Vehicle Inheritance hierarchy - * and basic provides properties for all the vehicles. + * An abstract class that is the root of the Vehicle Inheritance hierarchy and basic provides + * properties for all the vehicles. */ @Data @NoArgsConstructor @@ -41,14 +65,13 @@ protected Vehicle(String manufacturer, String model) { @Override public String toString() { return "Vehicle{" - + "vehicleId=" - + vehicleId - + ", manufacturer='" - + manufacturer - + '\'' - + ", model='" - + model - + '}'; + + "vehicleId=" + + vehicleId + + ", manufacturer='" + + manufacturer + + '\'' + + ", model='" + + model + + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java b/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java index 5aec26c3aa95..1de73abab760 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.repository; import com.iluwatar.entity.Vehicle; @@ -5,10 +29,8 @@ import org.springframework.stereotype.Repository; /** - * A repository that is extending the JPA Repository - * to provide the default Spring DATA JPA methods for the Vehicle class. + * A repository that is extending the JPA Repository to provide the default Spring DATA JPA methods + * for the Vehicle class. */ @Repository -public interface VehicleRepository extends JpaRepository { - -} +public interface VehicleRepository extends JpaRepository {} diff --git a/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java b/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java index 4aeacd04824d..1179590df117 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.service; import com.iluwatar.entity.Vehicle; @@ -7,9 +31,8 @@ import org.springframework.stereotype.Service; /** - * A service class that is used to provide the business logic - * for the Vehicle class and connect to the database to - * perform the CRUD operations on the root Vehicle class. + * A service class that is used to provide the business logic for the Vehicle class and connect to + * the database to perform the CRUD operations on the root Vehicle class. * * @see Vehicle */ @@ -67,5 +90,4 @@ public Vehicle updateVehicle(Vehicle vehicle) { public void deleteVehicle(Vehicle vehicle) { vehicleRepository.delete(vehicle); } - } diff --git a/singleton/README.md b/singleton/README.md index bb94d5375c0a..626a2ed659bf 100644 --- a/singleton/README.md +++ b/singleton/README.md @@ -1,21 +1,29 @@ --- -title: Singleton +title: "Singleton Pattern in Java: Implementing Global Access Points in Java Applications" +shortTitle: Singleton +description: "Explore the Singleton Pattern in Java with our comprehensive guide. Learn how to implement efficient object management for your Java applications, ensuring optimal use of resources and easy access with examples and detailed explanations." category: Creational language: en tag: - - Gang of Four + - Gang of Four + - Instantiation + - Lazy initialization + - Resource management --- -## Intent +## Also known as -Ensure a class only has one instance, and provide a global point of access to it. +* Single Instance -## Explanation +## Intent of Singleton Design Pattern + +Ensure a Java class only has one instance, and provide a global point of access to this singleton instance. + +## Detailed Explanation of Singleton Pattern with Real-World Examples Real-world example -> There can only be one ivory tower where the wizards study their magic. The same enchanted ivory -> tower is always used by the wizards. The ivory tower here is a singleton. +> A real-world analogy for the Singleton pattern is a government issuing a passport. In a country, each citizen can only be issued one valid passport at a time. The passport office ensures that no duplicate passports are issued to the same person. Whenever a citizen needs to travel, they must use this single passport, which serves as the unique, globally recognized identifier for their travel credentials. This controlled access and unique instance management mirrors how the Singleton pattern ensures efficient object management in Java applications. In plain words @@ -23,11 +31,9 @@ In plain words Wikipedia says -> In software engineering, the singleton pattern is a software design pattern that restricts the -> instantiation of a class to one object. This is useful when exactly one object is needed to -> coordinate actions across the system. +> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. -**Programmatic Example** +## Programmatic Example of Singleton Pattern in Java Joshua Bloch, Effective Java 2nd Edition p.18 @@ -55,40 +61,49 @@ enumIvoryTower1=com.iluwatar.singleton.EnumIvoryTower@1221555852 enumIvoryTower2=com.iluwatar.singleton.EnumIvoryTower@1221555852 ``` -## Class diagram - -![alt text](./etc/singleton.urm.png "Singleton pattern class diagram") - -## Applicability +## When to Use the Singleton Pattern in Java Use the Singleton pattern when * There must be exactly one instance of a class, and it must be accessible to clients from a well-known access point * When the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code -Some typical use cases for the Singleton +## Real-World Applications of Singleton Pattern in Java * The logging class -* Managing a connection to a database +* Configuration classes in many applications +* Connection pools * File manager - -## Known uses - * [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) * [java.awt.Desktop#getDesktop()](http://docs.oracle.com/javase/8/docs/api/java/awt/Desktop.html#getDesktop--) * [java.lang.System#getSecurityManager()](http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getSecurityManager--) +## Benefits and Trade-offs of Singleton Pattern + +Benefits: + +* Controlled access to the single instance. +* Reduced namespace pollution. +* Allows refinement of operations and representation. +* Permits a variable number of instances (more than one, if desired). +* More flexible than class operations. + +Trade-offs: + +* Difficult to test due to global state. +* Potentially more complex lifecycle management. +* Can introduce bottlenecks if used in a concurrent context without careful synchronization. -## Consequences +## Related Java Design Patterns -* Violates Single Responsibility Principle (SRP) by controlling their creation and lifecycle. -* Encourages using a globally shared instance which prevents an object and resources used by this object from being deallocated. -* Creates tightly coupled code. The clients of the Singleton become difficult to test. -* Makes it almost impossible to subclass a Singleton. +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Often used to ensure a class only has one instance. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Singleton pattern can be implemented using a Factory Method to encapsulate the creation logic. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Avoids the need to create instances, can work alongside Singleton to manage unique instances. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/singleton/pom.xml b/singleton/pom.xml index c6e7a65ca126..5d6a57326cbc 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -34,6 +34,14 @@ singleton + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/singleton/src/main/java/com/iluwatar/singleton/App.java b/singleton/src/main/java/com/iluwatar/singleton/App.java index 8d7946cae95d..ccc0ff82718f 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/App.java +++ b/singleton/src/main/java/com/iluwatar/singleton/App.java @@ -27,39 +27,40 @@ import lombok.extern.slf4j.Slf4j; /** - *

    Singleton pattern ensures that the class can have only one existing instance per Java - * classloader instance and provides global access to it.

    + * Singleton pattern ensures that the class can have only one existing instance per Java classloader + * instance and provides global access to it. * *

    One of the risks of this pattern is that bugs resulting from setting a singleton up in a - * distributed environment can be tricky to debug since it will work fine if you debug with a - * single classloader. Additionally, these problems can crop up a while after the implementation of - * a singleton, since they may start synchronous and only become async with time, so it may - * not be clear why you are seeing certain changes in behavior.

    + * distributed environment can be tricky to debug since it will work fine if you debug with a single + * classloader. Additionally, these problems can crop up a while after the implementation of a + * singleton, since they may start synchronous and only become async with time, so it may not be + * clear why you are seeing certain changes in behavior. * *

    There are many ways to implement the Singleton. The first one is the eagerly initialized * instance in {@link IvoryTower}. Eager initialization implies that the implementation is thread * safe. If you can afford to give up control of the instantiation moment, then this implementation - * will suit you fine.

    + * will suit you fine. * *

    The other option to implement eagerly initialized Singleton is enum-based Singleton. The * example is found in {@link EnumIvoryTower}. At first glance, the code looks short and simple. * However, you should be aware of the downsides including committing to implementation strategy, * extending the enum class, serializability, and restrictions to coding. These are extensively - * discussed in Stack Overflow: http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing - * -a-singleton-with-javas-enum

    + * discussed in Stack Overflow: + * http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing + * -a-singleton-with-javas-enum * *

    {@link ThreadSafeLazyLoadedIvoryTower} is a Singleton implementation that is initialized on * demand. The downside is that it is very slow to access since the whole access method is - * synchronized.

    + * synchronized. * - *

    Another Singleton implementation that is initialized on demand is found in - * {@link ThreadSafeDoubleCheckLocking}. It is somewhat faster than {@link - * ThreadSafeLazyLoadedIvoryTower} since it doesn't synchronize the whole access method but only the - * method internals on specific conditions.

    + *

    Another Singleton implementation that is initialized on demand is found in {@link + * ThreadSafeDoubleCheckLocking}. It is somewhat faster than {@link ThreadSafeLazyLoadedIvoryTower} + * since it doesn't synchronize the whole access method but only the method internals on specific + * conditions. * - *

    Yet another way to implement thread-safe lazily initialized Singleton can be found in - * {@link InitializingOnDemandHolderIdiom}. However, this implementation requires at least Java 8 - * API level to work.

    + *

    Yet another way to implement thread-safe lazily initialized Singleton can be found in {@link + * InitializingOnDemandHolderIdiom}. However, this implementation requires at least Java 8 API level + * to work. */ @Slf4j public class App { @@ -89,7 +90,7 @@ public static void main(String[] args) { LOGGER.info("enumIvoryTower1={}", enumIvoryTower1); LOGGER.info("enumIvoryTower2={}", enumIvoryTower2); - // double checked locking + // double-checked locking var dcl1 = ThreadSafeDoubleCheckLocking.getInstance(); LOGGER.info(dcl1.toString()); var dcl2 = ThreadSafeDoubleCheckLocking.getInstance(); diff --git a/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java b/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java index ba66386181df..6d784787f735 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java +++ b/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java @@ -25,44 +25,37 @@ package com.iluwatar.singleton; /** - *

    Bill Pugh Singleton Implementation.

    + * Bill Pugh Singleton Implementation. * - *

    This implementation of the singleton design pattern takes advantage of the - * Java memory model's guarantees about class initialization. Each class is - * initialized only once, when it is first used. If the class hasn't been used - * yet, it won't be loaded into memory, and no memory will be allocated for - * a static instance. This makes the singleton instance lazy-loaded and thread-safe.

    - * - * @author owen.leung2@gmail.com + *

    This implementation of the singleton design pattern takes advantage of the Java memory model's + * guarantees about class initialization. Each class is initialized only once, when it is first + * used. If the class hasn't been used yet, it won't be loaded into memory, and no memory will be + * allocated for a static instance. This makes the singleton instance lazy-loaded and thread-safe. */ public final class BillPughImplementation { - /** - * Private constructor to prevent instantiation from outside the class. - */ + /** Private constructor to prevent instantiation from outside the class. */ private BillPughImplementation() { - // private constructor + // to prevent instantiating by Reflection call + if (InstanceHolder.instance != null) { + throw new IllegalStateException("Already initialized."); + } } /** - * The InstanceHolder is a static inner class and it holds the Singleton instance. - * It is not loaded into memory until the getInstance() method is called. + * The InstanceHolder is a static inner class, and it holds the Singleton instance. It is not + * loaded into memory until the getInstance() method is called. */ private static class InstanceHolder { - /** - * Singleton instance of the class. - */ + /** Singleton instance of the class. */ private static BillPughImplementation instance = new BillPughImplementation(); } /** * Public accessor for the singleton instance. * - *

    - * When this method is called, the InstanceHolder is loaded into memory - * and creates the Singleton instance. This method provides a global access point - * for the singleton instance. - *

    + *

    When this method is called, the InstanceHolder is loaded into memory and creates the + * Singleton instance. This method provides a global access point for the singleton instance. * * @return an instance of the class. */ diff --git a/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java index 8130dc55dd70..a33876642993 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java @@ -25,16 +25,14 @@ package com.iluwatar.singleton; /** - *

    Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18

    + * Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18 * - *

    This implementation is thread safe, however adding any other method and its thread safety - * is developers responsibility.

    + *

    This implementation is thread safe, however adding any other method and its thread safety is + * developers responsibility. */ public enum EnumIvoryTower { - /** - * The singleton instance of the class, created by the Java enum singleton pattern. - */ + /** The singleton instance of the class, created by the Java enum singleton pattern. */ INSTANCE; @Override diff --git a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java index 6cff5b561a92..9f9a2105789b 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java +++ b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java @@ -25,24 +25,25 @@ package com.iluwatar.singleton; /** - *

    The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton - * object in Java.

    + * The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton + * object in Java. * *

    The technique is as lazy as possible and works in all known versions of Java. It takes - * advantage of language guarantees about class initialization, and will therefore work correctly - * in all Java-compliant compilers and virtual machines.

    + * advantage of language guarantees about class initialization, and will therefore work correctly in + * all Java-compliant compilers and virtual machines. * *

    The inner class is referenced no earlier (and therefore loaded no earlier by the class loader) * than the moment that getInstance() is called. Thus, this solution is thread-safe without - * requiring special language constructs (i.e. volatile or synchronized).

    - * + * requiring special language constructs (i.e. volatile or synchronized). */ public final class InitializingOnDemandHolderIdiom { - /** - * Private constructor. - */ + /** Private constructor. */ private InitializingOnDemandHolderIdiom() { + // to prevent instantiating by Reflection call + if (HelperHolder.INSTANCE != null) { + throw new IllegalStateException("Already initialized."); + } } /** @@ -54,14 +55,10 @@ public static InitializingOnDemandHolderIdiom getInstance() { return HelperHolder.INSTANCE; } - /** - * Provides the lazy-loaded Singleton instance. - */ + /** Provides the lazy-loaded Singleton instance. */ private static class HelperHolder { - /** - * Singleton instance of the class. - */ + /** Singleton instance of the class. */ private static final InitializingOnDemandHolderIdiom INSTANCE = new InitializingOnDemandHolderIdiom(); } diff --git a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java index f27467272762..fc89ab312dd5 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java @@ -1,51 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.singleton; - -/** - * Singleton class. Eagerly initialized static instance guarantees thread safety. - */ -public final class IvoryTower { - - /** - * Private constructor so nobody can instantiate the class. - */ - private IvoryTower() { - } - - /** - * Static to class instance of the class. - */ - private static final IvoryTower INSTANCE = new IvoryTower(); - - /** - * To be called by user to obtain instance of the class. - * - * @return instance of the singleton. - */ - public static IvoryTower getInstance() { - return INSTANCE; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.singleton; + +/** Singleton class. Eagerly initialized static instance guarantees thread safety. */ +public final class IvoryTower { + + /** Private constructor so nobody can instantiate the class. */ + private IvoryTower() { + // to prevent instantiating by Reflection call + if (INSTANCE != null) { + throw new IllegalStateException("Already initialized."); + } + } + + /** Static to class instance of the class. */ + private static final IvoryTower INSTANCE = new IvoryTower(); + + /** + * To be called by user to obtain instance of the class. + * + * @return instance of the singleton. + */ + public static IvoryTower getInstance() { + return INSTANCE; + } +} diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java index e409432a1945..dc3907f120ee 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java @@ -25,23 +25,20 @@ package com.iluwatar.singleton; /** - *

    Double check locking.

    + * Double check locking. * - *

    http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

    + *

    http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html * - *

    Broken under Java 1.4.

    - * - * @author mortezaadi@gmail.com + *

    Broken under Java 1.4. */ public final class ThreadSafeDoubleCheckLocking { /** - * Singleton instance of the class, declared as volatile to ensure atomic access by multiple threads. + * Singleton instance of the class, declared as volatile to ensure atomic access by multiple + * threads. */ private static volatile ThreadSafeDoubleCheckLocking instance; - /** - * private constructor to prevent client from instantiating. - */ + /** private constructor to prevent client from instantiating. */ private ThreadSafeDoubleCheckLocking() { // to prevent instantiating by Reflection call if (instance != null) { @@ -62,7 +59,7 @@ public static ThreadSafeDoubleCheckLocking getInstance() { // Check if singleton instance is initialized. // If it is initialized then we can return the instance. if (result == null) { - // It is not initialized but we cannot be sure because some other thread might have + // It is not initialized, but we cannot be sure because some other thread might have // initialized it in the meanwhile. // So to make sure we need to lock on an object to get mutual exclusion. synchronized (ThreadSafeDoubleCheckLocking.class) { @@ -72,7 +69,7 @@ public static ThreadSafeDoubleCheckLocking getInstance() { // just like the previous null check. result = instance; if (result == null) { - // The instance is still not initialized so we can safely + // The instance is still not initialized, so we can safely // (no other thread can enter this zone) // create an instance and make it our singleton instance. result = new ThreadSafeDoubleCheckLocking(); diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java index 4d1c25739673..1ead462b9493 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java @@ -25,20 +25,18 @@ package com.iluwatar.singleton; /** - *

    Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization - * mechanism.

    - * + * Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization + * mechanism. */ public final class ThreadSafeLazyLoadedIvoryTower { /** - * Singleton instance of the class, declared as volatile to ensure atomic access by multiple threads. + * Singleton instance of the class, declared as volatile to ensure atomic access by multiple + * threads. */ private static volatile ThreadSafeLazyLoadedIvoryTower instance; - /** - * Private constructor to prevent instantiation from outside the class. - */ + /** Private constructor to prevent instantiation from outside the class. */ private ThreadSafeLazyLoadedIvoryTower() { // Protect against instantiation via reflection if (instance != null) { diff --git a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java index 468fe7299630..085cb0f153d3 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.singleton; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test. - */ +import org.junit.jupiter.api.Test; + +/** Application test. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java b/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java index 90a3424e412c..0f61c6776190 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java @@ -1,16 +1,33 @@ -package com.iluwatar.singleton; - -/** - * Date: 06/18/23 - 16:29 PM. +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * @author Owen Leung + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ -public class BillPughImplementationTest - extends SingletonTest{ - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ - public BillPughImplementationTest() { - super(BillPughImplementation::getInstance); - } +package com.iluwatar.singleton; + +/** BillPughImplementationTest */ +public class BillPughImplementationTest extends SingletonTest { + /** Create a new singleton test instance using the given 'getInstance' method. */ + public BillPughImplementationTest() { + super(BillPughImplementation::getInstance); + } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java index a03501530809..c6af4420ae0f 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java @@ -24,18 +24,24 @@ */ package com.iluwatar.singleton; -/** - * Date: 12/29/15 - 19:20 PM. - * - * @author Jeroen Meulemeester - */ +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** EnumIvoryTowerTest */ class EnumIvoryTowerTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public EnumIvoryTowerTest() { super(() -> EnumIvoryTower.INSTANCE); } + /** Test creating new instance by reflection. */ + @Override + @Test + void testCreatingNewInstanceByReflection() throws Exception { + // Java does not allow Enum instantiation + // http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9 + assertThrows(ReflectiveOperationException.class, EnumIvoryTower.class::getDeclaredConstructor); + } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java index 8391c4f09a14..5071dfc4fdd1 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java @@ -24,19 +24,11 @@ */ package com.iluwatar.singleton; -/** - * Date: 12/29/15 - 19:22 PM. - * - * @author Jeroen Meulemeester - */ -class InitializingOnDemandHolderIdiomTest - extends SingletonTest { +/** InitializingOnDemandHolderIdiomTest */ +class InitializingOnDemandHolderIdiomTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public InitializingOnDemandHolderIdiomTest() { super(InitializingOnDemandHolderIdiom::getInstance); } - -} \ No newline at end of file +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java index 4fb0c8d90386..98621224de8b 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java @@ -24,18 +24,11 @@ */ package com.iluwatar.singleton; -/** - * Date: 12/29/15 - 19:23 PM. - * - * @author Jeroen Meulemeester - */ +/** IvoryTowerTest */ class IvoryTowerTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public IvoryTowerTest() { super(IvoryTower::getInstance); } - -} \ No newline at end of file +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java index 5fb6c3f8e0f9..bf0b403d8f7b 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java @@ -27,8 +27,10 @@ import static java.time.Duration.ofMillis; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeout; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.Executors; @@ -38,23 +40,17 @@ import org.junit.jupiter.api.Test; /** - *

    This class provides several test case that test singleton construction.

    + * This class provides several test case that test singleton construction. * *

    The first proves that multiple calls to the singleton getInstance object are the same when * called in the SAME thread. The second proves that multiple calls to the singleton getInstance - * object are the same when called in the DIFFERENT thread.

    - * - *

    Date: 12/29/15 - 19:25 PM

    + * object are the same when called in the DIFFERENT thread. * * @param Supplier method generating singletons - * @author Jeroen Meulemeester - * @author Richard Jones */ abstract class SingletonTest { - /** - * The singleton's getInstance method. - */ + /** The singleton's getInstance method. */ private final Supplier singletonInstanceMethod; /** @@ -66,9 +62,7 @@ public SingletonTest(final Supplier singletonInstanceMethod) { this.singletonInstanceMethod = singletonInstanceMethod; } - /** - * Test the singleton in a non-concurrent setting. - */ + /** Test the singleton in a non-concurrent setting. */ @Test void testMultipleCallsReturnTheSameObjectInSameThread() { // Create several instances in the same calling thread @@ -81,33 +75,41 @@ void testMultipleCallsReturnTheSameObjectInSameThread() { assertSame(instance2, instance3); } - /** - * Test singleton instance in a concurrent setting. - */ + /** Test singleton instance in a concurrent setting. */ @Test - void testMultipleCallsReturnTheSameObjectInDifferentThreads() throws Exception { - assertTimeout(ofMillis(10000), () -> { - // Create 10000 tasks and inside each callable instantiate the singleton class - final var tasks = IntStream.range(0, 10000) - .>mapToObj(i -> this.singletonInstanceMethod::get) - .collect(Collectors.toCollection(ArrayList::new)); - - // Use up to 8 concurrent threads to handle the tasks - final var executorService = Executors.newFixedThreadPool(8); - final var results = executorService.invokeAll(tasks); + void testMultipleCallsReturnTheSameObjectInDifferentThreads() { + assertTimeout( + ofMillis(10000), + () -> { + // Create 10000 tasks and inside each callable instantiate the singleton class + final var tasks = + IntStream.range(0, 10000) + .>mapToObj(i -> this.singletonInstanceMethod::get) + .collect(Collectors.toCollection(ArrayList::new)); - // wait for all of the threads to complete - final var expectedInstance = this.singletonInstanceMethod.get(); - for (var res : results) { - final var instance = res.get(); - assertNotNull(instance); - assertSame(expectedInstance, instance); - } + // Use up to 8 concurrent threads to handle the tasks + final var executorService = Executors.newFixedThreadPool(8); + final var results = executorService.invokeAll(tasks); - // tidy up the executor - executorService.shutdown(); - }); + // wait for all the threads to complete + final var expectedInstance = this.singletonInstanceMethod.get(); + for (var res : results) { + final var instance = res.get(); + assertNotNull(instance); + assertSame(expectedInstance, instance); + } + // tidy up the executor + executorService.shutdown(); + }); } + /** Test creating new instance by reflection. */ + @Test + void testCreatingNewInstanceByReflection() throws Exception { + var firstTimeInstantiated = this.singletonInstanceMethod.get(); + var constructor = firstTimeInstantiated.getClass().getDeclaredConstructor(); + constructor.setAccessible(true); + assertThrows(InvocationTargetException.class, () -> constructor.newInstance((Object[]) null)); + } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java index e8d3cb24fa1f..b0bc8a4b9135 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java @@ -24,34 +24,11 @@ */ package com.iluwatar.singleton; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.lang.reflect.InvocationTargetException; -import org.junit.jupiter.api.Test; - -/** - * Date: 12/29/15 - 19:26 PM. - * - * @author Jeroen Meulemeester - */ +/** ThreadSafeDoubleCheckLockingTest */ class ThreadSafeDoubleCheckLockingTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public ThreadSafeDoubleCheckLockingTest() { super(ThreadSafeDoubleCheckLocking::getInstance); } - - /** - * Test creating new instance by refection. - */ - @Test - void testCreatingNewInstanceByRefection() throws Exception { - ThreadSafeDoubleCheckLocking.getInstance(); - var constructor = ThreadSafeDoubleCheckLocking.class.getDeclaredConstructor(); - constructor.setAccessible(true); - assertThrows(InvocationTargetException.class, () -> constructor.newInstance((Object[]) null)); - } - } diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java index c1832016b6bf..e1cd097a2cc0 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java @@ -24,19 +24,11 @@ */ package com.iluwatar.singleton; -/** - * Date: 12/29/15 - 19:26 PM. - * - * @author Jeroen Meulemeester - */ -class ThreadSafeLazyLoadedIvoryTowerTest - extends SingletonTest { +/** ThreadSafeLazyLoadedIvoryTowerTest */ +class ThreadSafeLazyLoadedIvoryTowerTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public ThreadSafeLazyLoadedIvoryTowerTest() { super(ThreadSafeLazyLoadedIvoryTower::getInstance); } - } diff --git a/spatial-partition/README.md b/spatial-partition/README.md index c7a09985d127..26f070d5b6c0 100644 --- a/spatial-partition/README.md +++ b/spatial-partition/README.md @@ -1,73 +1,134 @@ --- -title: Spatial Partition -category: Behavioral +title: "Spatial Partition Pattern in Java: Optimizing Spatial Queries for Enhanced Performance" +shortTitle: Spatial Partition +description: "Explore the Spatial Partition design pattern for Java, ideal for optimizing game development and simulations. Learn how it enhances performance by efficiently managing objects in space, with examples and practical applications." +category: Structural language: en tag: - - Performance - - Game programming + - Game programming + - Optimization + - Performance + - Resource management + - Scalability --- -## Intent +## Also known as -As explained in the book [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html) -by Bob Nystrom, spatial partition pattern helps to efficiently locate objects by storing them in a -data structure organized by their positions. +* Space Partitioning +* Spatial Indexing -## Explanation +## Intent of Spatial Partition Design Pattern -Say, you are building a war game with hundreds, or maybe even thousands of players, who are clashing -on the battle field. Each player's position is getting updated every frame. The simple way to handle -all interactions taking place on the field is to check each player's position against every other -player's position: +Efficiently organize a large number of objects in space to optimize queries and operations. + +## Detailed Explanation of Spatial Partition Pattern with Real-World Examples + +Real-world example + +> Imagine managing a large warehouse where items are constantly being moved, added, or retrieved. To optimize the search for specific items and manage space efficiently, the warehouse is divided into different sections or zones. Each zone contains shelves, and each shelf holds a specific category of items. +> +> This setup is analogous to the Spatial Partition design pattern in software, where a large space (the warehouse) is divided into manageable parts (zones) to optimize operations like searching for an item or checking inventory (spatial queries). By organizing the warehouse this way, it becomes easier to locate and manage items, much like how spatial partitioning helps in efficiently managing and querying objects in a large spatial environment. + +In plain words + +> The Spatial Partition design pattern organizes objects in a defined space to optimize spatial queries and operations. + +Wikipedia says + +> The Spatial Partition design pattern, also known as Space Partitioning, involves dividing a space into non-overlapping regions to manage and query spatial data efficiently. This method is widely used in computer graphics, particularly for optimizing tasks like collision detection, ray tracing, and rendering large scenes with numerous objects. Organizing objects into hierarchical structures like BSP trees, Quadtrees, or Octrees under the Spatial Partition pattern allows for more efficient spatial queries, a significant advantage in Java applications for complex graphic rendering and collision detection. +> +> For example, in ray tracing, space partitioning helps quickly determine the objects a ray might intersect by narrowing down the search space, leading to faster rendering times. Similarly, in game development, Quadtrees can efficiently manage 2D game environments by segmenting the space into smaller regions, facilitating quicker collision detection and rendering. + +## Programmatic Example of Spatial Partition Pattern in Java + +The Spatial Partition design pattern in Java is a strategic approach for handling multiple objects in expansive game worlds or detailed simulation environments, boosting query efficiency and operational speed. It allows us to efficiently manage these objects and perform operations like collision detection or range queries. The pattern works by dividing the space into smaller, manageable regions, and each object is associated with the region it belongs to. This way, we can limit our operations to a specific region, instead of checking every object against every other object. + +In the provided code, we have an example of a war game where the positions of players are updated every frame. The simple way to handle interactions on the battlefield is to check each player's position against every other player's position. However, this approach includes a lot of unnecessary checks between players who are too far apart to influence each other. The Spatial Partition pattern can help us optimize this operation. + +Here's a simplified version of the code with added comments to explain the Spatial Partition pattern: ```java +// This is the simple way to handle interactions on the battlefield +// It includes a lot of unnecessary checks between players who are too far apart to influence each other public void handleMeLee(Unit units[], int numUnits) { - for (var a = 0; a < numUnits - 1; a++) - { - for (var b = a + 1; b < numUnits; b++) - { - if (units[a].position() == units[b].position()) - { - handleAttack(units[a], units[b]); - } + for (var a = 0; a < numUnits - 1; a++) { + for (var b = a + 1; b < numUnits; b++) { + // We check if two units are at the same position + if (units[a].position() == units[b].position()) { + // If they are, we handle the attack + handleAttack(units[a], units[b]); + } + } } - } } ``` -This will include a lot of unnecessary checks between players which are too far apart to have any -influence on each other. The nested loops gives this operation an O(n^2) complexity, which has to be -performed every frame since many of the objects on the field may be moving each frame. The idea -behind the Spatial Partition design pattern is to enable quick location of objects using a data -structure that is organised by their positions, so when performing an operation like the one above, -every object's position need not be checked against all other objects' positions. The data structure -can be used to store moving and static objects, though in order to keep track of the moving objects, -their positions will have to be reset each time they move. This would mean having to create a new -instance of the data structure each time an object moves, which would use up additional memory. The -common data structures used for this design pattern are: +The above code has a time complexity of O(n^2), which can be quite inefficient when the number of units is large. The Spatial Partition pattern can help us reduce this complexity. + +In the Spatial Partition pattern, we would divide the battlefield into smaller regions, and each unit would be associated with the region it belongs to. When we need to check for interactions, we would only need to check the units within the same region, significantly reducing the number of checks. + +Here's a simplified version of how this might look: + +```java +// We first create a spatial partition (e.g., a grid or a quadtree) +SpatialPartition partition = new SpatialPartition(); + +// We then add each unit to the partition +for (Unit unit : units) { + partition.add(unit); +} + +// When we need to handle interactions, we only check the units within the same region +for (Unit unit : units) { + List nearbyUnits = partition.getUnitsInSameRegion(unit); + for (Unit nearbyUnit : nearbyUnits) { + if (unit.position() == nearbyUnit.position()) { + handleAttack(unit, nearbyUnit); + } + } +} +``` + +In this code, `SpatialPartition` is a class that represents the spatial partition. The `add` method is used to add a unit to the partition, and the `getUnitsInSameRegion` method is used to get all units in the same region as a given unit. The exact implementation of these methods would depend on the specific type of spatial partition used (e.g., grid, quadtree, etc.). + +This way, we can reduce the time complexity of finding the units within a certain range from O(n^2) to O(nlogn), decreasing the computations required significantly in case of a large number of units. + +## When to Use the Spatial Partition Pattern in Java + +* Use when managing a large number of objects in a spatial environment, such as in games or simulations. +* Useful for optimizing spatial queries like finding nearby objects or detecting collisions. + +## Spatial Partition Pattern Java Tutorials + +* [Coding Challenge #98.1: Quadtree - Part 1 (The Coding Train)](https://www.youtube.com/watch?v=OJxEcs0w_kE) + +## Real-World Applications of Spatial Partition Pattern in Java + +* Quadtree in 2D games for collision detection. +* Octree in 3D environments for rendering and physics calculations. +* KD-tree in spatial databases for efficient range searches. -* Grid -* Quad tree -* K-d tree -* BSP -* Boundary volume hierarchy +## Benefits and Trade-offs of Spatial Partition Pattern -In our implementation, we use the Quadtree data structure which will reduce the time complexity of -finding the objects within a certain range from O(n^2) to O(nlogn), decreasing the computations -required significantly in case of large number of objects. +Benefits: -## Class diagram +* Significant performance improvements in spatial queries. +* Reduces the complexity of managing objects in large spaces. +* Scales well with an increasing number of objects. -![alt text](./etc/spatial-partition.urm.png "Spatial Partition pattern class diagram") +Trade-offs: -## Applicability +* Increased complexity in implementation. +* May require periodic rebalancing or restructuring as objects move. -This pattern can be used: +## Related Java Design Patterns -* When you need to keep track of a large number of objects' positions, which are getting updated every frame. -* When it is acceptable to trade memory for speed, since creating and updating data structure will use up extra memory. +* [Composite](https://java-design-patterns.com/patterns/composite/): Helps manage hierarchical data structures like trees used in spatial partitioning. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Can be used to manage memory efficiently for objects stored in spatial partitions. -## Credits +## References and Credits -* [Game Programming Patterns/Spatial Partition](http://gameprogrammingpatterns.com/spatial-partition.html) by Bob Nystrom -* [Quadtree tutorial](https://www.youtube.com/watch?v=OJxEcs0w_kE) by Daniel Schiffman +* [Game Programming Patterns](https://amzn.to/3K96fOn) +* [Introduction to Algorithms](https://amzn.to/4aC5hW0) +* [Real-Time Collision Detection](https://amzn.to/4as9gnW) +* [Spatial Partition (Game Programming Patterns)](http://gameprogrammingpatterns.com/spatial-partition.html) diff --git a/spatial-partition/pom.xml b/spatial-partition/pom.xml index 753ee7422304..a9d33cff6b59 100644 --- a/spatial-partition/pom.xml +++ b/spatial-partition/pom.xml @@ -34,6 +34,14 @@ spatial-partition + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java index e7b14249a732..8eabbed060ad 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java @@ -30,76 +30,77 @@ import lombok.extern.slf4j.Slf4j; /** - *

    The idea behind the Spatial Partition design pattern is to enable efficient location - * of objects by storing them in a data structure that is organised by their positions. This is + * The idea behind the Spatial Partition design pattern is to enable efficient location of + * objects by storing them in a data structure that is organised by their positions. This is * especially useful in the gaming world, where one may need to look up all the objects within a * certain boundary, or near a certain other object, repeatedly. The data structure can be used to * store moving and static objects, though in order to keep track of the moving objects, their * positions will have to be reset each time they move. This would mean having to create a new * instance of the data structure each frame, which would use up additional memory, and so this * pattern should only be used if one does not mind trading memory for speed and the number of - * objects to keep track of is large to justify the use of the extra space.

    + * objects to keep track of is large to justify the use of the extra space. + * *

    In our example, we use {@link QuadTree} data structure which divides into 4 (quad) * sub-sections when the number of objects added to it exceeds a certain number (int field - * capacity). There is also a - * {@link Rect} class to define the boundary of the quadtree. We use an abstract class - * {@link Point} - * with x and y coordinate fields and also an id field so that it can easily be put and looked up in - * the hashmap. This class has abstract methods to define how the object moves (move()), when to - * check for collision with any object (touches(obj)) and how to handle collision - * (handleCollision(obj)), and will be extended by any object whose position has to be kept track of - * in the quadtree. The {@link SpatialPartitionGeneric} abstract class has 2 fields - a - * hashmap containing all objects (we use hashmap for faster lookups, insertion and deletion) - * and a quadtree, and contains an abstract method which defines how to handle interactions between - * objects using the quadtree.

    + * capacity). There is also a {@link Rect} class to define the boundary of the quadtree. We + * use an abstract class {@link Point} with x and y coordinate fields and also an id field so + * that it can easily be put and looked up in the hashmap. This class has abstract methods to define + * how the object moves (move()), when to check for collision with any object (touches(obj)) and how + * to handle collision (handleCollision(obj)), and will be extended by any object whose position has + * to be kept track of in the quadtree. The {@link SpatialPartitionGeneric} abstract class + * has 2 fields - a hashmap containing all objects (we use hashmap for faster lookups, insertion and + * deletion) and a quadtree, and contains an abstract method which defines how to handle + * interactions between objects using the quadtree. + * *

    Using the quadtree data structure will reduce the time complexity of finding the objects * within a certain range from O(n^2) to O(nlogn), increasing the speed of computations * immensely in case of large number of objects, which will have a positive effect on the rendering - * speed of the game.

    + * speed of the game. */ - @Slf4j public class App { static void noSpatialPartition(int numOfMovements, Map bubbles) { - //all bubbles have to be checked for collision for all bubbles + // all bubbles have to be checked for collision for all bubbles var bubblesToCheck = bubbles.values(); - //will run numOfMovement times or till all bubbles have popped + // will run numOfMovement times or till all bubbles have popped while (numOfMovements > 0 && !bubbles.isEmpty()) { - bubbles.forEach((i, bubble) -> { - // bubble moves, new position gets updated - // and collisions are checked with all bubbles in bubblesToCheck - bubble.move(); - bubbles.replace(i, bubble); - bubble.handleCollision(bubblesToCheck, bubbles); - }); + bubbles.forEach( + (i, bubble) -> { + // bubble moves, new position gets updated + // and collisions are checked with all bubbles in bubblesToCheck + bubble.move(); + bubbles.replace(i, bubble); + bubble.handleCollision(bubblesToCheck, bubbles); + }); numOfMovements--; } - //bubbles not popped + // bubbles not popped bubbles.keySet().forEach(key -> LOGGER.info("Bubble {} not popped", key)); } static void withSpatialPartition( int height, int width, int numOfMovements, Map bubbles) { - //creating quadtree + // creating quadtree var rect = new Rect(width / 2D, height / 2D, width, height); var quadTree = new QuadTree(rect, 4); - //will run numOfMovement times or till all bubbles have popped + // will run numOfMovement times or till all bubbles have popped while (numOfMovements > 0 && !bubbles.isEmpty()) { - //quadtree updated each time + // quadtree updated each time bubbles.values().forEach(quadTree::insert); - bubbles.forEach((i, bubble) -> { - //bubble moves, new position gets updated, quadtree used to reduce computations - bubble.move(); - bubbles.replace(i, bubble); - var sp = new SpatialPartitionBubbles(bubbles, quadTree); - sp.handleCollisionsUsingQt(bubble); - }); + bubbles.forEach( + (i, bubble) -> { + // bubble moves, new position gets updated, quadtree used to reduce computations + bubble.move(); + bubbles.replace(i, bubble); + var sp = new SpatialPartitionBubbles(bubbles, quadTree); + sp.handleCollisionsUsingQt(bubble); + }); numOfMovements--; } - //bubbles not popped + // bubbles not popped bubbles.keySet().forEach(key -> LOGGER.info("Bubble {} not popped", key)); } @@ -108,7 +109,6 @@ static void withSpatialPartition( * * @param args command line args */ - public static void main(String[] args) { var bubbles1 = new ConcurrentHashMap(); var bubbles2 = new ConcurrentHashMap(); @@ -117,8 +117,8 @@ public static void main(String[] args) { var b = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1); bubbles1.put(i, b); bubbles2.put(i, b); - LOGGER.info("Bubble {} with radius {} added at ({},{})", - i, b.radius, b.coordinateX, b.coordinateY); + LOGGER.info( + "Bubble {} with radius {} added at ({},{})", i, b.radius, b.coordinateX, b.coordinateY); } var start1 = System.currentTimeMillis(); diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java index 70f8cae48d65..b2b0a10d9a0a 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java @@ -33,7 +33,6 @@ * Bubble class extends Point. In this example, we create several bubbles in the field, let them * move and keep track of which ones have popped and which ones remain. */ - @Slf4j public class Bubble extends Point { private static final SecureRandom RANDOM = new SecureRandom(); @@ -46,15 +45,15 @@ public class Bubble extends Point { } void move() { - //moves by 1 unit in either direction + // moves by 1 unit in either direction this.coordinateX += RANDOM.nextInt(3) - 1; this.coordinateY += RANDOM.nextInt(3) - 1; } boolean touches(Bubble b) { - //distance between them is greater than sum of radii (both sides of equation squared) + // distance between them is greater than sum of radii (both sides of equation squared) return (this.coordinateX - b.coordinateX) * (this.coordinateX - b.coordinateX) - + (this.coordinateY - b.coordinateY) * (this.coordinateY - b.coordinateY) + + (this.coordinateY - b.coordinateY) * (this.coordinateY - b.coordinateY) <= (this.radius + b.radius) * (this.radius + b.radius); } @@ -64,12 +63,12 @@ void pop(Map allBubbles) { } void handleCollision(Collection toCheck, Map allBubbles) { - var toBePopped = false; //if any other bubble collides with it, made true + var toBePopped = false; // if any other bubble collides with it, made true for (var point : toCheck) { var otherId = point.id; - if (allBubbles.get(otherId) != null //the bubble hasn't been popped yet - && this.id != otherId //the two bubbles are not the same - && this.touches(allBubbles.get(otherId))) { //the bubbles touch + if (allBubbles.get(otherId) != null // the bubble hasn't been popped yet + && this.id != otherId // the two bubbles are not the same + && this.touches(allBubbles.get(otherId))) { // the bubbles touch allBubbles.get(otherId).pop(allBubbles); toBePopped = true; } diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java index 1b24e70fa37e..adcc4862b155 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java @@ -33,7 +33,6 @@ * * @param T will be type subclass */ - public abstract class Point { public int coordinateX; @@ -46,9 +45,7 @@ public abstract class Point { this.id = id; } - /** - * defines how the object moves. - */ + /** defines how the object moves. */ abstract void move(); /** @@ -63,7 +60,7 @@ public abstract class Point { * handling interactions/collisions with other objects. * * @param toCheck contains the objects which need to be checked - * @param all contains hashtable of all points on field at this time + * @param all contains hashtable of all points on field at this time */ abstract void handleCollision(Collection toCheck, Map all); } diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java index 40c09a1d8f6e..c83ccc42cc43 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java @@ -33,7 +33,6 @@ * insert(Point) and query(range) methods to insert a new object and find the objects within a * certain (rectangular) range respectively. */ - public class QuadTree { Rect boundary; int capacity; @@ -93,13 +92,9 @@ void divide() { } Collection query(Rect r, Collection relevantPoints) { - //could also be a circle instead of a rectangle + // could also be a circle instead of a rectangle if (this.boundary.intersects(r)) { - this.points - .values() - .stream() - .filter(r::contains) - .forEach(relevantPoints::add); + this.points.values().stream().filter(r::contains).forEach(relevantPoints::add); if (this.divided) { this.northwest.query(r, relevantPoints); this.northeast.query(r, relevantPoints); diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java index 5fc654949e59..9f743e0d69f4 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java @@ -28,14 +28,13 @@ * The Rect class helps in defining the boundary of the quadtree and is also used to define the * range within which objects need to be found in our example. */ - public class Rect { double coordinateX; double coordinateY; double width; double height; - //(x,y) - centre of rectangle + // (x,y) - centre of rectangle Rect(double x, double y, double width, double height) { this.coordinateX = x; @@ -58,4 +57,3 @@ boolean intersects(Rect other) { || this.coordinateY - this.height / 2 >= other.coordinateY + other.height / 2); } } - diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java index 3551f5ca3b9b..cefc1a824c46 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java @@ -31,7 +31,6 @@ * This class extends the generic SpatialPartition abstract class and is used in our example to keep * track of all the bubbles that collide, pop and stay un-popped. */ - public class SpatialPartitionBubbles extends SpatialPartitionGeneric { private final Map bubbles; @@ -48,7 +47,7 @@ void handleCollisionsUsingQt(Bubble b) { var rect = new Rect(b.coordinateX, b.coordinateY, 2D * b.radius, 2D * b.radius); var quadTreeQueryResult = new ArrayList(); this.bubblesQuadTree.query(rect, quadTreeQueryResult); - //handling these collisions + // handling these collisions b.handleCollision(quadTreeQueryResult, this.bubbles); } } diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java index 7c03969a08e6..1e5baabb8d07 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java @@ -32,7 +32,6 @@ * * @param T will be type of object (that extends Point) */ - public abstract class SpatialPartitionGeneric { Map playerPositions; diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java index 2fb277057091..3f9ae809d918 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java @@ -33,10 +33,7 @@ import java.util.HashMap; import org.junit.jupiter.api.Test; -/** - * Testing methods in Bubble class. - */ - +/** Testing methods in Bubble class. */ class BubbleTest { @Test @@ -45,7 +42,7 @@ void moveTest() { var initialX = b.coordinateX; var initialY = b.coordinateY; b.move(); - //change in x and y < |2| + // change in x and y < |2| assertTrue(b.coordinateX - initialX < 2 && b.coordinateX - initialX > -2); assertTrue(b.coordinateY - initialY < 2 && b.coordinateY - initialY > -2); } @@ -55,7 +52,7 @@ void touchesTest() { var b1 = new Bubble(0, 0, 1, 2); var b2 = new Bubble(1, 1, 2, 1); var b3 = new Bubble(10, 10, 3, 1); - //b1 touches b2 but not b3 + // b1 touches b2 but not b3 assertTrue(b1.touches(b2)); assertFalse(b1.touches(b3)); } @@ -68,7 +65,7 @@ void popTest() { bubbles.put(1, b1); bubbles.put(2, b2); b1.pop(bubbles); - //after popping, bubble no longer in hashMap containing all bubbles + // after popping, bubble no longer in hashMap containing all bubbles assertNull(bubbles.get(1)); assertNotNull(bubbles.get(2)); } @@ -86,7 +83,7 @@ void handleCollisionTest() { bubblesToCheck.add(b2); bubblesToCheck.add(b3); b1.handleCollision(bubblesToCheck, bubbles); - //b1 touches b2 and not b3, so b1, b2 will be popped + // b1 touches b2 and not b3, so b1, b2 will be popped assertNull(bubbles.get(1)); assertNull(bubbles.get(2)); assertNotNull(bubbles.get(3)); diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java index 8d4adadaaa26..b1d9e48b792a 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java @@ -33,10 +33,7 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -/** - * Testing QuadTree class. - */ - +/** Testing QuadTree class. */ class QuadTreeTest { @Test @@ -47,22 +44,21 @@ void queryTest() { var p = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1); points.add(p); } - var field = new Rect(150, 150, 300, 300); //size of field - var queryRange = new Rect(70, 130, 100, 100); //result = all points lying in this rectangle - //points found in the query range using quadtree and normal method is same + var field = new Rect(150, 150, 300, 300); // size of field + var queryRange = new Rect(70, 130, 100, 100); // result = all points lying in this rectangle + // points found in the query range using quadtree and normal method is same var points1 = QuadTreeTest.quadTreeTest(points, field, queryRange); var points2 = QuadTreeTest.verify(points, queryRange); assertEquals(points1, points2); } - static Hashtable quadTreeTest(Collection points, Rect field, Rect queryRange) { - //creating quadtree and inserting all points + static Hashtable quadTreeTest( + Collection points, Rect field, Rect queryRange) { + // creating quadtree and inserting all points var qTree = new QuadTree(queryRange, 4); points.forEach(qTree::insert); - return qTree - .query(field, new ArrayList<>()) - .stream() + return qTree.query(field, new ArrayList<>()).stream() .collect(Collectors.toMap(p -> p.id, p -> p, (a, b) -> b, Hashtable::new)); } diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java index 3a2ea45628aa..0aca25388ea0 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Testing Rect class. - */ - +/** Testing Rect class. */ class RectTest { @Test @@ -40,7 +37,7 @@ void containsTest() { var r = new Rect(10, 10, 20, 20); var b1 = new Bubble(2, 2, 1, 1); var b2 = new Bubble(30, 30, 2, 1); - //r contains b1 and not b2 + // r contains b1 and not b2 assertTrue(r.contains(b1)); assertFalse(r.contains(b2)); } @@ -50,7 +47,7 @@ void intersectsTest() { var r1 = new Rect(10, 10, 20, 20); var r2 = new Rect(15, 15, 20, 20); var r3 = new Rect(50, 50, 20, 20); - //r1 intersects r2 and not r3 + // r1 intersects r2 and not r3 assertTrue(r1.intersects(r2)); assertFalse(r1.intersects(r3)); } diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java index c491ecb46e23..b36fa99bc74f 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java @@ -30,10 +30,7 @@ import java.util.HashMap; import org.junit.jupiter.api.Test; -/** - * Testing SpatialPartition_Bubbles class. - */ - +/** Testing SpatialPartition_Bubbles class. */ class SpatialPartitionBubblesTest { @Test @@ -55,7 +52,7 @@ void handleCollisionsUsingQtTest() { qt.insert(b4); var sp = new SpatialPartitionBubbles(bubbles, qt); sp.handleCollisionsUsingQt(b1); - //b1 touches b3 and b4 but not b2 - so b1,b3,b4 get popped + // b1 touches b3 and b4 but not b2 - so b1,b3,b4 get popped assertNull(bubbles.get(1)); assertNotNull(bubbles.get(2)); assertNull(bubbles.get(3)); diff --git a/special-case/README.md b/special-case/README.md index 4ce3a3c397c3..eda6ad0ff797 100644 --- a/special-case/README.md +++ b/special-case/README.md @@ -1,191 +1,108 @@ --- -title: Special Case -category: Behavioral +title: "Special Case Pattern in Java: Simplifying Exception Handling with Predefined Cases" +shortTitle: Special Case +description: "Explore the Special Case design pattern in Java for handling exceptional cases without cluttering the main code logic. Learn its applicability, real-world examples, and benefits for clean, maintainable code." +category: Structural language: en tag: - - Extensibility + - Abstraction + - Code simplification + - Decoupling + - Error handling + - Polymorphism + - Runtime --- -## Intent +## Also known as -Define some special cases, and encapsulates them into subclasses that provide different special behaviors. +* Exceptional Case -## Explanation +## Intent of Special Case Design Pattern -Real world example +The Special Case design pattern in Java offers a robust framework for addressing unique or exceptional conditions in software development without complicating the main codebase. -> In an e-commerce system, presentation layer expects application layer to produce certain view model. -> We have a successful scenario, in which receipt view model contains actual data from the purchase, -> and a couple of failure scenarios. +## Detailed Explanation of Special Case Pattern with Real-World Examples -In plain words - -> Special Case pattern allows returning non-null real objects that perform special behaviors. - -In [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html) says -the difference from Null Object Pattern - -> If you’ll pardon the unresistable pun, I see Null Object as special case of Special Case. - -**Programmatic Example** - -To focus on the pattern itself, we implement DB and maintenance lock of the e-commerce system by the singleton instance. - -```java -public class Db { - private static Db instance; - private Map userName2User; - private Map user2Account; - private Map itemName2Product; - - public static Db getInstance() { - if (instance == null) { - synchronized (Db.class) { - if (instance == null) { - instance = new Db(); - instance.userName2User = new HashMap<>(); - instance.user2Account = new HashMap<>(); - instance.itemName2Product = new HashMap<>(); - } - } - } - return instance; - } - - public void seedUser(String userName, Double amount) { - User user = new User(userName); - instance.userName2User.put(userName, user); - Account account = new Account(amount); - instance.user2Account.put(user, account); - } - - public void seedItem(String itemName, Double price) { - Product item = new Product(price); - itemName2Product.put(itemName, item); - } - - public User findUserByUserName(String userName) { - if (!userName2User.containsKey(userName)) { - return null; - } - return userName2User.get(userName); - } +Real-world example - public Account findAccountByUser(User user) { - if (!user2Account.containsKey(user)) { - return null; - } - return user2Account.get(user); - } +> Consider a toll booth system on a highway. Normally, vehicles pass through the booth, and the system charges a toll based on the vehicle type. However, there are special cases: emergency vehicles like ambulances and fire trucks, which should not be charged. +> +> For instance, in a toll management system, the Special Case pattern facilitates separate handling for emergency vehicles, ensuring a streamlined toll process without additional checks. The emergency vehicle class would override the toll calculation method to ensure no charge is applied, encapsulating this special behavior without cluttering the main toll calculation logic with conditional checks. This keeps the codebase clean and ensures the special case is handled consistently. - public Product findProductByItemName(String itemName) { - if (!itemName2Product.containsKey(itemName)) { - return null; - } - return itemName2Product.get(itemName); - } - - public class User { - private String userName; +In plain words - public User(String userName) { - this.userName = userName; - } +> The Special Case design pattern encapsulates and isolates exceptional conditions and specific scenarios to simplify the main code logic and enhance maintainability. - public String getUserName() { - return userName; - } +In [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) Martin Fowler says: - public ReceiptDto purchase(Product item) { - return new ReceiptDto(item.getPrice()); - } - } +> If you’ll pardon the unresistable pun, I see [Null Object](https://java-design-patterns.com/patterns/null-object/) as special case of Special Case. - public class Account { - private Double amount; +## Programmatic Example of Special Case Pattern in Java - public Account(Double amount) { - this.amount = amount; - } +The Special Case Pattern is a software design pattern that is used to handle a specific, often uncommon, case separately from the general case in the code. This pattern is useful when a class has behavior that requires conditional logic based on its state. Instead of cluttering the class with conditional logic, we can encapsulate the special behavior in a subclass. - public MoneyTransaction withdraw(Double price) { - if (price > amount) { - return null; - } - return new MoneyTransaction(amount, price); - } +In an e-commerce system, the presentation layer relies on the application layer to generate a specific view model. There is a successful scenario where the receipt view model includes actual purchase data, along with a few failure scenarios. - public Double getAmount() { - return amount; - } - } +The `Db` class is a singleton that holds data for users, accounts, and products. It provides methods to seed data into the database and find data in the database. - public class Product { - private Double price; +```java +@RequiredArgsConstructor +@Getter +public class Db { + // Singleton instance of Db + private static Db instance; - public Product(Double price) { - this.price = price; - } + // Maps to hold data + private Map userName2User; + private Map user2Account; + private Map itemName2Product; - public Double getPrice() { - return price; + // Singleton method to get instance of Db + public static synchronized Db getInstance() { + if (instance == null) { + Db newInstance = new Db(); + newInstance.userName2User = new HashMap<>(); + newInstance.user2Account = new HashMap<>(); + newInstance.itemName2Product = new HashMap<>(); + instance = newInstance; + } + return instance; } - } -} -public class MaintenanceLock { - private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class); + // Methods to seed data into Db + public void seedUser(String userName, Double amount) { /*...*/ } - private static MaintenanceLock instance; - private boolean lock = true; + public void seedItem(String itemName, Double price) { /*...*/ } - public static MaintenanceLock getInstance() { - if (instance == null) { - synchronized (MaintenanceLock.class) { - if (instance == null) { - instance = new MaintenanceLock(); - } - } - } - return instance; - } + // Methods to find data in Db + public User findUserByUserName(String userName) { /*...*/ } - public boolean isLock() { - return lock; - } + public Account findAccountByUser(User user) { /*...*/ } - public void setLock(boolean lock) { - this.lock = lock; - LOGGER.info("Maintenance lock is set to: " + lock); - } + public Product findProductByItemName(String itemName) { /*...*/ } } ``` -Let's first introduce presentation layer, the receipt view model interface and its implementation of successful scenario. +Next, here are the presentation layer, the receipt view model interface and its implementation of successful scenario. ```java public interface ReceiptViewModel { - void show(); + void show(); } +``` +```java +@RequiredArgsConstructor +@Getter public class ReceiptDto implements ReceiptViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class); - private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class); + private final Double price; - private Double price; - - public ReceiptDto(Double price) { - this.price = price; - } - - public Double getPrice() { - return price; - } - - @Override - public void show() { - LOGGER.info("Receipt: " + price + " paid"); - } + @Override + public void show() { + LOGGER.info(String.format("Receipt: %s paid", price)); + } } ``` @@ -193,174 +110,172 @@ And here are the implementations of failure scenarios, which are the special cas ```java public class DownForMaintenance implements ReceiptViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class); - @Override - public void show() { - LOGGER.info("Down for maintenance"); - } + @Override + public void show() { + LOGGER.info("Down for maintenance"); + } } +``` +```java public class InvalidUser implements ReceiptViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class); + private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class); - private final String userName; + private final String userName; - public InvalidUser(String userName) { - this.userName = userName; - } + public InvalidUser(String userName) { + this.userName = userName; + } - @Override - public void show() { - LOGGER.info("Invalid user: " + userName); - } + @Override + public void show() { + LOGGER.info("Invalid user: " + userName); + } } +``` +```java public class OutOfStock implements ReceiptViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class); - - private String userName; - private String itemName; + private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class); - public OutOfStock(String userName, String itemName) { - this.userName = userName; - this.itemName = itemName; - } + private String userName; + private String itemName; - @Override - public void show() { - LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy"); - } -} + public OutOfStock(String userName, String itemName) { + this.userName = userName; + this.itemName = itemName; + } -public class InsufficientFunds implements ReceiptViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class); - - private String userName; - private Double amount; - private String itemName; - - public InsufficientFunds(String userName, Double amount, String itemName) { - this.userName = userName; - this.amount = amount; - this.itemName = itemName; - } - - @Override - public void show() { - LOGGER.info("Insufficient funds: " + amount + " of user: " + userName - + " for buying item: " + itemName); - } + @Override + public void show() { + LOGGER.info("Out of stock: " + itemName + " for user = " + userName + " to buy"); + } } ``` -Second, here's the application layer, the application services implementation and the domain services implementation. - ```java -public class ApplicationServicesImpl implements ApplicationServices { - private DomainServicesImpl domain = new DomainServicesImpl(); - - @Override - public ReceiptViewModel loggedInUserPurchase(String userName, String itemName) { - if (isDownForMaintenance()) { - return new DownForMaintenance(); - } - return this.domain.purchase(userName, itemName); - } - - private boolean isDownForMaintenance() { - return MaintenanceLock.getInstance().isLock(); - } -} - -public class DomainServicesImpl implements DomainServices { - public ReceiptViewModel purchase(String userName, String itemName) { - Db.User user = Db.getInstance().findUserByUserName(userName); - if (user == null) { - return new InvalidUser(userName); - } +public class InsufficientFunds implements ReceiptViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(InsufficientFunds.class); - Db.Account account = Db.getInstance().findAccountByUser(user); - return purchase(user, account, itemName); - } + private String userName; + private Double amount; + private String itemName; - private ReceiptViewModel purchase(Db.User user, Db.Account account, String itemName) { - Db.Product item = Db.getInstance().findProductByItemName(itemName); - if (item == null) { - return new OutOfStock(user.getUserName(), itemName); + public InsufficientFunds(String userName, Double amount, String itemName) { + this.userName = userName; + this.amount = amount; + this.itemName = itemName; } - ReceiptDto receipt = user.purchase(item); - MoneyTransaction transaction = account.withdraw(receipt.getPrice()); - if (transaction == null) { - return new InsufficientFunds(user.getUserName(), account.getAmount(), itemName); + @Override + public void show() { + LOGGER.info("Insufficient funds: " + amount + " of user: " + userName + + " for buying item: " + itemName); } - - return receipt; - } } ``` -Finally, the client send requests the application services to get the presentation view. +Here is the `App` and its `main` function that executes the different scenarios. ```java - // DB seeding - LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, " - + "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}"); - Db.getInstance().seedUser("ignite1771", 1000.0); - Db.getInstance().seedItem("computer", 800.0); - Db.getInstance().seedItem("car", 20000.0); - - var applicationServices = new ApplicationServicesImpl(); - ReceiptViewModel receipt; - - LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv"); - receipt = applicationServices.loggedInUserPurchase("abc123", "tv"); - receipt.show(); - MaintenanceLock.getInstance().setLock(false); - LOGGER.info("[REQUEST] User: " + "abc123" + " buy product: " + "tv"); - receipt = applicationServices.loggedInUserPurchase("abc123", "tv"); - receipt.show(); - LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "tv"); - receipt = applicationServices.loggedInUserPurchase("ignite1771", "tv"); - receipt.show(); - LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "car"); - receipt = applicationServices.loggedInUserPurchase("ignite1771", "car"); - receipt.show(); - LOGGER.info("[REQUEST] User: " + "ignite1771" + " buy product: " + "computer"); - receipt = applicationServices.loggedInUserPurchase("ignite1771", "computer"); - receipt.show(); +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private static final String LOGGER_STRING = "[REQUEST] User: {} buy product: {}"; + private static final String TEST_USER_1 = "ignite1771"; + private static final String TEST_USER_2 = "abc123"; + private static final String ITEM_TV = "tv"; + private static final String ITEM_CAR = "car"; + private static final String ITEM_COMPUTER = "computer"; + + public static void main(String[] args) { + // DB seeding + LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, " + + "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}"); + Db.getInstance().seedUser(TEST_USER_1, 1000.0); + Db.getInstance().seedItem(ITEM_COMPUTER, 800.0); + Db.getInstance().seedItem(ITEM_CAR, 20000.0); + + final var applicationServices = new ApplicationServicesImpl(); + ReceiptViewModel receipt; + + LOGGER.info(LOGGER_STRING, TEST_USER_2, ITEM_TV); + receipt = applicationServices.loggedInUserPurchase(TEST_USER_2, ITEM_TV); + receipt.show(); + MaintenanceLock.getInstance().setLock(false); + LOGGER.info(LOGGER_STRING, TEST_USER_2, ITEM_TV); + receipt = applicationServices.loggedInUserPurchase(TEST_USER_2, ITEM_TV); + receipt.show(); + LOGGER.info(LOGGER_STRING, TEST_USER_1, ITEM_TV); + receipt = applicationServices.loggedInUserPurchase(TEST_USER_1, ITEM_TV); + receipt.show(); + LOGGER.info(LOGGER_STRING, TEST_USER_1, ITEM_CAR); + receipt = applicationServices.loggedInUserPurchase(TEST_USER_1, ITEM_CAR); + receipt.show(); + LOGGER.info(LOGGER_STRING, TEST_USER_1, ITEM_COMPUTER); + receipt = applicationServices.loggedInUserPurchase(TEST_USER_1, ITEM_COMPUTER); + receipt.show(); + } +} ``` -Program output of every request: +Here is the output from running the example. ``` - Down for maintenance - Invalid user: abc123 - Out of stock: tv for user = ignite1771 to buy - Insufficient funds: 1000.0 of user: ignite1771 for buying item: car - Receipt: 800.0 paid +11:23:48.669 [main] INFO com.iluwatar.specialcase.App -- Db seeding: 1 user: {"ignite1771", amount = 1000.0}, 2 products: {"computer": price = 800.0, "car": price = 20000.0} +11:23:48.672 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: abc123 buy product: tv +11:23:48.672 [main] INFO com.iluwatar.specialcase.DownForMaintenance -- Down for maintenance +11:23:48.672 [main] INFO com.iluwatar.specialcase.MaintenanceLock -- Maintenance lock is set to: false +11:23:48.672 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: abc123 buy product: tv +11:23:48.673 [main] INFO com.iluwatar.specialcase.InvalidUser -- Invalid user: abc123 +11:23:48.674 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: ignite1771 buy product: tv +11:23:48.674 [main] INFO com.iluwatar.specialcase.OutOfStock -- Out of stock: tv for user = ignite1771 to buy +11:23:48.674 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: ignite1771 buy product: car +11:23:48.676 [main] INFO com.iluwatar.specialcase.InsufficientFunds -- Insufficient funds: 1000.0 of user: ignite1771 for buying item: car +11:23:48.676 [main] INFO com.iluwatar.specialcase.App -- [REQUEST] User: ignite1771 buy product: computer +11:23:48.676 [main] INFO com.iluwatar.specialcase.ReceiptDto -- Receipt: 800.0 paid ``` -## Class diagram +In conclusion, the Special Case Pattern helps to keep the code clean and easy to understand by separating the special case from the general case. It also promotes code reuse and makes the code easier to maintain. + +## When to Use the Special Case Pattern in Java + +* Use when you want to encapsulate and handle special cases or error conditions in a manner that avoids conditional logic scattered throughout the main codebase. +* Useful in scenarios where certain operations have known exceptional cases that require different handling. + +## Real-World Applications of Special Case Pattern in Java + +* Implementing null object patterns to avoid null checks. +* Handling specific business rules or validation logic in e-commerce applications. +* Managing different file formats or protocols in data processing applications. + +## Benefits and Trade-offs of Special Case Pattern + +Benefits: -![alt text](./etc/special_case_urm.png "Special Case") +Adopting the Special Case design pattern -## Applicability +* Simplifies the main logic by removing special case handling from the core algorithms. +* Enhances code readability and maintainability by isolating special cases. -Use the Special Case pattern when +Trade-offs: -* You have multiple places in the system that have the same behavior after a conditional check -for a particular class instance, or the same behavior after a null check. -* Return a real object that performs the real behavior, instead of a null object that performs nothing. +* May introduce additional classes or interfaces, increasing the number of components in the system. +* Requires careful design to ensure that special cases are correctly encapsulated and do not introduce unexpected behaviors. -## Tutorial +## Related Java Design Patterns -* [Special Case Tutorial](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case) +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used to add special case behavior to objects dynamically without modifying their code. +* [Null Object](https://java-design-patterns.com/patterns/null-object/): Used to provide a default behavior for null references, which is a specific type of special case. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Allows dynamic switching of special case behaviors by encapsulating them in different strategy classes. -## Credits +## References and Credits -* [How to Reduce Cyclomatic Complexity Part 2: Special Case Pattern](https://www.codinghelmet.com/articles/reduce-cyclomatic-complexity-special-case) -* [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html) -* [Special Case](https://www.martinfowler.com/eaaCatalog/specialCase.html) \ No newline at end of file +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Special Case (Martin Fowler)](https://www.martinfowler.com/eaaCatalog/specialCase.html) diff --git a/special-case/pom.xml b/special-case/pom.xml index 59ec1c10f121..55400ffba9bc 100644 --- a/special-case/pom.xml +++ b/special-case/pom.xml @@ -34,10 +34,37 @@ 4.0.0 special-case + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.specialcase.App + + + + + + + +
    diff --git a/special-case/src/main/java/com/iluwatar/specialcase/App.java b/special-case/src/main/java/com/iluwatar/specialcase/App.java index 5c6a10602543..6bbad3322b40 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/App.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/App.java @@ -28,10 +28,10 @@ import org.slf4j.LoggerFactory; /** - *

    The Special Case Pattern is a software design pattern that encapsulates particular cases - * into subclasses that provide special behaviors.

    + * The Special Case Pattern is a software design pattern that encapsulates particular cases into + * subclasses that provide special behaviors. * - *

    In this example ({@link ReceiptViewModel}) encapsulates all particular cases.

    + *

    In this example ({@link ReceiptViewModel}) encapsulates all particular cases. */ public class App { @@ -44,13 +44,13 @@ public class App { private static final String ITEM_CAR = "car"; private static final String ITEM_COMPUTER = "computer"; - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { // DB seeding - LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, " - + "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}"); + LOGGER.info( + "Db seeding: " + + "1 user: {\"ignite1771\", amount = 1000.0}, " + + "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}"); Db.getInstance().seedUser(TEST_USER_1, 1000.0); Db.getInstance().seedItem(ITEM_COMPUTER, 800.0); Db.getInstance().seedItem(ITEM_CAR, 20000.0); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java index 9dafdba44f68..507415663f31 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * ApplicationServices interface to demonstrate special case pattern. - */ +/** ApplicationServices interface to demonstrate special case pattern. */ public interface ApplicationServices { ReceiptViewModel loggedInUserPurchase(String userName, String itemName); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java index cb21e57f6592..fa455a23dca2 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * Implementation of special case pattern. - */ +/** Implementation of special case pattern. */ public class ApplicationServicesImpl implements ApplicationServices { private DomainServicesImpl domain = new DomainServicesImpl(); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/Db.java b/special-case/src/main/java/com/iluwatar/specialcase/Db.java index c5829431995e..32ac6501d4ef 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/Db.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/Db.java @@ -26,10 +26,10 @@ import java.util.HashMap; import java.util.Map; +import lombok.Getter; +import lombok.RequiredArgsConstructor; -/** - * DB class for seeding user info. - */ +/** DB class for seeding user info. */ public class Db { private static Db instance; @@ -116,36 +116,24 @@ public Product findProductByItemName(String itemName) { return itemName2Product.get(itemName); } - /** - * User class to store user info. - */ + /** User class to store user info. */ + @RequiredArgsConstructor + @Getter public class User { - private String userName; - - public User(String userName) { - this.userName = userName; - } - - public String getUserName() { - return userName; - } + private final String userName; public ReceiptDto purchase(Product item) { return new ReceiptDto(item.getPrice()); } } - /** - * Account info. - */ - public class Account { + /** Account info. */ + @RequiredArgsConstructor + @Getter + public static class Account { - private Double amount; - - public Account(Double amount) { - this.amount = amount; - } + private final Double amount; /** * Withdraw the price of the item from the account. @@ -159,25 +147,13 @@ public MoneyTransaction withdraw(Double price) { } return new MoneyTransaction(amount, price); } - - public Double getAmount() { - return amount; - } } - /** - * Product info. - */ - public class Product { + /** Product info. */ + @RequiredArgsConstructor + @Getter + public static class Product { - private Double price; - - public Product(Double price) { - this.price = price; - } - - public Double getPrice() { - return price; - } + private final Double price; } } diff --git a/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java b/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java index 84502e64d4fc..daf14c062310 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java @@ -24,8 +24,5 @@ */ package com.iluwatar.specialcase; -/** - * DomainServices interface. - */ -public interface DomainServices { -} +/** DomainServices interface. */ +public interface DomainServices {} diff --git a/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java b/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java index ac31a90839c1..54c7b99833cb 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * Implementation of DomainServices for special case. - */ +/** Implementation of DomainServices for special case. */ public class DomainServicesImpl implements DomainServices { /** @@ -47,9 +45,8 @@ public ReceiptViewModel purchase(String userName, String itemName) { } /** - * Domain purchase with user, account and itemName, - * with validation for whether product is out of stock - * and whether user has insufficient funds in the account. + * Domain purchase with user, account and itemName, with validation for whether product is out of + * stock and whether user has insufficient funds in the account. * * @param user in Db * @param account in Db diff --git a/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java b/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java index 713dcaef7686..504678ecad1e 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java @@ -27,9 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Down for Maintenance view for the ReceiptViewModel. - */ +/** Down for Maintenance view for the ReceiptViewModel. */ public class DownForMaintenance implements ReceiptViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java b/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java index 03dff2c41d2c..66c974ca5bd8 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View representing insufficient funds. - */ +/** View representing insufficient funds. */ @Slf4j public class InsufficientFunds implements ReceiptViewModel { @@ -51,7 +49,12 @@ public InsufficientFunds(String userName, Double amount, String itemName) { @Override public void show() { - LOGGER.info("Insufficient funds: " + amount + " of user: " + userName - + " for buying item: " + itemName); + LOGGER.info( + "Insufficient funds: " + + amount + + " of user: " + + userName + + " for buying item: " + + itemName); } } diff --git a/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java b/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java index 861d69e8c98d..37c08d1f72ce 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java @@ -27,9 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Receipt View representing invalid user. - */ +/** Receipt View representing invalid user. */ public class InvalidUser implements ReceiptViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java b/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java index 341dbee85686..0e39e900f538 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java @@ -24,18 +24,18 @@ */ package com.iluwatar.specialcase; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Acquire lock on the DB for maintenance. - */ +/** Acquire lock on the DB for maintenance. */ public class MaintenanceLock { private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class); private static MaintenanceLock instance; - private boolean lock = true; + + @Getter private boolean lock = true; /** * Get the instance of MaintenanceLock. @@ -49,10 +49,6 @@ public static synchronized MaintenanceLock getInstance() { return instance; } - public boolean isLock() { - return lock; - } - public void setLock(boolean lock) { this.lock = lock; LOGGER.info("Maintenance lock is set to: {}", lock); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java b/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java index 8ff191501286..cdaf3fb785b9 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java @@ -24,16 +24,12 @@ */ package com.iluwatar.specialcase; -/** - * Represents the money transaction taking place at a given moment. - */ -public class MoneyTransaction { +import lombok.RequiredArgsConstructor; - private Double amount; - private Double price; +/** Represents the money transaction taking place at a given moment. */ +@RequiredArgsConstructor +public class MoneyTransaction { - public MoneyTransaction(Double amount, Double price) { - this.amount = amount; - this.price = price; - } + private final Double amount; + private final Double price; } diff --git a/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java b/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java index 5bfa09186d2d..79c47df9ae6d 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java @@ -27,9 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Receipt view for showing out of stock message. - */ +/** Receipt view for showing out of stock message. */ public class OutOfStock implements ReceiptViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java index c619ee87d061..45d2129d6421 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java @@ -24,26 +24,20 @@ */ package com.iluwatar.specialcase; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Receipt view representing the transaction recceipt. - */ +/** Receipt view representing the transaction recceipt. */ +@RequiredArgsConstructor +@Getter public class ReceiptDto implements ReceiptViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptDto.class); private final Double price; - public ReceiptDto(Double price) { - this.price = price; - } - - public Double getPrice() { - return price; - } - @Override public void show() { LOGGER.info(String.format("Receipt: %s paid", price)); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java index 633005b07d30..df33227bb245 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * ReceiptViewModel interface. - */ +/** ReceiptViewModel interface. */ public interface ReceiptViewModel { void show(); diff --git a/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java b/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java index a571024a57c0..15dd9cf1074a 100644 --- a/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java +++ b/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test. - */ +/** Application test. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java b/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java index 8411398b2020..da5d532b8ac9 100644 --- a/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java +++ b/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java @@ -26,19 +26,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.BeforeAll; -import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import java.util.List; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; -/** - * Special cases unit tests. (including the successful scenario {@link ReceiptDto}) - */ +/** Special cases unit tests. (including the successful scenario {@link ReceiptDto}) */ class SpecialCasesTest { private static ApplicationServices applicationServices; private static ReceiptViewModel receipt; @@ -102,8 +100,8 @@ void testOutOfStock() { receipt.show(); List loggingEventList = listAppender.list; - assertEquals("Out of stock: tv for user = ignite1771 to buy" - , loggingEventList.get(0).getMessage()); + assertEquals( + "Out of stock: tv for user = ignite1771 to buy", loggingEventList.get(0).getMessage()); assertEquals(Level.INFO, loggingEventList.get(0).getLevel()); } @@ -119,8 +117,9 @@ void testInsufficientFunds() { receipt.show(); List loggingEventList = listAppender.list; - assertEquals("Insufficient funds: 1000.0 of user: ignite1771 for buying item: car" - , loggingEventList.get(0).getMessage()); + assertEquals( + "Insufficient funds: 1000.0 of user: ignite1771 for buying item: car", + loggingEventList.get(0).getMessage()); assertEquals(Level.INFO, loggingEventList.get(0).getLevel()); } @@ -136,8 +135,7 @@ void testReceiptDto() { receipt.show(); List loggingEventList = listAppender.list; - assertEquals("Receipt: 800.0 paid" - , loggingEventList.get(0).getMessage()); + assertEquals("Receipt: 800.0 paid", loggingEventList.get(0).getMessage()); assertEquals(Level.INFO, loggingEventList.get(0).getLevel()); } } diff --git a/specification/README.md b/specification/README.md index d7dfd5e00127..4ccb68cc55de 100644 --- a/specification/README.md +++ b/specification/README.md @@ -1,60 +1,59 @@ --- -title: Specification +title: "Specification Pattern in Java: Enhancing Business Rules with Decoupled Logic" +shortTitle: Specification +description: "Dive deep into the Specification design pattern in Java, a strategic solution for encapsulating business rules. Learn how to implement, combine, and apply this pattern effectively in your software development projects." category: Behavioral language: en tag: - - Data access + - Business + - Domain + - Encapsulation + - Enterprise patterns + - Extensibility --- ## Also known as -Filter, Criteria +* Filter +* Criteria -## Intent +## Intent of Specification Design Pattern -Specification pattern separates the statement of how to match a candidate, from the candidate object -that it is matched against. As well as its usefulness in selection, it is also valuable for -validation and for building to order. +Encapsulate business rules and criteria that an object must satisfy to enable checking these rules in various parts of the application. -## Explanation +## Detailed Explanation of Specification Pattern with Real-World Examples -Real world example +Real-world example -> There is a pool of different creatures and we often need to select some subset of them. We can -> write our search specification such as "creatures that can fly", "creatures heavier than 500 -> kilograms", or as a combination of other search specifications, and then give it to the party that -> will perform the filtering. +> Imagine you are organizing a conference and need to filter attendees based on specific criteria such as registration status, payment completion, and session interests. +> +> Using the Specification design pattern, you would create separate specifications for each criterion (e.g., "IsRegistered", "HasPaid", "IsInterestedInSessionX"). These specifications can be combined dynamically to filter attendees who meet all the required criteria, such as those who are registered, have completed their payment, and are interested in a particular session. This approach allows for flexible and reusable business rules, ensuring that the filtering logic can be easily adjusted as needed without changing the underlying attendee objects. -In Plain Words +In plain words -> Specification pattern allows us to separate the search criteria from the object that performs the -> search. +> The Specification design pattern in Java enables the efficient encapsulation and reuse of business rules, offering a flexible and dynamic way to combine criteria for robust software development Wikipedia says -> In computer programming, the specification pattern is a particular software design pattern, -> whereby business rules can be recombined by chaining the business rules together using boolean -> logic. +> In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. -**Programmatic Example** +## Programmatic Example of Specification Pattern in Java -If we look at our creature pool example from above, we have a set of creatures with certain -properties. Those properties can be part of a pre-defined, limited set (represented here by the -enums Size, Movement and Color); but they can also be continuous values (e.g. the mass of a -Creature). In this case, it is more appropriate to use what we call "parameterized specification", -where the property value can be given as an argument when the Creature is instantiated, allowing for -more flexibility. A third option is to combine pre-defined and/or parameterized properties using -boolean logic, allowing for near-endless selection possibilities (this is called "composite -specification", see below). The pros and cons of each approach are detailed in the table at the end -of this document. +Let's consider a creature pool example. We have a collection of creatures with specific properties. These properties might belong to a predefined, limited set (represented by enums like `Size`, `Movement`, and `Color`) or they might be continuous values (e.g., the mass of a `Creature`). In cases with continuous values, it's better to use a "parameterized specification," where the property value is provided as an argument when the `Creature` is instantiated, allowing for greater flexibility. Additionally, predefined and/or parameterized properties can be combined using boolean logic, offering almost limitless selection possibilities (this is known as a "composite specification," explained further below). The advantages and disadvantages of each approach are detailed in the table at the end of this document. + +First, here is interface `Creature`. ```java public interface Creature { - String getName(); - Size getSize(); - Movement getMovement(); - Color getColor(); - Mass getMass(); + String getName(); + + Size getSize(); + + Movement getMovement(); + + Color getColor(); + + Mass getMass(); } ``` @@ -69,8 +68,24 @@ public class Dragon extends AbstractCreature { } ``` -Now that we want to select some subset of them, we use selectors. To select creatures that fly, we -should use `MovementSelector`. +Now that we want to select some subset of them, we use selectors. To select creatures that fly, we should use `MovementSelector`. The snippet also shows the base class `AbstractSelector`. + +```java +public abstract class AbstractSelector implements Predicate { + + public AbstractSelector and(AbstractSelector other) { + return new ConjunctionSelector<>(this, other); + } + + public AbstractSelector or(AbstractSelector other) { + return new DisjunctionSelector<>(this, other); + } + + public AbstractSelector not() { + return new NegationSelector<>(this); + } +} +``` ```java public class MovementSelector extends AbstractSelector { @@ -88,8 +103,7 @@ public class MovementSelector extends AbstractSelector { } ``` -On the other hand, when selecting creatures heavier than a chosen amount, we use -`MassGreaterThanSelector`. +On the other hand, when selecting creatures heavier than a chosen amount, we use `MassGreaterThanSelector`. ```java public class MassGreaterThanSelector extends AbstractSelector { @@ -107,108 +121,129 @@ public class MassGreaterThanSelector extends AbstractSelector { } ``` -With these building blocks in place, we can perform a search for red creatures as follows: - -```java - var redCreatures = creatures.stream().filter(new ColorSelector(Color.RED)) - .collect(Collectors.toList()); -``` - -But we could also use our parameterized selector like this: - -```java - var heavyCreatures = creatures.stream().filter(new MassGreaterThanSelector(500.0) - .collect(Collectors.toList()); -``` - -Our third option is to combine multiple selectors together. Performing a search for special -creatures (defined as red, flying, and not small) could be done as follows: - -```java - var specialCreaturesSelector = - new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING)).and(new SizeSelector(Size.SMALL).not()); - - var specialCreatures = creatures.stream().filter(specialCreaturesSelector) - .collect(Collectors.toList()); -``` - -**More on Composite Specification** - -In Composite Specification, we will create custom instances of `AbstractSelector` by combining -other selectors (called "leaves") using the three basic logical operators. These are implemented in -`ConjunctionSelector`, `DisjunctionSelector` and `NegationSelector`. +With these building blocks in place, we can perform some searches. ```java -public abstract class AbstractSelector implements Predicate { - - public AbstractSelector and(AbstractSelector other) { - return new ConjunctionSelector<>(this, other); - } - - public AbstractSelector or(AbstractSelector other) { - return new DisjunctionSelector<>(this, other); +@Slf4j +public class App { + + public static void main(String[] args) { + // initialize creatures list + var creatures = List.of( + new Goblin(), + new Octopus(), + new Dragon(), + new Shark(), + new Troll(), + new KillerBee() + ); + // so-called "hard-coded" specification + LOGGER.info("Demonstrating hard-coded specification :"); + // find all walking creatures + LOGGER.info("Find all walking creatures"); + print(creatures, new MovementSelector(Movement.WALKING)); + // find all dark creatures + LOGGER.info("Find all dark creatures"); + print(creatures, new ColorSelector(Color.DARK)); + LOGGER.info("\n"); + // so-called "parameterized" specification + LOGGER.info("Demonstrating parameterized specification :"); + // find all creatures heavier than 500kg + LOGGER.info("Find all creatures heavier than 600kg"); + print(creatures, new MassGreaterThanSelector(600.0)); + // find all creatures heavier than 500kg + LOGGER.info("Find all creatures lighter than or weighing exactly 500kg"); + print(creatures, new MassSmallerThanOrEqSelector(500.0)); + LOGGER.info("\n"); + // so-called "composite" specification + LOGGER.info("Demonstrating composite specification :"); + // find all red and flying creatures + LOGGER.info("Find all red and flying creatures"); + var redAndFlying = new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING)); + print(creatures, redAndFlying); + // find all creatures dark or red, non-swimming, and heavier than or equal to 400kg + LOGGER.info("Find all scary creatures"); + var scaryCreaturesSelector = new ColorSelector(Color.DARK) + .or(new ColorSelector(Color.RED)).and(new MovementSelector(Movement.SWIMMING).not()) + .and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0))); + print(creatures, scaryCreaturesSelector); } - public AbstractSelector not() { - return new NegationSelector<>(this); + private static void print(List creatures, Predicate selector) { + creatures.stream().filter(selector).map(Objects::toString).forEach(LOGGER::info); } } ``` -```java -public class ConjunctionSelector extends AbstractSelector { +Console output: - private final List> leafComponents; +``` +12:49:24.808 [main] INFO com.iluwatar.specification.app.App -- Demonstrating hard-coded specification : +12:49:24.810 [main] INFO com.iluwatar.specification.app.App -- Find all walking creatures +12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Goblin [size=small, movement=walking, color=green, mass=30.0kg] +12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg] +12:49:24.812 [main] INFO com.iluwatar.specification.app.App -- Find all dark creatures +12:49:24.815 [main] INFO com.iluwatar.specification.app.App -- Octopus [size=normal, movement=swimming, color=dark, mass=12.0kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- + +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Demonstrating parameterized specification : +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all creatures heavier than 600kg +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all creatures lighter than or weighing exactly 500kg +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Goblin [size=small, movement=walking, color=green, mass=30.0kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Octopus [size=normal, movement=swimming, color=dark, mass=12.0kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Shark [size=normal, movement=swimming, color=light, mass=500.0kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- KillerBee [size=small, movement=flying, color=light, mass=6.7kg] +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- + +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Demonstrating composite specification : +12:49:24.816 [main] INFO com.iluwatar.specification.app.App -- Find all red and flying creatures +12:49:24.817 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg] +12:49:24.817 [main] INFO com.iluwatar.specification.app.App -- Find all scary creatures +12:49:24.818 [main] INFO com.iluwatar.specification.app.App -- Dragon [size=large, movement=flying, color=red, mass=39300.0kg] +12:49:24.818 [main] INFO com.iluwatar.specification.app.App -- Troll [size=large, movement=walking, color=dark, mass=4000.0kg] +``` - @SafeVarargs - ConjunctionSelector(AbstractSelector... selectors) { - this.leafComponents = List.of(selectors); - } +Adopting the Specification pattern significantly enhances the flexibility and reusability of business rules within Java applications, contributing to more maintainable code. - /** - * Tests if *all* selectors pass the test. - */ - @Override - public boolean test(T t) { - return leafComponents.stream().allMatch(comp -> (comp.test(t))); - } -} -``` +## When to Use the Specification Pattern in Java + +Apply the Java Specification pattern when -All that is left to do is now to create leaf selectors (be it hard-coded or parameterized ones) that -are as generic as possible, and we will be able to instantiate the ``AbstractSelector`` class by -combining any amount of selectors, as exemplified above. We should be careful though, as it is easy -to make a mistake when combining many logical operators; in particular, we should pay attention to -the priority of the operations. In general, Composite Specification is a great way to write more -reusable code, as there is no need to create a Selector class for each filtering operation. Instead, -we just create an instance of ``AbstractSelector`` "on the spot", using tour generic "leaf" -selectors and some basic boolean logic. +* You need to filter objects based on different criteria. +* The filtering criteria can change dynamically. +* Ideal for use cases involving complex business rules that must be reused across different parts of an application. -**Comparison of the different approaches** +## Real-World Applications of Specification Pattern in Java -| Pattern | Usage | Pros | Cons | -|---|---|---|---| -| Hard-Coded Specification | Selection criteria are few and known in advance | + Easy to implement | - Inflexible | -| | | + Expressive | -| Parameterized Specification | Selection criteria are a large range of values (e.g. mass, speed,...) | + Some flexibility | - Still requires special-purpose classes | -| Composite Specification | There are a lot of selection criteria that can be combined in multiple ways, hence it is not feasible to create a class for each selector | + Very flexible, without requiring many specialized classes | - Somewhat more difficult to comprehend | -| | | + Supports logical operations | - You still need to create the base classes used as leaves | +* Validating user inputs in enterprise applications. +* Filtering search results in e-commerce applications. +* Business rule validation in domain-driven design (DDD). -## Class diagram +## Benefits and Trade-offs of Specification Pattern -![alt text](./etc/specification.png "Specification") +Benefits: -## Applicability +* Enhances the flexibility and reusability of business rules. +* Promotes [single responsibility principle](https://java-design-patterns.com/principles/#single-responsibility-principle) by separating business rules from the entities. +* Facilitates unit testing of business rules. -Use the Specification pattern when +Trade-offs: -* You need to select a subset of objects based on some criteria, and to refresh the selection at various times. -* You need to check that only suitable objects are used for a certain role (validation). +* Can lead to a proliferation of small classes, increasing complexity. +* Might introduce performance overhead due to the dynamic checking of specifications. -## Related patterns +## Related Java Design Patterns -* Repository +* [Composite](https://java-design-patterns.com/patterns/composite/): Often used together with Specification to combine multiple specifications. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used to add additional criteria to a specification dynamically. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Both patterns involve encapsulating a family of algorithms. Strategy encapsulates different strategies or algorithms, while Specification encapsulates business rules. -## Credits +## References and Credits -* [Martin Fowler - Specifications](http://martinfowler.com/apsupp/spec.pdf) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Implementing Domain-Driven Design](https://amzn.to/4dmBjrB) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Specifications (Martin Fowler)](http://martinfowler.com/apsupp/spec.pdf) diff --git a/specification/pom.xml b/specification/pom.xml index 6f97d68dcddc..078025193aba 100644 --- a/specification/pom.xml +++ b/specification/pom.xml @@ -34,6 +34,14 @@ specification + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/specification/src/main/java/com/iluwatar/specification/app/App.java b/specification/src/main/java/com/iluwatar/specification/app/App.java index 381cce202c3a..83486d21d862 100644 --- a/specification/src/main/java/com/iluwatar/specification/app/App.java +++ b/specification/src/main/java/com/iluwatar/specification/app/App.java @@ -44,32 +44,25 @@ import lombok.extern.slf4j.Slf4j; /** - *

    The central idea of the Specification pattern is to separate the statement of how to match a + * The central idea of the Specification pattern is to separate the statement of how to match a * candidate, from the candidate object that it is matched against. As well as its usefulness in - * selection, it is also valuable for validation and for building to order.

    + * selection, it is also valuable for validation and for building to order. * *

    In this example we have a pool of creatures with different properties. We then have defined * separate selection rules (Specifications) that we apply to the collection and as output receive - * only the creatures that match the selection criteria.

    + * only the creatures that match the selection criteria. * - *

    http://martinfowler.com/apsupp/spec.pdf

    + *

    http://martinfowler.com/apsupp/spec.pdf */ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { // initialize creatures list - var creatures = List.of( - new Goblin(), - new Octopus(), - new Dragon(), - new Shark(), - new Troll(), - new KillerBee() - ); + var creatures = + List.of( + new Goblin(), new Octopus(), new Dragon(), new Shark(), new Troll(), new KillerBee()); // so-called "hard-coded" specification LOGGER.info("Demonstrating hard-coded specification :"); // find all walking creatures @@ -96,9 +89,11 @@ public static void main(String[] args) { print(creatures, redAndFlying); // find all creatures dark or red, non-swimming, and heavier than or equal to 400kg LOGGER.info("Find all scary creatures"); - var scaryCreaturesSelector = new ColorSelector(Color.DARK) - .or(new ColorSelector(Color.RED)).and(new MovementSelector(Movement.SWIMMING).not()) - .and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0))); + var scaryCreaturesSelector = + new ColorSelector(Color.DARK) + .or(new ColorSelector(Color.RED)) + .and(new MovementSelector(Movement.SWIMMING).not()) + .and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0))); print(creatures, scaryCreaturesSelector); } diff --git a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java index 610404fb914e..9afa65e432fe 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Base class for concrete creatures. - */ +/** Base class for concrete creatures. */ public abstract class AbstractCreature implements Creature { private final String name; @@ -40,9 +38,7 @@ public abstract class AbstractCreature implements Creature { private final Color color; private final Mass mass; - /** - * Constructor. - */ + /** Constructor. */ public AbstractCreature(String name, Size size, Movement movement, Color color, Mass mass) { this.name = name; this.size = size; @@ -53,8 +49,8 @@ public AbstractCreature(String name, Size size, Movement movement, Color color, @Override public String toString() { - return String.format("%s [size=%s, movement=%s, color=%s, mass=%s]", - name, size, movement, color, mass); + return String.format( + "%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass); } @Override diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Creature.java b/specification/src/main/java/com/iluwatar/specification/creature/Creature.java index b52d2d7ef704..0e02e8b6d312 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Creature.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Creature.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Creature interface. - */ +/** Creature interface. */ public interface Creature { String getName(); diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java b/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java index 9d85da31b22c..5d05819c0014 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Dragon creature. - */ +/** Dragon creature. */ public class Dragon extends AbstractCreature { public Dragon() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java b/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java index 4177df1edcf9..faff6750a0da 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Goblin creature. - */ +/** Goblin creature. */ public class Goblin extends AbstractCreature { public Goblin() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java b/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java index c27e931197a9..4bfc3eefef8f 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * KillerBee creature. - */ +/** KillerBee creature. */ public class KillerBee extends AbstractCreature { public KillerBee() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java b/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java index 66096d465bc4..5760511cf802 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Octopus creature. - */ +/** Octopus creature. */ public class Octopus extends AbstractCreature { public Octopus() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Shark.java b/specification/src/main/java/com/iluwatar/specification/creature/Shark.java index 2a485092daae..972feae762f5 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Shark.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Shark.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Shark creature. - */ +/** Shark creature. */ public class Shark extends AbstractCreature { public Shark() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Troll.java b/specification/src/main/java/com/iluwatar/specification/creature/Troll.java index de4ab9b75876..0ef9ca2570bb 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Troll.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Troll.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Troll creature. - */ +/** Troll creature. */ public class Troll extends AbstractCreature { public Troll() { diff --git a/specification/src/main/java/com/iluwatar/specification/property/Color.java b/specification/src/main/java/com/iluwatar/specification/property/Color.java index 9b15a82c958b..d52ec3088e83 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Color.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Color.java @@ -24,12 +24,12 @@ */ package com.iluwatar.specification.property; -/** - * Color property. - */ +/** Color property. */ public enum Color { - - DARK("dark"), LIGHT("light"), GREEN("green"), RED("red"); + DARK("dark"), + LIGHT("light"), + GREEN("green"), + RED("red"); private final String title; diff --git a/specification/src/main/java/com/iluwatar/specification/property/Mass.java b/specification/src/main/java/com/iluwatar/specification/property/Mass.java index 384f0dc7b756..68682235148d 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Mass.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Mass.java @@ -26,9 +26,7 @@ import lombok.EqualsAndHashCode; -/** - * Mass property. - */ +/** Mass property. */ @EqualsAndHashCode public class Mass { @@ -60,5 +58,4 @@ public final boolean smallerThanOrEq(Mass other) { public String toString() { return title; } - } diff --git a/specification/src/main/java/com/iluwatar/specification/property/Movement.java b/specification/src/main/java/com/iluwatar/specification/property/Movement.java index d6deb7cac3cb..2bead515390b 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Movement.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Movement.java @@ -24,12 +24,11 @@ */ package com.iluwatar.specification.property; -/** - * Movement property. - */ +/** Movement property. */ public enum Movement { - - WALKING("walking"), SWIMMING("swimming"), FLYING("flying"); + WALKING("walking"), + SWIMMING("swimming"), + FLYING("flying"); private final String title; diff --git a/specification/src/main/java/com/iluwatar/specification/property/Size.java b/specification/src/main/java/com/iluwatar/specification/property/Size.java index 9869c26b7551..66b32dc885bc 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Size.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Size.java @@ -24,12 +24,11 @@ */ package com.iluwatar.specification.property; -/** - * Size property. - */ +/** Size property. */ public enum Size { - - SMALL("small"), NORMAL("normal"), LARGE("large"); + SMALL("small"), + NORMAL("normal"), + LARGE("large"); private final String title; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java index c223c203a9c4..48b04c2bd1bc 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java @@ -26,9 +26,7 @@ import java.util.function.Predicate; -/** - * Base class for selectors. - */ +/** Base class for selectors. */ public abstract class AbstractSelector implements Predicate { public AbstractSelector and(AbstractSelector other) { diff --git a/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java index a14bb6c98319..97d9757e068d 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java @@ -27,9 +27,7 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Color; -/** - * Color selector. - */ +/** Color selector. */ public class ColorSelector extends AbstractSelector { private final Color color; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java index 60d13deb9d58..303fea7ae4a3 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java @@ -26,9 +26,7 @@ import java.util.List; -/** - * A Selector defined as the conjunction (AND) of other (leaf) selectors. - */ +/** A Selector defined as the conjunction (AND) of other (leaf) selectors. */ public class ConjunctionSelector extends AbstractSelector { private final List> leafComponents; @@ -38,9 +36,7 @@ public class ConjunctionSelector extends AbstractSelector { this.leafComponents = List.of(selectors); } - /** - * Tests if *all* selectors pass the test. - */ + /** Tests if *all* selectors pass the test. */ @Override public boolean test(T t) { return leafComponents.stream().allMatch(comp -> (comp.test(t))); diff --git a/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java index 212b6f9df314..0a8003034ea8 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java @@ -26,9 +26,7 @@ import java.util.List; -/** - * A Selector defined as the disjunction (OR) of other (leaf) selectors. - */ +/** A Selector defined as the disjunction (OR) of other (leaf) selectors. */ public class DisjunctionSelector extends AbstractSelector { private final List> leafComponents; @@ -38,9 +36,7 @@ public class DisjunctionSelector extends AbstractSelector { this.leafComponents = List.of(selectors); } - /** - * Tests if *at least one* selector passes the test. - */ + /** Tests if *at least one* selector passes the test. */ @Override public boolean test(T t) { return leafComponents.stream().anyMatch(comp -> comp.test(t)); diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java index 32cbee4452a2..244b49d55666 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java @@ -27,16 +27,12 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -/** - * Mass selector for values exactly equal than the parameter. - */ +/** Mass selector for values exactly equal than the parameter. */ public class MassEqualSelector extends AbstractSelector { private final Mass mass; - /** - * The use of a double as a parameter will spare some typing when instantiating this class. - */ + /** The use of a double as a parameter will spare some typing when instantiating this class. */ public MassEqualSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java index d77b2cbdb590..62a92281ce66 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java @@ -27,16 +27,12 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -/** - * Mass selector for values greater than the parameter. - */ +/** Mass selector for values greater than the parameter. */ public class MassGreaterThanSelector extends AbstractSelector { private final Mass mass; - /** - * The use of a double as a parameter will spare some typing when instantiating this class. - */ + /** The use of a double as a parameter will spare some typing when instantiating this class. */ public MassGreaterThanSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java index 75b6d0bc2591..7abb593a0e87 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java @@ -27,16 +27,12 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -/** - * Mass selector for values smaller or equal to the parameter. - */ +/** Mass selector for values smaller or equal to the parameter. */ public class MassSmallerThanOrEqSelector extends AbstractSelector { private final Mass mass; - /** - * The use of a double as a parameter will spare some typing when instantiating this class. - */ + /** The use of a double as a parameter will spare some typing when instantiating this class. */ public MassSmallerThanOrEqSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java index c4b6a5da2e87..d6c56de8a73f 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java @@ -27,9 +27,7 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Movement; -/** - * Movement selector. - */ +/** Movement selector. */ public class MovementSelector extends AbstractSelector { private final Movement movement; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java index 3239eee01293..2948e26bd309 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java @@ -24,7 +24,6 @@ */ package com.iluwatar.specification.selector; - /** * A Selector defined as the negation (NOT) of a (leaf) selectors. This is of course only useful * when used in combination with other composite selectors. @@ -37,9 +36,7 @@ public class NegationSelector extends AbstractSelector { this.component = selector; } - /** - * Tests if the selector fails the test (yes). - */ + /** Tests if the selector fails the test (yes). */ @Override public boolean test(T t) { return !(component.test(t)); diff --git a/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java index 16e6d1f22b14..c9b7764f9f39 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java @@ -27,9 +27,7 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Size; -/** - * Size selector. - */ +/** Size selector. */ public class SizeSelector extends AbstractSelector { private final Size size; diff --git a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java index 1d1be65a7e35..5b9d76843f0f 100644 --- a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java +++ b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.specification.app; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java index 4db6db95aee5..e400c9835894 100644 --- a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java +++ b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java @@ -36,11 +36,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * Date: 12/29/15 - 7:47 PM - * - * @author Jeroen Meulemeester - */ +/** CreatureTest */ class CreatureTest { /** @@ -48,19 +44,24 @@ class CreatureTest { */ public static Collection dataProvider() { return List.of( - new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED, - new Mass(39300.0)}, - new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, - new Mass(30.0)}, - new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, - new Mass(6.7)}, - new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, - new Mass(12.0)}, - new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, - new Mass(500.0)}, - new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK, - new Mass(4000.0)} - ); + new Object[] { + new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0) + }, + new Object[] { + new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, new Mass(30.0) + }, + new Object[] { + new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, new Mass(6.7) + }, + new Object[] { + new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, new Mass(12.0) + }, + new Object[] { + new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, new Mass(500.0) + }, + new Object[] { + new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK, new Mass(4000.0) + }); } @ParameterizedTest @@ -83,25 +84,27 @@ void testGetMovement(Creature testedCreature, String name, Size size, Movement m @ParameterizedTest @MethodSource("dataProvider") - void testGetColor(Creature testedCreature, String name, Size size, Movement movement, - Color color) { + void testGetColor( + Creature testedCreature, String name, Size size, Movement movement, Color color) { assertEquals(color, testedCreature.getColor()); } @ParameterizedTest @MethodSource("dataProvider") - void testGetMass(Creature testedCreature, String name, Size size, Movement movement, - Color color, Mass mass) { + void testGetMass( + Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) { assertEquals(mass, testedCreature.getMass()); } @ParameterizedTest @MethodSource("dataProvider") - void testToString(Creature testedCreature, String name, Size size, Movement movement, - Color color, Mass mass) { + void testToString( + Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) { final var toString = testedCreature.toString(); assertNotNull(toString); - assertEquals(String - .format("%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass), toString); + assertEquals( + String.format( + "%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass), + toString); } -} \ No newline at end of file +} diff --git a/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java index 1f883d4ad336..d2e6d2624f5d 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java @@ -33,16 +33,10 @@ import com.iluwatar.specification.property.Color; import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 7:35 PM - * - * @author Jeroen Meulemeester - */ +/** ColorSelectorTest */ class ColorSelectorTest { - /** - * Verify if the color selector gives the correct results - */ + /** Verify if the color selector gives the correct results */ @Test void testColor() { final var greenCreature = mock(Creature.class); @@ -54,7 +48,5 @@ void testColor() { final var greenSelector = new ColorSelector(Color.GREEN); assertTrue(greenSelector.test(greenCreature)); assertFalse(greenSelector.test(redCreature)); - } - -} \ No newline at end of file +} diff --git a/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java b/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java index cc314dd6bfaf..d7dc5e12c2a6 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java @@ -36,9 +36,7 @@ class CompositeSelectorsTest { - /** - * Verify if the conjunction selector gives the correct results. - */ + /** Verify if the conjunction selector gives the correct results. */ @Test void testAndComposition() { final var swimmingHeavyCreature = mock(Creature.class); @@ -49,15 +47,13 @@ void testAndComposition() { when(swimmingLightCreature.getMovement()).thenReturn(Movement.SWIMMING); when(swimmingLightCreature.getMass()).thenReturn(new Mass(25.0)); - final var lightAndSwimmingSelector = new MassSmallerThanOrEqSelector(50.0) - .and(new MovementSelector(Movement.SWIMMING)); + final var lightAndSwimmingSelector = + new MassSmallerThanOrEqSelector(50.0).and(new MovementSelector(Movement.SWIMMING)); assertFalse(lightAndSwimmingSelector.test(swimmingHeavyCreature)); assertTrue(lightAndSwimmingSelector.test(swimmingLightCreature)); } - /** - * Verify if the disjunction selector gives the correct results. - */ + /** Verify if the disjunction selector gives the correct results. */ @Test void testOrComposition() { final var swimmingHeavyCreature = mock(Creature.class); @@ -68,15 +64,13 @@ void testOrComposition() { when(swimmingLightCreature.getMovement()).thenReturn(Movement.SWIMMING); when(swimmingLightCreature.getMass()).thenReturn(new Mass(25.0)); - final var lightOrSwimmingSelector = new MassSmallerThanOrEqSelector(50.0) - .or(new MovementSelector(Movement.SWIMMING)); + final var lightOrSwimmingSelector = + new MassSmallerThanOrEqSelector(50.0).or(new MovementSelector(Movement.SWIMMING)); assertTrue(lightOrSwimmingSelector.test(swimmingHeavyCreature)); assertTrue(lightOrSwimmingSelector.test(swimmingLightCreature)); } - /** - * Verify if the negation selector gives the correct results. - */ + /** Verify if the negation selector gives the correct results. */ @Test void testNotComposition() { final var swimmingHeavyCreature = mock(Creature.class); diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java index b4697b60fe96..e55ddbde1346 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java @@ -35,9 +35,7 @@ class MassSelectorTest { - /** - * Verify if the mass selector gives the correct results. - */ + /** Verify if the mass selector gives the correct results. */ @Test void testMass() { final var lightCreature = mock(Creature.class); diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java index e35f09da41dc..e0fb51569021 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java @@ -33,16 +33,10 @@ import com.iluwatar.specification.property.Movement; import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 7:37 PM - * - * @author Jeroen Meulemeester - */ +/** MovementSelectorTest */ class MovementSelectorTest { - /** - * Verify if the movement selector gives the correct results. - */ + /** Verify if the movement selector gives the correct results. */ @Test void testMovement() { final var swimmingCreature = mock(Creature.class); @@ -54,7 +48,5 @@ void testMovement() { final var swimmingSelector = new MovementSelector(Movement.SWIMMING); assertTrue(swimmingSelector.test(swimmingCreature)); assertFalse(swimmingSelector.test(flyingCreature)); - } - -} \ No newline at end of file +} diff --git a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java index 7584743fd6b0..6aa65226ac17 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java @@ -33,16 +33,10 @@ import com.iluwatar.specification.property.Size; import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 7:43 PM - * - * @author Jeroen Meulemeester - */ +/** SizeSelectorTest */ class SizeSelectorTest { - /** - * Verify if the size selector gives the correct results - */ + /** Verify if the size selector gives the correct results */ @Test void testMovement() { final var normalCreature = mock(Creature.class); @@ -55,5 +49,4 @@ void testMovement() { assertTrue(normalSelector.test(normalCreature)); assertFalse(normalSelector.test(smallCreature)); } - } diff --git a/state/README.md b/state/README.md index 84adc148847a..7fdc242aefef 100644 --- a/state/README.md +++ b/state/README.md @@ -1,27 +1,34 @@ --- -title: State +title: "State Pattern in Java: Enhancing Behavior Dynamics with State Encapsulation" +shortTitle: State +description: "Explore the State Pattern, a core component of Java design patterns that enables dynamic behavior change in objects with internal state shifts. Includes real-world examples, applicability, benefits, and detailed code snippets." category: Behavioral language: en tag: - - Gang of Four + - Decoupling + - Gang of Four + - State tracking --- ## Also known as -Objects for States +* Objects for States -## Intent +## Intent of State Design Pattern -Allow an object to alter its behavior when its internal state changes. The object will appear to -change its class. +Enable an object to alter its behavior dynamically as its internal state changes, optimizing Java application responsiveness. -## Explanation +## Detailed Explanation of State Pattern with Real-World Examples Real-world example -> When observing a mammoth in its natural habitat it seems to change its behavior based on the -> situation. It may first appear calm, but over time when it detects a threat, it gets angry and -> dangerous to its surroundings. +> Imagine a traffic light system at an intersection. The traffic light can be in one of three states: Green, Yellow, or Red. Depending on the current state, the traffic light's behavior changes: +> +> 1. **Green State**: Cars are allowed to pass through the intersection. +> 2. **Yellow State**: Cars are warned that the light will soon turn red, so they should prepare to stop. +> 3. **Red State**: Cars must stop and wait for the light to turn green. +> +> In this scenario, the traffic light uses the State design pattern. Each state (Green, Yellow, Red) is represented by a different object that defines what happens in that particular state. The traffic light (context) delegates the behavior to the current state object. When the state changes (e.g., from Green to Yellow), the traffic light updates its state object and changes its behavior accordingly. In plain words @@ -29,14 +36,13 @@ In plain words Wikipedia says -> The state pattern is a behavioral software design pattern that allows an object to alter its -> behavior when its internal state changes. This pattern is close to the concept of finite-state -> machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a -> strategy through invocations of methods defined in the pattern's interface. +> The state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. This pattern is close to the concept of finite-state machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern's interface. -**Programmatic Example** +## Programmatic Example of State Pattern in Java -Here is the state interface and its concrete implementations. +In our programmatic example there is a mammoth with alternating moods. + +First, here is the state interface and its concrete implementations. ```java public interface State { @@ -45,7 +51,9 @@ public interface State { void observe(); } +``` +```java @Slf4j public class PeacefulState implements State { @@ -65,7 +73,9 @@ public class PeacefulState implements State { LOGGER.info("{} calms down.", mammoth); } } +``` +```java @Slf4j public class AngryState implements State { @@ -87,7 +97,7 @@ public class AngryState implements State { } ``` -And here is the mammoth containing the state. +And here is the mammoth containing the state. The state changes via calls to `timePasses` method. ```java public class Mammoth { @@ -125,12 +135,15 @@ public class Mammoth { Here is the full example of how the mammoth behaves over time. ```java +public static void main(String[] args) { + var mammoth = new Mammoth(); mammoth.observe(); mammoth.timePasses(); mammoth.observe(); mammoth.timePasses(); mammoth.observe(); +} ``` Program output: @@ -142,24 +155,37 @@ Program output: The mammoth is calm and peaceful. ``` -## Class diagram +## When to Use the State Pattern in Java + +* An object's behavior depends on its state, and it must change its behavior at runtime depending on that state. +* Operations have large, multipart conditional statements that depend on the object's state. + +## Real-World Applications of State Pattern in Java + +* `java.util.Iterator` in Java's Collections Framework uses different states for iteration. +* TCP connection classes in network programming often implement states like `Established`, `Listen`, and `Closed`. + +## Benefits and Trade-offs of State Pattern -![alt text](./etc/state_urm.png "State") +Benefits: -## Applicability +* Localizes state-specific behavior and partitions behavior for different states. +* Makes state transitions explicit. +* Reusable State objects can be efficiently shared among various contexts in Java, enhancing memory management and performance. -Use the State pattern in either of the following cases +Trade-offs: -* An object's behavior depends on its state, and it must change its behavior at run-time depending on that state -* Operations have large, multipart conditional statements that depend on the object's state. This state is usually represented by one or more enumerated constants. Often, several operations will contain this same conditional structure. The State pattern puts each branch of the conditional in a separate class. This lets you treat the object's state as an object in its own right that can vary independently from other objects. +* Can result in a large number of classes for states. +* Context class can become complicated with the state transition logic. -## Known uses +## Related Java Design Patterns -* [javax.faces.lifecycle.Lifecycle#execute()](http://docs.oracle.com/javaee/7/api/javax/faces/lifecycle/Lifecycle.html#execute-javax.faces.context.FacesContext-) controlled by [FacesServlet](http://docs.oracle.com/javaee/7/api/javax/faces/webapp/FacesServlet.html), the behavior is dependent on current phase of lifecycle. -* [JDiameter - Diameter State Machine](https://github.com/npathai/jdiameter/blob/master/core/jdiameter/api/src/main/java/org/jdiameter/api/app/State.java) +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): State objects may be shared between different contexts. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): State objects are often singletons. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Both patterns have similar structures, but the State pattern's implementations depend on the context’s state. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/state/pom.xml b/state/pom.xml index c4ce3b6f382f..9bd6df825b64 100644 --- a/state/pom.xml +++ b/state/pom.xml @@ -34,6 +34,14 @@ state + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/state/src/main/java/com/iluwatar/state/AngryState.java b/state/src/main/java/com/iluwatar/state/AngryState.java index c20085c44838..f26126ee0696 100644 --- a/state/src/main/java/com/iluwatar/state/AngryState.java +++ b/state/src/main/java/com/iluwatar/state/AngryState.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Angry state. - */ +/** Angry state. */ @Slf4j public class AngryState implements State { @@ -47,5 +45,4 @@ public void observe() { public void onEnterState() { LOGGER.info("{} gets angry!", mammoth); } - } diff --git a/state/src/main/java/com/iluwatar/state/App.java b/state/src/main/java/com/iluwatar/state/App.java index 9a973d220a82..37d8ae37b09d 100644 --- a/state/src/main/java/com/iluwatar/state/App.java +++ b/state/src/main/java/com/iluwatar/state/App.java @@ -28,16 +28,14 @@ * In the State pattern, the container object has an internal state object that defines the current * behavior. The state object can be changed to alter the behavior. * - *

    This can be a cleaner way for an object to change its behavior at runtime without resorting - * to large monolithic conditional statements and thus improves maintainability. + *

    This can be a cleaner way for an object to change its behavior at runtime without resorting to + * large monolithic conditional statements and thus improves maintainability. * *

    In this example the {@link Mammoth} changes its behavior as time passes by. */ public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var mammoth = new Mammoth(); @@ -46,6 +44,5 @@ public static void main(String[] args) { mammoth.observe(); mammoth.timePasses(); mammoth.observe(); - } } diff --git a/state/src/main/java/com/iluwatar/state/Mammoth.java b/state/src/main/java/com/iluwatar/state/Mammoth.java index 4815e1259473..f9b3006eca6e 100644 --- a/state/src/main/java/com/iluwatar/state/Mammoth.java +++ b/state/src/main/java/com/iluwatar/state/Mammoth.java @@ -1,62 +1,58 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.state; - -/** - * Mammoth has internal state that defines its behavior. - */ -public class Mammoth { - - private State state; - - public Mammoth() { - state = new PeacefulState(this); - } - - /** - * Makes time pass for the mammoth. - */ - public void timePasses() { - if (state.getClass().equals(PeacefulState.class)) { - changeStateTo(new AngryState(this)); - } else { - changeStateTo(new PeacefulState(this)); - } - } - - private void changeStateTo(State newState) { - this.state = newState; - this.state.onEnterState(); - } - - @Override - public String toString() { - return "The mammoth"; - } - - public void observe() { - this.state.observe(); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.state; + +/** Mammoth has internal state that defines its behavior. */ +public class Mammoth { + + private State state; + + public Mammoth() { + state = new PeacefulState(this); + } + + /** Makes time pass for the mammoth. */ + public void timePasses() { + if (state.getClass().equals(PeacefulState.class)) { + changeStateTo(new AngryState(this)); + } else { + changeStateTo(new PeacefulState(this)); + } + } + + private void changeStateTo(State newState) { + this.state = newState; + this.state.onEnterState(); + } + + @Override + public String toString() { + return "The mammoth"; + } + + public void observe() { + this.state.observe(); + } +} diff --git a/state/src/main/java/com/iluwatar/state/PeacefulState.java b/state/src/main/java/com/iluwatar/state/PeacefulState.java index 4f558bb7d1a7..3511ba4cb7c0 100644 --- a/state/src/main/java/com/iluwatar/state/PeacefulState.java +++ b/state/src/main/java/com/iluwatar/state/PeacefulState.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Peaceful state. - */ +/** Peaceful state. */ @Slf4j public class PeacefulState implements State { @@ -47,5 +45,4 @@ public void observe() { public void onEnterState() { LOGGER.info("{} calms down.", mammoth); } - } diff --git a/state/src/main/java/com/iluwatar/state/State.java b/state/src/main/java/com/iluwatar/state/State.java index ee39a8aa69b5..f305dff21ebf 100644 --- a/state/src/main/java/com/iluwatar/state/State.java +++ b/state/src/main/java/com/iluwatar/state/State.java @@ -1,35 +1,33 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.state; - -/** - * State interface. - */ -public interface State { - - void onEnterState(); - - void observe(); -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.state; + +/** State interface. */ +public interface State { + + void onEnterState(); + + void observe(); +} diff --git a/state/src/test/java/com/iluwatar/state/AppTest.java b/state/src/test/java/com/iluwatar/state/AppTest.java index e6623790ddec..dc80141b96b7 100644 --- a/state/src/test/java/com/iluwatar/state/AppTest.java +++ b/state/src/test/java/com/iluwatar/state/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.state; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/state/src/test/java/com/iluwatar/state/MammothTest.java b/state/src/test/java/com/iluwatar/state/MammothTest.java index 8479c4738d93..8fa8be1ea0e2 100644 --- a/state/src/test/java/com/iluwatar/state/MammothTest.java +++ b/state/src/test/java/com/iluwatar/state/MammothTest.java @@ -37,11 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/29/15 - 8:27 PM - * - * @author Jeroen Meulemeester - */ +/** MammothTest */ class MammothTest { private InMemoryAppender appender; @@ -83,12 +79,9 @@ void testTimePasses() { mammoth.observe(); assertEquals("The mammoth is calm and peaceful.", appender.getLastMessage()); assertEquals(5, appender.getLogSize()); - } - /** - * Verify if {@link Mammoth#toString()} gives the expected value - */ + /** Verify if {@link Mammoth#toString()} gives the expected value */ @Test void testToString() { final var toString = new Mammoth().toString(); @@ -96,7 +89,7 @@ void testToString() { assertEquals("The mammoth", toString); } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender() { @@ -117,5 +110,4 @@ public String getLastMessage() { return log.get(log.size() - 1).getFormattedMessage(); } } - } diff --git a/step-builder/README.md b/step-builder/README.md index a1e98b237611..a651cff45642 100644 --- a/step-builder/README.md +++ b/step-builder/README.md @@ -1,105 +1,210 @@ --- -title: Step Builder +title: "Step Builder Pattern in Java: Crafting Fluent Interfaces for Complex Object Construction" +shortTitle: Step Builder +description: "Master the Step Builder pattern in Java to streamline complex object creation. Learn through detailed explanations and examples. Perfect for software developers aiming to enhance code readability and maintainability." category: Creational language: en tag: - - Instantiation + - Code simplification + - Domain + - Encapsulation + - Extensibility + - Instantiation + - Interface --- -# Step Builder Pattern +## Also known as -## Explanation +* Fluent Builder -The Step Builder pattern is a creational design pattern used to construct a complex object step by step. It provides a fluent interface to create an object with a large number of possible configurations, making the code more readable and reducing the need for multiple constructors or setter methods. +## Intent of Step Builder Design Pattern -## Intent -An extension of the Builder pattern that fully guides the user through the creation of the object with no chances of confusion. -The user experience will be much more improved by the fact that he will only see the next step methods available, NO build method until is the right time to build the object. +The Step Builder pattern in Java is an advanced technique to create complex objects with clarity and flexibility. It is perfect for scenarios requiring meticulous step-by-step object construction. -## Real World Example +## Detailed Explanation of Step Builder Pattern with Real-World Examples -Imagine you are building a configuration object for a database connection. The connection has various optional parameters such as host, port, username, password, and others. Using the Step Builder pattern, you can set these parameters in a clean and readable way: +Real-world example + +> Imagine a scenario where you are assembling a custom computer. The process involves several steps: selecting the CPU, choosing the motherboard, adding memory, picking a graphics card, and installing storage. Each step builds on the previous one, gradually creating a fully functional computer. This step-by-step assembly process mirrors the Step Builder design pattern, ensuring that each component is correctly chosen and installed in a structured manner, ultimately resulting in a custom-built computer that meets specific requirements and preferences. + +In plain words + +> The Step Builder pattern constructs complex objects incrementally through a series of defined steps, ensuring clarity and flexibility in the creation process. + +Wikipedia says + +> The Step Builder pattern is a variation of the Builder design pattern, designed to provide a flexible solution for constructing complex objects step-by-step. This pattern is particularly useful when an object requires multiple initialization steps, which can be done incrementally to ensure clarity and flexibility in the creation process. + +## Programmatic Example of Step Builder Pattern in Java + +The Step Builder pattern in Java is an extension of the Builder pattern that guides the user through the creation of an object in a step-by-step manner. This pattern improves the user experience by only showing the next step methods available, and not showing the build method until it's the right time to build the object. + +Let's consider a `Character` class that has many attributes such as `name`, `fighterClass`, `wizardClass`, `weapon`, `spell`, and `abilities`. + +```java +@Getter +@Setter +public class Character { + + private String name; + private String fighterClass; + private String wizardClass; + private String weapon; + private String spell; + private List abilities; + + public Character(String name) { + this.name = name; + } + + // toString method omitted +} +``` + +Creating an instance of this class can be complex due to the number of attributes. This is where the Step Builder pattern comes in handy. + +We create a `CharacterStepBuilder` class that guides the user through the creation of a `Character` object. + +```java +public class CharacterStepBuilder { + + // Builder steps and methods... + + public static NameStep newBuilder() { + return new Steps(); + } + + // Steps implementation... +} +``` + +The `CharacterStepBuilder` class defines a series of nested interfaces, each representing a step in the construction process. Each interface declares a method for the next step, guiding the user through the construction of a `Character` object. + +```java +public interface NameStep { + ClassStep name(String name); +} +``` ```java -DatabaseConnection connection = new DatabaseConnection.Builder() - .setHost("localhost") - .setPort(3306) - .setUsername("user") - .setPassword("password") - .setSSL(true) - .build(); +public interface ClassStep { + WeaponStep fighterClass(String fighterClass); + SpellStep wizardClass(String wizardClass); +} + +// More steps... ``` -## In Plain Words +The `Steps` class implements all these interfaces and finally builds the `Character` object. -The Step Builder pattern allows you to construct complex objects by breaking down the construction process into a series of steps. Each step corresponds to setting a particular attribute or configuration option of the object. This results in more readable and maintainable code, especially when dealing with objects that have numerous configuration options. +```java +private static class Steps implements NameStep, ClassStep, WeaponStep, SpellStep, BuildStep { -## Wikipedia Says + private String name; + private String fighterClass; + private String wizardClass; + private String weapon; + private String spell; + private List abilities; -According to Wikipedia, the Step Builder pattern is a creational design pattern in which an object is constructed step by step. It involves a dedicated 'director' class, which orchestrates the construction process through a series of 'builder' classes, each responsible for a specific aspect of the object's configuration. This pattern is particularly useful when dealing with objects that have a large number of optional parameters. + // Implement the methods for each step... -## Programmatic Example + @Override + public Character build() { + return new Character(name, fighterClass, wizardClass, weapon, spell, abilities); + } +} +``` -Assuming you have a class `Product` with several configurable attributes, a Step Builder for it might look like this: +Now, creating a `Character` object becomes a guided process: ```java -public class Product { - private String name; - private double price; - private int quantity; - - // private constructor to force the use of the builder - private Product(String name, double price, int quantity) { - - this.name = name; - this.price = price; - this.quantity = quantity; - - } - - public static class Builder { - private String name; - private double price; - private int quantity; - - public Builder setName(String name) { - this.name = name; - return this; - } - - public Builder setPrice(double price) { - this.price = price; - return this; - } - - public Builder setQuantity(int quantity) { - this.quantity = quantity; - return this; - } - - public Product build() { - return new Product(name, price, quantity); - } - } +public static void main(String[] args) { + + var warrior = CharacterStepBuilder + .newBuilder() + .name("Amberjill") + .fighterClass("Paladin") + .withWeapon("Sword") + .noAbilities() + .build(); + + LOGGER.info(warrior.toString()); + + var mage = CharacterStepBuilder + .newBuilder() + .name("Riobard") + .wizardClass("Sorcerer") + .withSpell("Fireball") + .withAbility("Fire Aura") + .withAbility("Teleport") + .noMoreAbilities() + .build(); + + LOGGER.info(mage.toString()); + + var thief = CharacterStepBuilder + .newBuilder() + .name("Desmond") + .fighterClass("Rogue") + .noWeapon() + .build(); + + LOGGER.info(thief.toString()); } +``` -// Usage -Product product = new Product.Builder() - .setName("Example Product") - .setPrice(29.99) - .setQuantity(100) - .build(); +Console output: + +``` +12:58:13.887 [main] INFO com.iluwatar.stepbuilder.App -- This is a Paladin named Amberjill armed with a Sword. +12:58:13.889 [main] INFO com.iluwatar.stepbuilder.App -- This is a Sorcerer named Riobard armed with a Fireball and wielding [Fire Aura, Teleport] abilities. +12:58:13.889 [main] INFO com.iluwatar.stepbuilder.App -- This is a Rogue named Desmond armed with a with nothing. ``` -This example demonstrates how you can use the Step Builder pattern to create a `Product` object with a customizable set of attributes. Each method in the builder corresponds to a step in the construction process. +## Detailed Explanation of Step Builder Pattern with Real-World Examples + +![Step Builder](./etc/step-builder.png "Step Builder") + +## When to Use the Step Builder Pattern in Java + +The Step Builder pattern in Java is used + +* When constructing an object that requires multiple initialization steps. +* When object construction is complex and involves many parameters. +* When you want to provide a clear, readable, and maintainable way to construct an object in a step-by-step manner. + +## Step Builder Pattern Java Tutorials + +* [Step Builder (Marco Castigliego)](http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html) + +## Real-World Applications of Step Builder Pattern in Java + +* Complex configuration settings in Java applications. +* Constructing objects for database records with multiple fields. +* Building UI elements where each step configures a different part of the interface. + +## Benefits and Trade-offs of Step Builder Pattern + +Benefits: + +* Improves code readability and maintainability by providing a clear and concise way to construct objects. +* Enhances flexibility in creating objects as it allows variations in the construction process. +* Encourages immutability by separating the construction process from the object's representation. + +Trade-offs: -## Applicability -Use the Step Builder pattern when the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled the construction process must allow different representations for the object that's constructed when in the process of constructing the order is important. +* May result in more complex code due to the additional classes and interfaces required. +* Can lead to verbose code when many steps are involved. -## Another example with class diagram -![alt text](./etc/step-builder.png "Step Builder") +## Related Java Design Patterns +* [Builder](https://java-design-patterns.com/patterns/builder/): Both patterns help in constructing complex objects. Step Builder is a variation that emphasizes incremental step-by-step construction. +* [Fluent Interface](https://java-design-patterns.com/patterns/fluentinterface/): Often used in conjunction with the Step Builder pattern to provide a fluent API for constructing objects. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Sometimes used with the Step Builder pattern to encapsulate the creation logic of the builder itself. -## Credits +## References and Credits -* [Marco Castigliego - Step Builder](http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/3wRnjp5) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) diff --git a/step-builder/pom.xml b/step-builder/pom.xml index 2c6979d489d8..79ebbec525ed 100644 --- a/step-builder/pom.xml +++ b/step-builder/pom.xml @@ -34,6 +34,14 @@ step-builder + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java index a5ffb6060c15..dfa73e573282 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java @@ -31,30 +31,28 @@ * *

    Intent
    * An extension of the Builder pattern that fully guides the user through the creation of the object - * with no chances of confusion.
    The user experience will be much more improved by the fact - * that he will only see the next step methods available, NO build method until is the right time to - * build the object. + * with no chances of confusion.
    + * The user experience will be much more improved by the fact that he will only see the next step + * methods available, NO build method until is the right time to build the object. * *

    Implementation
    * The concept is simple: - *

      - * - *
    • Write creational steps inner classes or interfaces where each method knows what can be - * displayed next.
    • * - *
    • Implement all your steps interfaces in an inner static class.
    • - * - *
    • Last step is the BuildStep, in charge of creating the object you need to build.
    • + *
        + *
      • Write creational steps inner classes or interfaces where each method knows what can be + * displayed next. + *
      • Implement all your steps interfaces in an inner static class. + *
      • Last step is the BuildStep, in charge of creating the object you need to build. *
      * *

      Applicability
      * Use the Step Builder pattern when the algorithm for creating a complex object should be * independent of the parts that make up the object and how they're assembled the construction * process must allow different representations for the object that's constructed when in the - * process of constructing the order is important. - *
      + * process of constructing the order is important.
      * - * @see http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html + * @see http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html */ @Slf4j public class App { @@ -66,34 +64,30 @@ public class App { */ public static void main(String[] args) { - var warrior = CharacterStepBuilder - .newBuilder() - .name("Amberjill") - .fighterClass("Paladin") - .withWeapon("Sword") - .noAbilities() - .build(); + var warrior = + CharacterStepBuilder.newBuilder() + .name("Amberjill") + .fighterClass("Paladin") + .withWeapon("Sword") + .noAbilities() + .build(); LOGGER.info(warrior.toString()); - var mage = CharacterStepBuilder - .newBuilder() - .name("Riobard") - .wizardClass("Sorcerer") - .withSpell("Fireball") - .withAbility("Fire Aura") - .withAbility("Teleport") - .noMoreAbilities() - .build(); + var mage = + CharacterStepBuilder.newBuilder() + .name("Riobard") + .wizardClass("Sorcerer") + .withSpell("Fireball") + .withAbility("Fire Aura") + .withAbility("Teleport") + .noMoreAbilities() + .build(); LOGGER.info(mage.toString()); - var thief = CharacterStepBuilder - .newBuilder() - .name("Desmond") - .fighterClass("Rogue") - .noWeapon() - .build(); + var thief = + CharacterStepBuilder.newBuilder().name("Desmond").fighterClass("Rogue").noWeapon().build(); LOGGER.info(thief.toString()); } diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java index 3ecd70de42fa..dedcd33efd33 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java @@ -28,11 +28,7 @@ import lombok.Getter; import lombok.Setter; - - -/** - * The class with many parameters. - */ +/** The class with many parameters. */ @Getter @Setter public class Character { @@ -48,7 +44,6 @@ public Character(String name) { this.name = name; } - @Override public String toString() { return new StringBuilder() diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java index 01c23130fe70..ff5b136312fd 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java @@ -27,21 +27,16 @@ import java.util.ArrayList; import java.util.List; -/** - * The Step Builder class. - */ +/** The Step Builder class. */ public final class CharacterStepBuilder { - private CharacterStepBuilder() { - } + private CharacterStepBuilder() {} public static NameStep newBuilder() { return new CharacterSteps(); } - /** - * First Builder Step in charge of the Character name. Next Step available : ClassStep - */ + /** First Builder Step in charge of the Character name. Next Step available : ClassStep */ public interface NameStep { ClassStep name(String name); } @@ -76,9 +71,7 @@ public interface SpellStep { BuildStep noSpell(); } - /** - * This step is in charge of abilities. Next Step available : BuildStep - */ + /** This step is in charge of abilities. Next Step available : BuildStep */ public interface AbilityStep { AbilityStep withAbility(String ability); @@ -94,12 +87,9 @@ public interface BuildStep { Character build(); } - - /** - * Step Builder implementation. - */ - private static class CharacterSteps implements NameStep, ClassStep, WeaponStep, SpellStep, - AbilityStep, BuildStep { + /** Step Builder implementation. */ + private static class CharacterSteps + implements NameStep, ClassStep, WeaponStep, SpellStep, AbilityStep, BuildStep { private String name; private String fighterClass; diff --git a/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java b/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java index b147f6da847c..1fcf9ae93cd4 100644 --- a/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java +++ b/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.stepbuilder; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java index 4df8e788d3bc..e6c1ba59d0c4 100644 --- a/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java +++ b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java @@ -31,26 +31,21 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 9:21 PM - * - * @author Jeroen Meulemeester - */ +/** CharacterStepBuilderTest */ class CharacterStepBuilderTest { - /** - * Build a new wizard {@link Character} and verify if it has the expected attributes - */ + /** Build a new wizard {@link Character} and verify if it has the expected attributes */ @Test void testBuildWizard() { - final var character = CharacterStepBuilder.newBuilder() - .name("Merlin") - .wizardClass("alchemist") - .withSpell("poison") - .withAbility("invisibility") - .withAbility("wisdom") - .noMoreAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .withAbility("invisibility") + .withAbility("wisdom") + .noMoreAbilities() + .build(); assertEquals("Merlin", character.getName()); assertEquals("alchemist", character.getWizardClass()); @@ -62,7 +57,6 @@ void testBuildWizard() { assertEquals(2, abilities.size()); assertTrue(abilities.contains("invisibility")); assertTrue(abilities.contains("wisdom")); - } /** @@ -71,53 +65,46 @@ void testBuildWizard() { */ @Test void testBuildPoorWizard() { - final var character = CharacterStepBuilder.newBuilder() - .name("Merlin") - .wizardClass("alchemist") - .noSpell() - .build(); + final var character = + CharacterStepBuilder.newBuilder().name("Merlin").wizardClass("alchemist").noSpell().build(); assertEquals("Merlin", character.getName()); assertEquals("alchemist", character.getWizardClass()); assertNull(character.getSpell()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } - /** - * Build a new wizard {@link Character} and verify if it has the expected attributes - */ + /** Build a new wizard {@link Character} and verify if it has the expected attributes */ @Test void testBuildWeakWizard() { - final var character = CharacterStepBuilder.newBuilder() - .name("Merlin") - .wizardClass("alchemist") - .withSpell("poison") - .noAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .noAbilities() + .build(); assertEquals("Merlin", character.getName()); assertEquals("alchemist", character.getWizardClass()); assertEquals("poison", character.getSpell()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } - /** - * Build a new warrior {@link Character} and verify if it has the expected attributes - */ + /** Build a new warrior {@link Character} and verify if it has the expected attributes */ @Test void testBuildWarrior() { - final var character = CharacterStepBuilder.newBuilder() - .name("Cuauhtemoc") - .fighterClass("aztec") - .withWeapon("spear") - .withAbility("speed") - .withAbility("strength") - .noMoreAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Cuauhtemoc") + .fighterClass("aztec") + .withWeapon("spear") + .withAbility("speed") + .withAbility("strength") + .noMoreAbilities() + .build(); assertEquals("Cuauhtemoc", character.getName()); assertEquals("aztec", character.getFighterClass()); @@ -129,7 +116,6 @@ void testBuildWarrior() { assertEquals(2, abilities.size()); assertTrue(abilities.contains("speed")); assertTrue(abilities.contains("strength")); - } /** @@ -138,18 +124,18 @@ void testBuildWarrior() { */ @Test void testBuildPoorWarrior() { - final var character = CharacterStepBuilder.newBuilder() - .name("Poor warrior") - .fighterClass("none") - .noWeapon() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Poor warrior") + .fighterClass("none") + .noWeapon() + .build(); assertEquals("Poor warrior", character.getName()); assertEquals("none", character.getFighterClass()); assertNull(character.getWeapon()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } /** @@ -158,19 +144,18 @@ void testBuildPoorWarrior() { */ @Test void testBuildWeakWarrior() { - final var character = CharacterStepBuilder.newBuilder() - .name("Weak warrior") - .fighterClass("none") - .withWeapon("Slingshot") - .noAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Weak warrior") + .fighterClass("none") + .withWeapon("Slingshot") + .noAbilities() + .build(); assertEquals("Weak warrior", character.getName()); assertEquals("none", character.getFighterClass()); assertEquals("Slingshot", character.getWeapon()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } - -} \ No newline at end of file +} diff --git a/strangler/README.md b/strangler/README.md index b7e3e611fb6a..569a2151f38d 100644 --- a/strangler/README.md +++ b/strangler/README.md @@ -1,28 +1,205 @@ --- -title: Strangler +title: "Strangler Pattern in Java: Modernizing Legacy Systems with Incremental Updates" +shortTitle: Strangler +description: "Explore the Strangler design pattern for Java, a strategic approach to incrementally modernize legacy systems without disruption. Learn how it facilitates smooth transitions to new architectures, with real-world applications and code examples." category: Structural language: en tag: - - Extensibility - - Cloud distributed + - Migration + - Modernization + - Refactoring --- -## Intent -Incrementally migrate a legacy system by gradually replacing specific pieces of functionality -with new applications and services. As features from the legacy system are replaced, the new -system eventually covers all the old system's features and may has its own new features, then -strangling the old system and allowing you to decommission it. +## Also known as -## Class diagram -![alt text](./etc/strangler.png "Strangler") +* Strangler Fig -## Applicability -This strangler pattern is a safe way to phase one thing out for something better, cheaper, or -more expandable. Especially when you want to update legacy system with new techniques and need -continuously develop new features at the same time. Note that this pattern indeed need extra effort, -so usually use it when the system is not so simple. +## Intent of Strangler Design Pattern -## Credits +The Strangler Pattern incrementally replaces the legacy system by building a new system alongside the old one, eventually strangling the old system. Using the pattern offer a seamless transition from old to new systems. -* [Strangler pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler) -* [Legacy Application Strangulation : Case Studies](https://paulhammant.com/2013/07/14/legacy-application-strangulation-case-studies/) +## Detailed Explanation of Strangler Pattern with Real-World Examples + +Real-world example + +> Imagine a city planning department that decides to modernize an old bridge that's crucial for daily commutes. Instead of demolishing the old bridge and causing major disruptions, they build a new, modern bridge next to it. As sections of the new bridge are completed, traffic is gradually diverted from the old bridge to the new one. Eventually, the entire flow of traffic moves to the new bridge, and the old bridge is either decommissioned or demolished. This way, the transition is smooth, and the city's daily activities are minimally affected. This approach mirrors the Strangler Design Pattern, where a legacy system is incrementally replaced by a new system, ensuring continuous operation during the transition. + +In plain words + +> The Strangler Design Pattern incrementally replaces a legacy system by developing a new system alongside it and gradually migrating functionality until the old system is entirely replaced. + +Wikipedia says + +> The Strangler Design Pattern involves incrementally migrating a legacy system by gradually replacing it with a new system. It wraps old code with new code, redirecting or logging uses of the old code to ensure a seamless transition. This pattern is named after the strangler fig plant, which grows around a host tree and eventually replaces it entirely. It's particularly useful for modernizing monolithic applications and transitioning them to microservices architecture with minimal risk and disruption. + +## Programmatic Example of Strangler Pattern in Java + +The Strangler design pattern in Java is a software design pattern that incrementally migrates a legacy system by gradually replacing specific pieces of functionality with new applications and services. As features from the legacy system are replaced, the new system eventually replaces all the old system's features, strangling the old system and allowing you to decommission it. + +In the provided code, we have an example of the Strangler pattern in action. The `OldArithmetic` class represents the legacy system, while the `HalfArithmetic` and `NewArithmetic` classes represent the new system at different stages of development. + +Let's break down the code to understand how the Strangler pattern is implemented. + +```java +public class OldArithmetic { + private final OldSource source; + + public OldArithmetic(OldSource source) { + this.source = source; + } + + // The sum and mul methods represent the functionality of the legacy system. + public int sum(int... nums) { + return source.accumulateSum(nums); + } + + public int mul(int... nums) { + return source.accumulateMul(nums); + } +} +``` + +The `OldArithmetic` class represents the legacy system. It has two methods, `sum` and `mul`, which depend on the `OldSource` class. + +```java +public class HalfArithmetic { + private final HalfSource newSource; + private final OldSource oldSource; + + public HalfArithmetic(HalfSource newSource, OldSource oldSource) { + this.newSource = newSource; + this.oldSource = oldSource; + } + + // The sum method has been migrated to use the new source. + public int sum(int... nums) { + return newSource.accumulateSum(nums); + } + + // The mul method still uses the old source. + public int mul(int... nums) { + return oldSource.accumulateMul(nums); + } + + // The ifHasZero method is a new feature added in the new system. + public boolean ifHasZero(int... nums) { + return !newSource.ifNonZero(nums); + } +} +``` + +The `HalfArithmetic` class represents the system during the migration process. It depends on both the `OldSource` and `HalfSource` classes. The `sum` method has been migrated to use the new source, while the `mul` method still uses the old source. The `ifHasZero` method is a new feature added in the new system. + +```java +public class NewArithmetic { + private final NewSource source; + + public NewArithmetic(NewSource source) { + this.source = source; + } + + // All methods now use the new source. + public int sum(int... nums) { + return source.accumulateSum(nums); + } + + public int mul(int... nums) { + return source.accumulateMul(nums); + } + + public boolean ifHasZero(int... nums) { + return !source.ifNonZero(nums); + } +} +``` + +The `NewArithmetic` class represents the system after the migration process. It only depends on the `NewSource` class. All methods now use the new source. + +Here is the `main` method executing our example. + +```java +public static void main(final String[] args) { + final var nums = new int[]{1, 2, 3, 4, 5}; + //Before migration + final var oldSystem = new OldArithmetic(new OldSource()); + oldSystem.sum(nums); + oldSystem.mul(nums); + //In process of migration + final var halfSystem = new HalfArithmetic(new HalfSource(), new OldSource()); + halfSystem.sum(nums); + halfSystem.mul(nums); + halfSystem.ifHasZero(nums); + //After migration + final var newSystem = new NewArithmetic(new NewSource()); + newSystem.sum(nums); + newSystem.mul(nums); + newSystem.ifHasZero(nums); + } +``` + +Console output: + +``` +13:02:25.030 [main] INFO com.iluwatar.strangler.OldArithmetic -- Arithmetic sum 1.0 +13:02:25.032 [main] INFO com.iluwatar.strangler.OldSource -- Source module 1.0 +13:02:25.032 [main] INFO com.iluwatar.strangler.OldArithmetic -- Arithmetic mul 1.0 +13:02:25.032 [main] INFO com.iluwatar.strangler.OldSource -- Source module 1.0 +13:02:25.032 [main] INFO com.iluwatar.strangler.HalfArithmetic -- Arithmetic sum 1.5 +13:02:25.032 [main] INFO com.iluwatar.strangler.HalfSource -- Source module 1.5 +13:02:25.033 [main] INFO com.iluwatar.strangler.HalfArithmetic -- Arithmetic mul 1.5 +13:02:25.033 [main] INFO com.iluwatar.strangler.OldSource -- Source module 1.0 +13:02:25.033 [main] INFO com.iluwatar.strangler.HalfArithmetic -- Arithmetic check zero 1.5 +13:02:25.033 [main] INFO com.iluwatar.strangler.HalfSource -- Source module 1.5 +13:02:25.034 [main] INFO com.iluwatar.strangler.NewArithmetic -- Arithmetic sum 2.0 +13:02:25.034 [main] INFO com.iluwatar.strangler.NewSource -- Source module 2.0 +13:02:25.034 [main] INFO com.iluwatar.strangler.NewArithmetic -- Arithmetic mul 2.0 +13:02:25.034 [main] INFO com.iluwatar.strangler.NewSource -- Source module 2.0 +13:02:25.034 [main] INFO com.iluwatar.strangler.NewArithmetic -- Arithmetic check zero 2.0 +13:02:25.035 [main] INFO com.iluwatar.strangler.NewSource -- Source module 2.0 +``` + +This is a typical example of the Strangler pattern. The legacy system (`OldArithmetic`) is gradually replaced by the new system (`HalfArithmetic` and `NewArithmetic`). The new system is developed incrementally, and at each stage, it strangles a part of the legacy system until the legacy system is completely replaced. + +## When to Use the Strangler Pattern in Java + +* Use when you need to replace a monolithic or legacy system incrementally. +* Ideal for scenarios where the system cannot be replaced in one go due to risk or complexity. +* Suitable when you need to modernize parts of an application while ensuring continuous operation. +* Perfect for applications requiring updates with zero downtime, the Strangler pattern supports incremental updates in complex Java systems. + +## Strangler Pattern Java Tutorials + +* [Legacy Application Strangulation: Case Studies (Paul Hammant)](https://paulhammant.com/2013/07/14/legacy-application-strangulation-case-studies/) + +## Real-World Applications of Strangler Pattern in Java + +* Replacing a legacy monolithic application with a microservices architecture. +* Transitioning from an on-premise system to a cloud-based system. +* Incrementally migrating from an old database schema to a new one without downtime. + +## Benefits and Trade-offs of Strangler Pattern + +Benefits: + +* Reduces risk by allowing gradual replacement. +* Enables continuous delivery and operation during migration. +* Allows for testing and validating new components before full replacement. + +Trade-offs: + +* Requires managing interactions between new and old systems, which can be complex. +* May introduce temporary performance overhead due to coexistence of old and new systems. +* Potentially increases the initial development time due to the need for integration. + +## Related Java Design Patterns + +* [Adapter](https://java-design-patterns.com/patterns/adapter/): Used to make new systems interact with the old system during the transition period. +* [Facade](https://java-design-patterns.com/patterns/facade/): Can provide a unified interface to the old and new systems, simplifying client interactions. +* Microservices: The target architecture in many cases where the Strangler Pattern is applied. + +## References and Credits + +* [Building Microservices](https://amzn.to/3UACtrU) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Refactoring: Improving the Design of Existing Code](https://amzn.to/3TVEgaB) +* [Strangler pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler) diff --git a/strangler/pom.xml b/strangler/pom.xml index 74b800b06cfb..c05d6c7ec29a 100644 --- a/strangler/pom.xml +++ b/strangler/pom.xml @@ -34,6 +34,14 @@ 4.0.0 strangler + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/strangler/src/main/java/com/iluwatar/strangler/App.java b/strangler/src/main/java/com/iluwatar/strangler/App.java index 930ce457d837..ab8382ca6c9a 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/App.java +++ b/strangler/src/main/java/com/iluwatar/strangler/App.java @@ -25,43 +25,40 @@ package com.iluwatar.strangler; /** + * The Strangler pattern is a software design pattern that incrementally migrate a legacy system by + * gradually replacing specific pieces of functionality with new applications and services. As + * features from the legacy system are replaced, the new system eventually replaces all of the old + * system's features, strangling the old system and allowing you to decommission it. * - *

      The Strangler pattern is a software design pattern that incrementally migrate a legacy - * system by gradually replacing specific pieces of functionality with new applications and - * services. As features from the legacy system are replaced, the new system eventually - * replaces all of the old system's features, strangling the old system and allowing you - * to decommission it.

      - * - *

      This pattern is not only about updating but also enhancement.

      - * - *

      In this example, {@link OldArithmetic} indicates old system and its implementation depends - * on its source ({@link OldSource}). Now we tend to update system with new techniques and - * new features. In reality, the system may too complex, so usually need gradual migration. - * {@link HalfArithmetic} indicates system in the process of migration, its implementation - * depends on old one ({@link OldSource}) and under development one ({@link HalfSource}). The - * {@link HalfSource} covers part of {@link OldSource} and add new functionality. You can release - * this version system with new features, which also supports old version system functionalities. - * After whole migration, the new system ({@link NewArithmetic}) only depends on new source - * ({@link NewSource}).

      + *

      This pattern is not only about updating but also enhancement. * + *

      In this example, {@link OldArithmetic} indicates old system and its implementation depends on + * its source ({@link OldSource}). Now we tend to update system with new techniques and new + * features. In reality, the system may too complex, so usually need gradual migration. {@link + * HalfArithmetic} indicates system in the process of migration, its implementation depends on old + * one ({@link OldSource}) and under development one ({@link HalfSource}). The {@link HalfSource} + * covers part of {@link OldSource} and add new functionality. You can release this version system + * with new features, which also supports old version system functionalities. After whole migration, + * the new system ({@link NewArithmetic}) only depends on new source ({@link NewSource}). */ public class App { /** * Program entry point. + * * @param args command line args */ public static void main(final String[] args) { - final var nums = new int[]{1, 2, 3, 4, 5}; - //Before migration + final var nums = new int[] {1, 2, 3, 4, 5}; + // Before migration final var oldSystem = new OldArithmetic(new OldSource()); oldSystem.sum(nums); oldSystem.mul(nums); - //In process of migration + // In process of migration final var halfSystem = new HalfArithmetic(new HalfSource(), new OldSource()); halfSystem.sum(nums); halfSystem.mul(nums); halfSystem.ifHasZero(nums); - //After migration + // After migration final var newSystem = new NewArithmetic(new NewSource()); newSystem.sum(nums); newSystem.mul(nums); diff --git a/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java b/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java index b9ff85df07b7..a34e46bde4b0 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java +++ b/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java @@ -27,8 +27,8 @@ import lombok.extern.slf4j.Slf4j; /** - * System under migration. Depends on old version source ({@link OldSource}) and - * developing one ({@link HalfSource}). + * System under migration. Depends on old version source ({@link OldSource}) and developing one + * ({@link HalfSource}). */ @Slf4j public class HalfArithmetic { @@ -44,6 +44,7 @@ public HalfArithmetic(HalfSource newSource, OldSource oldSource) { /** * Accumulate sum. + * * @param nums numbers need to add together * @return accumulate sum */ @@ -54,6 +55,7 @@ public int sum(int... nums) { /** * Accumulate multiplication. + * * @param nums numbers need to multiply together * @return accumulate multiplication */ @@ -63,9 +65,10 @@ public int mul(int... nums) { } /** - * Chech if has any zero. + * Check if it has any zero. + * * @param nums numbers need to check - * @return if has any zero, return true, else, return false + * @return if it has any zero, return true, else, return false */ public boolean ifHasZero(int... nums) { LOGGER.info("Arithmetic check zero {}", VERSION); diff --git a/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java b/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java index 9413b55d2733..ad9f38ec036c 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java +++ b/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java @@ -27,26 +27,18 @@ import java.util.Arrays; import lombok.extern.slf4j.Slf4j; -/** - * Source under development. Replace part of old source and has added some new features. - */ +/** Source under development. Replace part of old source and has added some new features. */ @Slf4j public class HalfSource { private static final String VERSION = "1.5"; - /** - * Implement accumulate sum with new technique. - * Replace old one in {@link OldSource} - */ + /** Implement accumulate sum with new technique. Replace old one in {@link OldSource} */ public int accumulateSum(int... nums) { LOGGER.info("Source module {}", VERSION); return Arrays.stream(nums).reduce(0, Integer::sum); } - /** - * Check if all number is not zero. - * New feature. - */ + /** Check if all number is not zero. New feature. */ public boolean ifNonZero(int... nums) { LOGGER.info("Source module {}", VERSION); return Arrays.stream(nums).allMatch(num -> num != 0); diff --git a/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java b/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java index 248e1f4692f0..75156da20e7c 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java +++ b/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * System after whole migration. Only depends on new version source ({@link NewSource}). - */ +/** System after whole migration. Only depends on new version source ({@link NewSource}). */ @Slf4j public class NewArithmetic { private static final String VERSION = "2.0"; @@ -41,6 +39,7 @@ public NewArithmetic(NewSource source) { /** * Accumulate sum. + * * @param nums numbers need to add together * @return accumulate sum */ @@ -51,6 +50,7 @@ public int sum(int... nums) { /** * Accumulate multiplication. + * * @param nums numbers need to multiply together * @return accumulate multiplication */ @@ -60,9 +60,10 @@ public int mul(int... nums) { } /** - * Chech if has any zero. + * Check if it has any zero. + * * @param nums numbers need to check - * @return if has any zero, return true, else, return false + * @return if it has any zero, return true, else, return false */ public boolean ifHasZero(int... nums) { LOGGER.info("Arithmetic check zero {}", VERSION); diff --git a/strangler/src/main/java/com/iluwatar/strangler/NewSource.java b/strangler/src/main/java/com/iluwatar/strangler/NewSource.java index 25bef8a3e07a..78e709408846 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/NewSource.java +++ b/strangler/src/main/java/com/iluwatar/strangler/NewSource.java @@ -28,8 +28,8 @@ import lombok.extern.slf4j.Slf4j; /** - * New source. Completely covers functionalities of old source with new techniques - * and also has some new features. + * New source. Completely covers functionalities of old source with new techniques and also has some + * new features. */ @Slf4j public class NewSource { @@ -41,10 +41,7 @@ public int accumulateSum(int... nums) { return Arrays.stream(nums).reduce(0, Integer::sum); } - /** - * Implement accumulate multiply with new technique. - * Replace old one in {@link OldSource} - */ + /** Implement accumulate multiply with new technique. Replace old one in {@link OldSource} */ public int accumulateMul(int... nums) { LOGGER.info(SOURCE_MODULE, VERSION); return Arrays.stream(nums).reduce(1, (a, b) -> a * b); diff --git a/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java b/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java index 5b5162b32e6c..299f61d30657 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java +++ b/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Old version system depends on old version source ({@link OldSource}). - */ +/** Old version system depends on old version source ({@link OldSource}). */ @Slf4j public class OldArithmetic { private static final String VERSION = "1.0"; @@ -41,6 +39,7 @@ public OldArithmetic(OldSource source) { /** * Accumulate sum. + * * @param nums numbers need to add together * @return accumulate sum */ @@ -51,6 +50,7 @@ public int sum(int... nums) { /** * Accumulate multiplication. + * * @param nums numbers need to multiply together * @return accumulate multiplication */ diff --git a/strangler/src/main/java/com/iluwatar/strangler/OldSource.java b/strangler/src/main/java/com/iluwatar/strangler/OldSource.java index 7e833dea9dd9..5be29bffc879 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/OldSource.java +++ b/strangler/src/main/java/com/iluwatar/strangler/OldSource.java @@ -26,16 +26,12 @@ import lombok.extern.slf4j.Slf4j; -/** - * Old source with techniques out of date. - */ +/** Old source with techniques out of date. */ @Slf4j public class OldSource { private static final String VERSION = "1.0"; - /** - * Implement accumulate sum with old technique. - */ + /** Implement accumulate sum with old technique. */ public int accumulateSum(int... nums) { LOGGER.info("Source module {}", VERSION); var sum = 0; @@ -45,9 +41,7 @@ public int accumulateSum(int... nums) { return sum; } - /** - * Implement accumulate multiply with old technique. - */ + /** Implement accumulate multiply with old technique. */ public int accumulateMul(int... nums) { LOGGER.info("Source module {}", VERSION); var sum = 1; diff --git a/strangler/src/test/java/com/iluwatar/strangler/AppTest.java b/strangler/src/test/java/com/iluwatar/strangler/AppTest.java index 2e5c2015011f..777af17660e3 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/AppTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.strangler; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java b/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java index 94aee37435d0..c8f92e18af24 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java @@ -29,11 +29,10 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in HalfArithmetic - */ +/** Test methods in HalfArithmetic */ class HalfArithmeticTest { - private static final HalfArithmetic arithmetic = new HalfArithmetic(new HalfSource(), new OldSource()); + private static final HalfArithmetic arithmetic = + new HalfArithmetic(new HalfSource(), new OldSource()); @Test void testSum() { @@ -49,4 +48,4 @@ void testMul() { void testIfHasZero() { assertTrue(arithmetic.ifHasZero(-1, 0, 1)); } -} \ No newline at end of file +} diff --git a/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java b/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java index 4b6926a0c8d9..09ae98c925a9 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in HalfSource - */ +/** Test methods in HalfSource */ class HalfSourceTest { private static final HalfSource source = new HalfSource(); diff --git a/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java b/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java index 51dda81c63e8..5c6958f3d77c 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in NewArithmetic - */ +/** Test methods in NewArithmetic */ class NewArithmeticTest { private static final NewArithmetic arithmetic = new NewArithmetic(new NewSource()); @@ -49,4 +47,4 @@ void testMul() { void testIfHasZero() { assertTrue(arithmetic.ifHasZero(-1, 0, 1)); } -} \ No newline at end of file +} diff --git a/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java b/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java index 2ceb745ee96a..658aa53971fe 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in NewSource - */ +/** Test methods in NewSource */ class NewSourceTest { private static final NewSource source = new NewSource(); diff --git a/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java b/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java index 4c449cbdfb7e..4ce9d2dddaa0 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in OldArithmetic - */ +/** Test methods in OldArithmetic */ class OldArithmeticTest { private static final OldArithmetic arithmetic = new OldArithmetic(new OldSource()); @@ -43,4 +41,4 @@ void testSum() { void testMul() { assertEquals(0, arithmetic.mul(-1, 0, 1)); } -} \ No newline at end of file +} diff --git a/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java b/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java index cb1d1b331bf4..b90d08af33b4 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in OldSource - */ +/** Test methods in OldSource */ class OldSourceTest { private static final OldSource source = new OldSource(); diff --git a/strategy/README.md b/strategy/README.md index b70ed94f29e0..781463d20af8 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -1,39 +1,44 @@ --- -title: Strategy +title: "Strategy Pattern in Java: Streamlining Object Behaviors with Interchangeable Algorithms" +shortTitle: Strategy +description: "Explore the Strategy design pattern in Java with a detailed guide and practical examples. Learn how to implement flexible and interchangeable algorithms effectively in your Java applications for enhanced design and maintenance." category: Behavioral language: en tag: - - Gang of Four + - Decoupling + - Extensibility + - Gang of Four + - Interface + - Polymorphism --- ## Also known as -Policy +* Policy -## Intent +## Intent of Strategy Design Pattern -Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets -the algorithm vary independently of the clients that use it. +Define a family of algorithms in Java, encapsulate each one, and make them interchangeable to enhance software development using the Strategy design pattern. Strategy lets the algorithm vary independently of the clients that use it. -## Explanation +## Detailed Explanation of Strategy Pattern with Real-World Examples Real-world example -> Slaying dragons is a dangerous job. With experience, it becomes easier. Veteran -> dragonslayers have developed different fighting strategies against different types of dragons. +> A practical real-world example of the Strategy design pattern in Java is evident in car navigation systems, where algorithm flexibility is paramount. Different navigation algorithms (such as shortest route, fastest route, and scenic route) can be used to determine the best path from one location to another. Each algorithm encapsulates a specific strategy for calculating the route. The user (client) can switch between these algorithms based on their preferences without changing the navigation system itself. This allows for flexible and interchangeable navigation strategies within the same system. In plain words -> Strategy pattern allows choosing the best-suited algorithm at runtime. +> Strategy pattern allows choosing the best-suited algorithm at runtime. Wikipedia says -> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral -> software design pattern that enables selecting an algorithm at runtime. +> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. -**Programmatic Example** +## Programmatic Example of Strategy Pattern in Java -Let's first introduce the dragon-slaying strategy interface and its implementations. +Slaying dragons is a dangerous job. With experience, it becomes easier. Veteran dragonslayers have developed different fighting strategies against different types of dragons. + +Let's explore how to implement the `DragonSlayingStrategy` interface in Java, demonstrating various Strategy pattern applications. ```java @FunctionalInterface @@ -41,7 +46,9 @@ public interface DragonSlayingStrategy { void execute(); } +``` +```java @Slf4j public class MeleeStrategy implements DragonSlayingStrategy { @@ -50,7 +57,9 @@ public class MeleeStrategy implements DragonSlayingStrategy { LOGGER.info("With your Excalibur you sever the dragon's head!"); } } +``` +```java @Slf4j public class ProjectileStrategy implements DragonSlayingStrategy { @@ -59,7 +68,9 @@ public class ProjectileStrategy implements DragonSlayingStrategy { LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); } } +``` +```java @Slf4j public class SpellStrategy implements DragonSlayingStrategy { @@ -70,8 +81,7 @@ public class SpellStrategy implements DragonSlayingStrategy { } ``` -And here is the mighty dragonslayer, who can pick his fighting strategy based on the -opponent. +And here is the mighty `DragonSlayer`, who can pick his fighting strategy based on the opponent. ```java public class DragonSlayer { @@ -92,96 +102,120 @@ public class DragonSlayer { } ``` -Finally, here's the dragonslayer in action. +Finally, here's the `DragonSlayer` in action. ```java - LOGGER.info("Green dragon spotted ahead!"); +@Slf4j +public class App { + + private static final String RED_DRAGON_EMERGES = "Red dragon emerges."; + private static final String GREEN_DRAGON_SPOTTED = "Green dragon spotted ahead!"; + private static final String BLACK_DRAGON_LANDS = "Black dragon lands before you."; + + public static void main(String[] args) { + // GoF Strategy pattern + LOGGER.info(GREEN_DRAGON_SPOTTED); var dragonSlayer = new DragonSlayer(new MeleeStrategy()); dragonSlayer.goToBattle(); - LOGGER.info("Red dragon emerges."); + LOGGER.info(RED_DRAGON_EMERGES); dragonSlayer.changeStrategy(new ProjectileStrategy()); dragonSlayer.goToBattle(); - LOGGER.info("Black dragon lands before you."); + LOGGER.info(BLACK_DRAGON_LANDS); dragonSlayer.changeStrategy(new SpellStrategy()); dragonSlayer.goToBattle(); + + // Java 8 functional implementation Strategy pattern + LOGGER.info(GREEN_DRAGON_SPOTTED); + dragonSlayer = new DragonSlayer( + () -> LOGGER.info("With your Excalibur you sever the dragon's head!")); + dragonSlayer.goToBattle(); + LOGGER.info(RED_DRAGON_EMERGES); + dragonSlayer.changeStrategy(() -> LOGGER.info( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")); + dragonSlayer.goToBattle(); + LOGGER.info(BLACK_DRAGON_LANDS); + dragonSlayer.changeStrategy(() -> LOGGER.info( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + dragonSlayer.goToBattle(); + + // Java 8 lambda implementation with enum Strategy pattern + LOGGER.info(GREEN_DRAGON_SPOTTED); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.MELEE_STRATEGY); + dragonSlayer.goToBattle(); + LOGGER.info(RED_DRAGON_EMERGES); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.PROJECTILE_STRATEGY); + dragonSlayer.goToBattle(); + LOGGER.info(BLACK_DRAGON_LANDS); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.SPELL_STRATEGY); + dragonSlayer.goToBattle(); + } +} ``` Program output: ``` - Green dragon spotted ahead! - With your Excalibur you sever the dragon's head! - Red dragon emerges. - You shoot the dragon with the magical crossbow and it falls dead on the ground! - Black dragon lands before you. - You cast the spell of disintegration and the dragon vaporizes in a pile of dust! +13:06:36.631 [main] INFO com.iluwatar.strategy.App -- Green dragon spotted ahead! +13:06:36.634 [main] INFO com.iluwatar.strategy.MeleeStrategy -- With your Excalibur you sever the dragon's head! +13:06:36.634 [main] INFO com.iluwatar.strategy.App -- Red dragon emerges. +13:06:36.634 [main] INFO com.iluwatar.strategy.ProjectileStrategy -- You shoot the dragon with the magical crossbow and it falls dead on the ground! +13:06:36.634 [main] INFO com.iluwatar.strategy.App -- Black dragon lands before you. +13:06:36.634 [main] INFO com.iluwatar.strategy.SpellStrategy -- You cast the spell of disintegration and the dragon vaporizes in a pile of dust! +13:06:36.634 [main] INFO com.iluwatar.strategy.App -- Green dragon spotted ahead! +13:06:36.634 [main] INFO com.iluwatar.strategy.App -- With your Excalibur you sever the dragon's head! +13:06:36.634 [main] INFO com.iluwatar.strategy.App -- Red dragon emerges. +13:06:36.635 [main] INFO com.iluwatar.strategy.App -- You shoot the dragon with the magical crossbow and it falls dead on the ground! +13:06:36.635 [main] INFO com.iluwatar.strategy.App -- Black dragon lands before you. +13:06:36.635 [main] INFO com.iluwatar.strategy.App -- You cast the spell of disintegration and the dragon vaporizes in a pile of dust! +13:06:36.635 [main] INFO com.iluwatar.strategy.App -- Green dragon spotted ahead! +13:06:36.637 [main] INFO com.iluwatar.strategy.LambdaStrategy -- With your Excalibur you sever the dragon's head! +13:06:36.637 [main] INFO com.iluwatar.strategy.App -- Red dragon emerges. +13:06:36.637 [main] INFO com.iluwatar.strategy.LambdaStrategy -- You shoot the dragon with the magical crossbow and it falls dead on the ground! +13:06:36.637 [main] INFO com.iluwatar.strategy.App -- Black dragon lands before you. +13:06:36.637 [main] INFO com.iluwatar.strategy.LambdaStrategy -- You cast the spell of disintegration and the dragon vaporizes in a pile of dust! ``` -What's more, the lambda expressions in Java 8 provides another approach for the implementation: +## When to Use the Strategy Pattern in Java -```java -public class LambdaStrategy { +Use the Strategy pattern when: - private static final Logger LOGGER = LoggerFactory.getLogger(LambdaStrategy.class); +* You need to use different variants of an algorithm within an object and want to switch algorithms at runtime. +* There are multiple related classes that differ only in their behavior. +* An algorithm uses data that clients shouldn't know about. +* A class defines many behaviors and these appear as multiple conditional statements in its operations. - public enum Strategy implements DragonSlayingStrategy { - MeleeStrategy(() -> LOGGER.info( - "With your Excalibur you severe the dragon's head!")), - ProjectileStrategy(() -> LOGGER.info( - "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), - SpellStrategy(() -> LOGGER.info( - "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); - - private final DragonSlayingStrategy dragonSlayingStrategy; +## Strategy Pattern Java Tutorials - Strategy(DragonSlayingStrategy dragonSlayingStrategy) { - this.dragonSlayingStrategy = dragonSlayingStrategy; - } +* [Strategy Pattern Tutorial (DigitalOcean)](https://www.digitalocean.com/community/tutorials/strategy-design-pattern-in-java-example-tutorial) - @Override - public void execute() { - dragonSlayingStrategy.execute(); - } - } -} -``` - -And here's the dragonslayer in action. - -```java - LOGGER.info("Green dragon spotted ahead!"); - dragonSlayer.changeStrategy(LambdaStrategy.Strategy.MeleeStrategy); - dragonSlayer.goToBattle(); - LOGGER.info("Red dragon emerges."); - dragonSlayer.changeStrategy(LambdaStrategy.Strategy.ProjectileStrategy); - dragonSlayer.goToBattle(); - LOGGER.info("Black dragon lands before you."); - dragonSlayer.changeStrategy(LambdaStrategy.Strategy.SpellStrategy); - dragonSlayer.goToBattle(); -``` +## Real-World Applications of Strategy Pattern in Java -The program output is the same as the above one. +* Java's `java.util.Comparator` interface is a common example of the Strategy pattern. +* In GUI frameworks, layout managers (such as those in Java's AWT and Swing) are strategies. -## Class diagram +## Benefits and Trade-offs of Strategy Pattern -![alt text](./etc/strategy_urm.png "Strategy") +Benefits: -## Applicability +* Families of related algorithms are reused. +* An alternative to subclassing for extending behavior. +* Avoids conditional statements for selecting desired behavior. +* Allows clients to choose algorithm implementation. -Use the Strategy pattern when +Trade-offs: -* Many related classes differ only in their behavior. Strategies provide a way to configure a class either one of many behaviors -* You need different variants of an algorithm. for example, you might define algorithms reflecting different space/time trade-offs. Strategies can be used when these variants are implemented as a class hierarchy of algorithms -* An algorithm uses data that clients shouldn't know about. Use the Strategy pattern to avoid exposing complex algorithm-specific data structures -* A class defines many behaviors, and these appear as multiple conditional statements in its operations. Instead of many conditionals, move the related conditional branches into their own Strategy class +* Clients must be aware of different Strategies. +* Increase in the number of objects. -## Tutorial +## Related Java Design Patterns -* [Strategy Pattern Tutorial](https://www.journaldev.com/1754/strategy-design-pattern-in-java-example-tutorial) +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Enhances an object without changing its interface but is more concerned with responsibilities than algorithms. +* [State](https://java-design-patterns.com/patterns/state/): Similar in structure but used to represent state-dependent behavior rather than interchangeable algorithms. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Functional Programming in Java](https://amzn.to/3JUIc5Q) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/strategy/pom.xml b/strategy/pom.xml index 9b7b1e9c80a1..3c84050bf19d 100644 --- a/strategy/pom.xml +++ b/strategy/pom.xml @@ -34,6 +34,14 @@ strategy + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/strategy/src/main/java/com/iluwatar/strategy/App.java b/strategy/src/main/java/com/iluwatar/strategy/App.java index 0378f6eb3f0d..6c24d0e46ad5 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/App.java +++ b/strategy/src/main/java/com/iluwatar/strategy/App.java @@ -27,17 +27,15 @@ import lombok.extern.slf4j.Slf4j; /** + * The Strategy pattern (also known as the policy pattern) is a software design pattern that enables + * an algorithm's behavior to be selected at runtime. * - *

      The Strategy pattern (also known as the policy pattern) is a software design pattern that - * enables an algorithm's behavior to be selected at runtime.

      - * - *

      Before Java 8 the Strategies needed to be separate classes forcing the developer - * to write lots of boilerplate code. With modern Java, it is easy to pass behavior - * with method references and lambdas making the code shorter and more readable.

      + *

      Before Java 8 the Strategies needed to be separate classes forcing the developer to write lots + * of boilerplate code. With modern Java, it is easy to pass behavior with method references and + * lambdas making the code shorter and more readable. * *

      In this example ({@link DragonSlayingStrategy}) encapsulates an algorithm. The containing - * object ({@link DragonSlayer}) can alter its behavior by changing its strategy.

      - * + * object ({@link DragonSlayer}) can alter its behavior by changing its strategy. */ @Slf4j public class App { @@ -65,27 +63,31 @@ public static void main(String[] args) { // Java 8 functional implementation Strategy pattern LOGGER.info(GREEN_DRAGON_SPOTTED); - dragonSlayer = new DragonSlayer( - () -> LOGGER.info("With your Excalibur you severe the dragon's head!")); + dragonSlayer = + new DragonSlayer(() -> LOGGER.info("With your Excalibur you sever the dragon's head!")); dragonSlayer.goToBattle(); LOGGER.info(RED_DRAGON_EMERGES); - dragonSlayer.changeStrategy(() -> LOGGER.info( - "You shoot the dragon with the magical crossbow and it falls dead on the ground!")); + dragonSlayer.changeStrategy( + () -> + LOGGER.info( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")); dragonSlayer.goToBattle(); LOGGER.info(BLACK_DRAGON_LANDS); - dragonSlayer.changeStrategy(() -> LOGGER.info( - "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + dragonSlayer.changeStrategy( + () -> + LOGGER.info( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); dragonSlayer.goToBattle(); // Java 8 lambda implementation with enum Strategy pattern LOGGER.info(GREEN_DRAGON_SPOTTED); - dragonSlayer.changeStrategy(LambdaStrategy.Strategy.MeleeStrategy); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.MELEE_STRATEGY); dragonSlayer.goToBattle(); LOGGER.info(RED_DRAGON_EMERGES); - dragonSlayer.changeStrategy(LambdaStrategy.Strategy.ProjectileStrategy); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.PROJECTILE_STRATEGY); dragonSlayer.goToBattle(); LOGGER.info(BLACK_DRAGON_LANDS); - dragonSlayer.changeStrategy(LambdaStrategy.Strategy.SpellStrategy); + dragonSlayer.changeStrategy(LambdaStrategy.Strategy.SPELL_STRATEGY); dragonSlayer.goToBattle(); } } diff --git a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java index 43416a4f620d..1e623e665e3f 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java +++ b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java @@ -1,45 +1,43 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -/** - * DragonSlayer uses different strategies to slay the dragon. - */ -public class DragonSlayer { - - private DragonSlayingStrategy strategy; - - public DragonSlayer(DragonSlayingStrategy strategy) { - this.strategy = strategy; - } - - public void changeStrategy(DragonSlayingStrategy strategy) { - this.strategy = strategy; - } - - public void goToBattle() { - strategy.execute(); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +/** DragonSlayer uses different strategies to slay the dragon. */ +public class DragonSlayer { + + private DragonSlayingStrategy strategy; + + public DragonSlayer(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void changeStrategy(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void goToBattle() { + strategy.execute(); + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java index ccba437368bd..c05b1965a90a 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java @@ -1,35 +1,32 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -/** - * Strategy interface. - */ -@FunctionalInterface -public interface DragonSlayingStrategy { - - void execute(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +/** Strategy interface. */ +@FunctionalInterface +public interface DragonSlayingStrategy { + + void execute(); +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java index 698e1bb140b2..27137bcc10e3 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java @@ -26,22 +26,21 @@ import lombok.extern.slf4j.Slf4j; -/** - * Lambda implementation for enum strategy pattern. - */ +/** Lambda implementation for enum strategy pattern. */ @Slf4j public class LambdaStrategy { - /** - * Enum to demonstrate strategy pattern. - */ + /** Enum to demonstrate strategy pattern. */ public enum Strategy implements DragonSlayingStrategy { - MeleeStrategy(() -> LOGGER.info( - "With your Excalibur you severe the dragon's head!")), - ProjectileStrategy(() -> LOGGER.info( - "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), - SpellStrategy(() -> LOGGER.info( - "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + MELEE_STRATEGY(() -> LOGGER.info("With your Excalibur you sever the dragon's head!")), + PROJECTILE_STRATEGY( + () -> + LOGGER.info( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), + SPELL_STRATEGY( + () -> + LOGGER.info( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); private final DragonSlayingStrategy dragonSlayingStrategy; diff --git a/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java index 23769944c3f4..6bf4603f4b5c 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java @@ -1,39 +1,37 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -import lombok.extern.slf4j.Slf4j; - -/** - * Melee strategy. - */ -@Slf4j -public class MeleeStrategy implements DragonSlayingStrategy { - - @Override - public void execute() { - LOGGER.info("With your Excalibur you sever the dragon's head!"); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +import lombok.extern.slf4j.Slf4j; + +/** Melee strategy. */ +@Slf4j +public class MeleeStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("With your Excalibur you sever the dragon's head!"); + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java index 93af41fa6bb9..38f51bd8a434 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java @@ -1,39 +1,37 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -import lombok.extern.slf4j.Slf4j; - -/** - * Projectile strategy. - */ -@Slf4j -public class ProjectileStrategy implements DragonSlayingStrategy { - - @Override - public void execute() { - LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +import lombok.extern.slf4j.Slf4j; + +/** Projectile strategy. */ +@Slf4j +public class ProjectileStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java index 5b7127bc28b0..6b284dbd74ee 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java @@ -1,40 +1,37 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -import lombok.extern.slf4j.Slf4j; - -/** - * Spell strategy. - */ -@Slf4j -public class SpellStrategy implements DragonSlayingStrategy { - - @Override - public void execute() { - LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"); - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +import lombok.extern.slf4j.Slf4j; + +/** Spell strategy. */ +@Slf4j +public class SpellStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"); + } +} diff --git a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java index 5e0ec437a7b0..e26c469a57da 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.strategy; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test. - */ +import org.junit.jupiter.api.Test; + +/** Application test. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java index 6753a9fa8c07..49b636ecfbbb 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java @@ -30,16 +30,10 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 10:50 PM. - * - * @author Jeroen Meulemeester - */ +/** DragonSlayerTest */ class DragonSlayerTest { - /** - * Verify if the dragon slayer uses the strategy during battle. - */ + /** Verify if the dragon slayer uses the strategy during battle. */ @Test void testGoToBattle() { final var strategy = mock(DragonSlayingStrategy.class); @@ -50,9 +44,7 @@ void testGoToBattle() { verifyNoMoreInteractions(strategy); } - /** - * Verify if the dragon slayer uses the new strategy during battle after a change of strategy. - */ + /** Verify if the dragon slayer uses the new strategy during battle after a change of strategy. */ @Test void testChangeStrategy() { final var initialStrategy = mock(DragonSlayingStrategy.class); @@ -69,4 +61,4 @@ void testChangeStrategy() { verifyNoMoreInteractions(initialStrategy, newStrategy); } -} \ No newline at end of file +} diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java index c01b9d3bea91..6162c641cd7f 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java @@ -38,11 +38,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.LoggerFactory; -/** - * Date: 12/29/15 - 10:58 PM. - * - * @author Jeroen Meulemeester - */ +/** DragonSlayingStrategyTest */ class DragonSlayingStrategyTest { /** @@ -52,19 +48,15 @@ class DragonSlayingStrategyTest { */ static Collection dataProvider() { return List.of( - new Object[]{ - new MeleeStrategy(), - "With your Excalibur you sever the dragon's head!" - }, - new Object[]{ - new ProjectileStrategy(), - "You shoot the dragon with the magical crossbow and it falls dead on the ground!" + new Object[] {new MeleeStrategy(), "With your Excalibur you sever the dragon's head!"}, + new Object[] { + new ProjectileStrategy(), + "You shoot the dragon with the magical crossbow and it falls dead on the ground!" }, - new Object[]{ - new SpellStrategy(), - "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!" - } - ); + new Object[] { + new SpellStrategy(), + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!" + }); } private InMemoryAppender appender; @@ -79,10 +71,7 @@ void tearDown() { appender.stop(); } - - /** - * Test if executing the strategy gives the correct response. - */ + /** Test if executing the strategy gives the correct response. */ @ParameterizedTest @MethodSource("dataProvider") void testExecute(DragonSlayingStrategy strategy, String expectedResult) { @@ -91,7 +80,7 @@ void testExecute(DragonSlayingStrategy strategy, String expectedResult) { assertEquals(1, appender.getLogSize()); } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender() { diff --git a/subclass-sandbox/README.md b/subclass-sandbox/README.md index 7a4587e3d4d5..1dbea163ed19 100644 --- a/subclass-sandbox/README.md +++ b/subclass-sandbox/README.md @@ -1,27 +1,49 @@ ---- -title: Subclass Sandbox +--- +title: "Subclass Sandbox Pattern in Java: Enhancing Code Reusability with Sandbox Techniques" +shortTitle: Subclass Sandbox +description: "Explore the Subclass Sandbox design pattern in Java, perfect for allowing flexible behavior customization in object-oriented programming. Ideal for game development and extending class behavior." category: Behavioral language: en -tag: - - Game programming ---- +tag: + - Abstraction + - Code simplification + - Decoupling + - Extensibility + - Game programming + - Polymorphism +--- -## Intent -The subclass sandbox pattern describes a basic idea, while not having a lot of detailed mechanics. You will need the pattern when you have several similar subclasses. If you have to make a tiny change, then change the base class, while all subclasses shouldn't have to be touched. So the base class has to be able to provide all of the operations a derived class needs to perform. +## Also known as + +* Hook Method + +## Intent of Subclass Sandbox Design Pattern + +The Subclass Sandbox design pattern in Java allows subclasses to alter the core behavior of a class by providing specific implementations of certain methods while keeping the overall structure unchanged. -## Explanation -Real world example -> Consider we want to create some superpower in the game, and they need to move accompanied by a sound effect and spawn particles. Create many classes that contain similar methods or need a base class to derivate them? The subclass-sandbox pattern allows you to deal with this problem in the second way. +## Detailed Explanation of Subclass Sandbox Pattern with Real-World Examples + +Real-world example + +> Imagine a cooking class where the instructor provides a standard recipe structure, including steps like "prepare ingredients," "cook," and "serve." Each student follows this structure but can customize specific steps to create their unique dish. For example, one student might choose to prepare a salad, while another might prepare a stir-fry, both adhering to the same overarching recipe format. This way, the instructor ensures that all dishes are made following a consistent process, but students have the flexibility to personalize the key parts of their recipes. This mirrors the Subclass Sandbox pattern, where the core structure is defined by the superclass, and specific behaviors are customized in subclasses. In plain words -> The subclass-sandbox is about moving the overlap methods in the subclasses to a base class which reduces the redundant rate in the classes. -Wikipedia says +> The Subclass Sandbox pattern allows subclasses to customize specific behaviors within a predefined algorithm structure provided by the superclass. + +[gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/) says + > A base class defines an abstract sandbox method and several provided operations. Marking them protected makes it clear that they are for use by derived classes. Each derived sandboxed subclass implements the sandbox method using the provided operations. -> -**Programmatic Example** -We start with the base class `Superpower`. It contains an abstract sandbox method `active()` and some provided operations. -``` + +## Programmatic Example of Subclass Sandbox Pattern in Java + +Using the Subclass Sandbox pattern, developers can create distinct functionalities within Java applications, enhancing game development and software design. + +Suppose you want to create various superpowers in a game, where each superpower needs to move with a sound effect and spawn particles. Should you create many classes with similar methods or derive them from a base class? The Subclass Sandbox pattern enables you to handle this efficiently by deriving these classes from a common base class. + +We start with the base class `Superpower`. It contains an abstract sandbox method `active` and some provided operations. + +```java public abstract class Superpower { protected Logger logger; @@ -41,8 +63,10 @@ public abstract class Superpower { } } ``` -Next we are able to create derived sandboxed subclass that implements the sandbox method using the provided operations. Here is the first power: -``` + +Next, we are able to create derived sandboxed subclass that implements the sandbox method using the provided operations. Here is the first power: + +```java public class SkyLaunch extends Superpower { public SkyLaunch() { @@ -58,8 +82,10 @@ public class SkyLaunch extends Superpower { } } ``` + Here is the second power. -``` + +```java public class GroundDive extends Superpower { public GroundDive() { @@ -75,37 +101,68 @@ public class GroundDive extends Superpower { } } ``` + Finally, here are the superpowers in active. -``` + +```java +public static void main(String[] args) { LOGGER.info("Use superpower: sky launch"); var skyLaunch = new SkyLaunch(); skyLaunch.activate(); LOGGER.info("Use superpower: ground dive"); var groundDive = new GroundDive(); groundDive.activate(); +} ``` + Program output: + ``` -// Use superpower: sky launch -// Move to ( 0.0, 0.0, 20.0 ) -// Play SKYLAUNCH_SOUND with volume 1 -// Spawn 100 particle with type SKYLAUNCH_PARTICLE -// Use superpower: ground dive -// Move to ( 0.0, 0.0, -20.0 ) -// Play GROUNDDIVE_SOUND with volume 5 -// Spawn 20 particle with type GROUNDDIVE_PARTICLE +13:10:23.177 [main] INFO com.iluwatar.subclasssandbox.App -- Use superpower: sky launch +13:10:23.179 [main] INFO com.iluwatar.subclasssandbox.SkyLaunch -- Move to ( 0.0, 0.0, 20.0 ) +13:10:23.180 [main] INFO com.iluwatar.subclasssandbox.SkyLaunch -- Play SKYLAUNCH_SOUND with volume 1 +13:10:23.180 [main] INFO com.iluwatar.subclasssandbox.SkyLaunch -- Spawn 100 particle with type SKYLAUNCH_PARTICLE +13:10:23.180 [main] INFO com.iluwatar.subclasssandbox.App -- Use superpower: ground dive +13:10:23.180 [main] INFO com.iluwatar.subclasssandbox.GroundDive -- Move to ( 0.0, 0.0, -20.0 ) +13:10:23.180 [main] INFO com.iluwatar.subclasssandbox.GroundDive -- Play GROUNDDIVE_SOUND with volume 5 +13:10:23.180 [main] INFO com.iluwatar.subclasssandbox.GroundDive -- Spawn 20 particle with type GROUNDDIVE_PARTICLE ``` -## Class diagram -![alt text](./etc/subclass-sandbox.urm.png "Subclass Sandbox pattern class diagram") - -## Applicability -The Subclass Sandbox pattern is a very simple, common pattern lurking in lots of codebases, even outside of games. If you have a non-virtual protected method laying around, you’re probably already using something like this. Subclass Sandbox is a good fit when: -- You have a base class with a number of derived classes. -- The base class is able to provide all of the operations that a derived class may need to perform. -- There is behavioral overlap in the subclasses and you want to make it easier to share code between them. -- You want to minimize coupling between those derived classes and the rest of the program. - -## Credits - -* [Game Programming Patterns - Subclass Sandbox](https://gameprogrammingpatterns.com/subclass-sandbox.html) +## When to Use the Subclass Sandbox Pattern in Java + +* Use when you want to create a framework that allows users to define their own behaviors by extending classes. +* Applicable in scenarios where you need to enforce a specific algorithm structure while allowing certain steps to be overridden. + +## Real-World Applications of Subclass Sandbox Pattern in Java + +* Template method pattern in GUI frameworks where the framework provides the structure and the subclasses implement the specifics. +* Game development where the core game loop is defined, but specific behaviors are provided by subclassing. +* Java libraries like the `AbstractList` where core methods are defined and certain behaviors can be customized by extending classes. + +## Benefits and Trade-offs of Subclass Sandbox Pattern + +Benefits: + +The Subclass Sandbox pattern in Java + +* Encourages code reuse by allowing shared code in the superclass. +* Simplifies the addition of new behaviors through subclassing. +* Enhances code readability and maintainability by separating the algorithm's structure from specific implementations. + +Trade-offs: + +* Can lead to a large number of subclasses. +* Requires careful design to ensure that the base class is flexible enough for various extensions. +* Increases complexity in understanding the code flow due to multiple layers of inheritance. + +## Related Java Design Patterns + +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Both involve interchangeable behaviors, but Strategy pattern uses composition over inheritance. +* [Template Method](https://java-design-patterns.com/patterns/template-method/): Similar in enforcing a structure where certain steps can be overridden by subclasses. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Game Programming Patterns](https://amzn.to/3K96fOn) +* [Subclass Sandbox (Game Programming Patterns)](https://gameprogrammingpatterns.com/subclass-sandbox.html) diff --git a/subclass-sandbox/pom.xml b/subclass-sandbox/pom.xml index bc3a2a2d7920..9d21c7400f09 100644 --- a/subclass-sandbox/pom.xml +++ b/subclass-sandbox/pom.xml @@ -33,11 +33,30 @@ 4.0.0 subclass-sandbox + + 2.0.17 + 1.5.18 + com.github.stefanbirkner system-lambda + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + ch.qos.logback + logback-core + ${logback.version} + org.junit.jupiter junit-jupiter-engine diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java index 0934e73b7d7f..7db986965aff 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java @@ -27,17 +27,18 @@ import lombok.extern.slf4j.Slf4j; /** - * The subclass sandbox pattern describes a basic idea, while not having a lot - * of detailed mechanics. You will need the pattern when you have several similar - * subclasses. If you have to make a tiny change, then change the base class, - * while all subclasses shouldn't have to be touched. So the base class has to be - * able to provide all of the operations a derived class needs to perform. + * The subclass sandbox pattern describes a basic idea, while not having a lot of detailed + * mechanics. You will need the pattern when you have several similar subclasses. If you have to + * make a tiny change, then change the base class, while all subclasses shouldn't have to be + * touched. So the base class has to be able to provide all the operations a derived class needs to + * perform. */ @Slf4j public class App { /** * Entry point of the main program. + * * @param args Program runtime arguments. */ public static void main(String[] args) { @@ -48,5 +49,4 @@ public static void main(String[] args) { var groundDive = new GroundDive(); groundDive.activate(); } - } diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java index 6cd9d324a8da..606dd3206684 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java @@ -26,9 +26,7 @@ import org.slf4j.LoggerFactory; -/** - * GroundDive superpower. - */ +/** GroundDive superpower. */ public class GroundDive extends Superpower { public GroundDive() { diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java index 9bab6ef9f37e..611dacb2ff3d 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java @@ -26,9 +26,7 @@ import org.slf4j.LoggerFactory; -/** - * SkyLaunch superpower. - */ +/** SkyLaunch superpower. */ public class SkyLaunch extends Superpower { public SkyLaunch() { diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java index 3c4f42aba53e..fefcce736105 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java @@ -27,21 +27,22 @@ import org.slf4j.Logger; /** - * Superpower abstract class. In this class the basic operations of all types of - * superpowers are provided as protected methods. + * Superpower abstract class. In this class the basic operations of all types of superpowers are + * provided as protected methods. */ public abstract class Superpower { protected Logger logger; /** - * Subclass of superpower should implement this sandbox method by calling the - * methods provided in this super class. + * Subclass of superpower should implement this sandbox method by calling the methods provided in + * this super class. */ protected abstract void activate(); /** * Move to (x, y, z). + * * @param x X coordinate. * @param y Y coordinate. * @param z Z coordinate. @@ -52,6 +53,7 @@ protected void move(double x, double y, double z) { /** * Play sound effect for the superpower. + * * @param soundName Sound name. * @param volume Value of volume. */ @@ -61,6 +63,7 @@ protected void playSound(String soundName, int volume) { /** * Spawn particles for the superpower. + * * @param particleType Particle type. * @param count Count of particles to be spawned. */ diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java index e578f32ed03a..18eb36065d42 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * App unit tests. - */ +/** App unit tests. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java index 03c6b2e5c8ba..45cb4ff50b4a 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java @@ -30,9 +30,7 @@ import com.github.stefanbirkner.systemlambda.Statement; import org.junit.jupiter.api.Test; -/** - * GroundDive unit tests. - */ +/** GroundDive unit tests. */ class GroundDiveTest { @Test @@ -55,8 +53,7 @@ void testPlaySound() throws Exception { @Test void testSpawnParticles() throws Exception { var groundDive = new GroundDive(); - final var outputLog = getLogContent( - () -> groundDive.spawnParticles("PARTICLE_TYPE", 100)); + final var outputLog = getLogContent(() -> groundDive.spawnParticles("PARTICLE_TYPE", 100)); final var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; assertEquals(outputLog, expectedLog); } @@ -64,10 +61,9 @@ void testSpawnParticles() throws Exception { @Test void testActivate() throws Exception { var groundDive = new GroundDive(); - var logs = tapSystemOutNormalized(groundDive::activate) - .split("\n"); + var logs = tapSystemOutNormalized(groundDive::activate).split("\n"); final var expectedSize = 3; - final var log1 = logs[0].split("-")[1].trim() + " -" + logs[0].split("-")[2].trim(); + final var log1 = logs[0].split("--")[1].trim(); final var expectedLog1 = "Move to ( 0.0, 0.0, -20.0 )"; final var log2 = getLogContent(logs[1]); final var expectedLog2 = "Play GROUNDDIVE_SOUND with volume 5"; @@ -85,7 +81,6 @@ private String getLogContent(Statement statement) throws Exception { } private String getLogContent(String log) { - return log.split("-")[1].trim(); + return log.split("--")[1].trim(); } - } diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java index 87cee4f3c697..3af507fb0be9 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java @@ -30,9 +30,7 @@ import com.github.stefanbirkner.systemlambda.Statement; import org.junit.jupiter.api.Test; -/** - * SkyLaunch unit tests. - */ +/** SkyLaunch unit tests. */ class SkyLaunchTest { @Test @@ -54,8 +52,7 @@ void testPlaySound() throws Exception { @Test void testSpawnParticles() throws Exception { var skyLaunch = new SkyLaunch(); - var outputLog = getLogContent( - () -> skyLaunch.spawnParticles("PARTICLE_TYPE", 100)); + var outputLog = getLogContent(() -> skyLaunch.spawnParticles("PARTICLE_TYPE", 100)); var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; assertEquals(outputLog, expectedLog); } @@ -63,8 +60,7 @@ void testSpawnParticles() throws Exception { @Test void testActivate() throws Exception { var skyLaunch = new SkyLaunch(); - var logs = tapSystemOutNormalized(skyLaunch::activate) - .split("\n"); + var logs = tapSystemOutNormalized(skyLaunch::activate).split("\n"); final var expectedSize = 3; final var log1 = getLogContent(logs[0]); final var expectedLog1 = "Move to ( 0.0, 0.0, 20.0 )"; @@ -84,6 +80,6 @@ private String getLogContent(Statement statement) throws Exception { } private String getLogContent(String log) { - return log.split("-")[1].trim(); + return log.split("--")[1].trim(); } } diff --git a/table-inheritance/README.md b/table-inheritance/README.md new file mode 100644 index 000000000000..3e3ad4f53bf9 --- /dev/null +++ b/table-inheritance/README.md @@ -0,0 +1,201 @@ +--- +title: "Table Inheritance Pattern in Java: Modeling Hierarchical Data in Relational Databases" +shortTitle: Table Inheritance +description: "Explore the Table Inheritance pattern in Java with real-world examples, database schema, and tutorials. Learn how to model class hierarchies elegantly in relational databases." +category: Data Access Pattern, Structural Pattern +language: en +tag: +- Decoupling +- Inheritance +- Polymorphism +- Object Mapping +- Persistence +- Data Transformation +--- + +## Also Known As +- Class Table Inheritance +--- + +## Intent of Table Inheritance Pattern +The Table Inheritance pattern models a class hierarchy in a relational database by creating +separate tables for each class in the hierarchy. These tables share a common primary key, which in +subclass tables also serves as a foreign key referencing the primary key of the base class table. +This linkage maintains relationships and effectively represents the inheritance structure. This pattern +enables the organization of complex data models, particularly when subclasses have unique properties +that must be stored in distinct tables. + +--- + +## Detailed Explanation of Table Inheritance Pattern with Real-World Examples + +### Real-World Example +Consider a **Vehicle Management System** with a `Vehicle` superclass and subclasses like `Car` and `Truck`. + +- The **Vehicle Table** stores attributes common to all vehicles, such as `make`, `model`, and `year`. Its primary key (`id`) uniquely identifies each vehicle. +- The **Car Table** and **Truck Table** store attributes specific to their respective types, such as `numberOfDoors` for cars and `payloadCapacity` for trucks. +- The `id` column in the **Car Table** and **Truck Table** serves as both the primary key for those tables and a foreign key referencing the `id` in the **Vehicle Table**. + +This setup ensures each subclass entry corresponds to a base class entry, maintaining the inheritance relationship while keeping subclass-specific data in their own tables. + +### In Plain Words +In table inheritance, each class in the hierarchy is represented by a separate table, which +allows for a clear distinction between shared attributes (stored in the base class table) and +specific attributes (stored in subclass tables). + +### Martin Fowler Says + +Relational databases don't support inheritance, which creates a mismatch when mapping objects. +To fix this, Table Inheritance uses a separate table for each class in the hierarchy while maintaining +relationships through foreign keys, making it easier to link the classes together in the database. + +For more detailed information, refer to Martin Fowler's article on [Class Table Inheritance](https://martinfowler.com/eaaCatalog/classTableInheritance.html). + + +## Programmatic Example of Table Inheritance Pattern in Java + + +The `Vehicle` class will be the superclass, and we will have `Car` and `Truck` as subclasses that extend +`Vehicle`. The `Vehicle` class will store common attributes, while `Car` and `Truck` will store +attributes specific to those subclasses. + +### Key Aspects of the Pattern: + +1. **Superclass (`Vehicle`)**: + The `Vehicle` class stores attributes shared by all vehicle types, such as: + - `make`: The manufacturer of the vehicle. + - `model`: The model of the vehicle. + - `year`: The year the vehicle was manufactured. + - `id`: A unique identifier for the vehicle. + + These attributes are stored in the **`Vehicle` table** in the database. + +2. **Subclass (`Car` and `Truck`)**: + Each subclass (`Car` and `Truck`) stores attributes specific to that vehicle type: + - `Car`: Has an additional attribute `numberOfDoors` representing the number of doors the car has. + - `Truck`: Has an additional attribute `payloadCapacity` representing the payload capacity of the truck. + + These subclass-specific attributes are stored in the **`Car` and `Truck` tables**. + +3. **Foreign Key Relationship**: + Each subclass (`Car` and `Truck`) contains the `id` field which acts as a **foreign key** that +references the primary key (`id`) of the superclass (`Vehicle`). This foreign key ensures the +relationship between the common attributes in the `Vehicle` table and the specific attributes in the +subclass tables (`Car` and `Truck`). + + +```java +/** + * Superclass + * Represents a generic vehicle with basic attributes like make, model, year, and ID. + */ +public class Vehicle { + private String make; + private String model; + private int year; + private int id; + + // Constructor, getters, and setters... +} + +/** + * Represents a car, which is a subclass of Vehicle. + */ +public class Car extends Vehicle { + private int numberOfDoors; + + // Constructor, getters, and setters... +} + +/** + * Represents a truck, which is a subclass of Vehicle. + */ +public class Truck extends Vehicle { + private int payloadCapacity; + + // Constructor, getters, and setters... +} +``` + + + +## Table Inheritance Pattern Class Diagram + + + + + + + + + +## Table Inheritance Pattern Database Schema + +### Vehicle Table +| Column | Description | +|--------|-------------------------------------| +| id | Primary key | +| make | The make of the vehicle | +| model | The model of the vehicle | +| year | The manufacturing year of the vehicle | + +### Car Table +| Column | Description | +|------------------|-------------------------------------| +| id | Foreign key referencing `Vehicle(id)` | +| numberOfDoors | Number of doors in the car | + +### Truck Table +| Column | Description | +|-------------------|-------------------------------------| +| id | Foreign key referencing `Vehicle(id)` | +| payloadCapacity | Payload capacity of the truck | + +--- + +## When to Use the Table Inheritance Pattern in Java + +- When your application requires a clear mapping of an object-oriented class hierarchy to relational tables. +- When subclasses have unique attributes that do not fit into a single base table. +- When scalability and normalization of data are important considerations. +- When you need to separate concerns and organize data in a way that each subclass has its own +table but maintains relationships with the superclass. + +## Table Inheritance Pattern Java Tutorials + +- [Software Patterns Lexicon: Class Table Inheritance](https://softwarepatternslexicon.com/patterns-sql/4/4/2/) +- [Martin Fowler: Class Table Inheritance](http://thierryroussel.free.fr/java/books/martinfowler/www.martinfowler.com/isa/classTableInheritance.html) + +--- + +## Real-World Applications of Table Inheritance Pattern in Java + +- **Vehicle Management System**: Used to store different types of vehicles like Car and Truck in separate tables but maintain a relationship through a common superclass `Vehicle`. +- **E-Commerce Platforms**: Where different product types, such as Clothing, Electronics, and Furniture, are stored in separate tables with shared attributes in a superclass `Product`. + +## Benefits and Trade-offs of Table Inheritance Pattern + +### Benefits + +- **Clear Structure**: Each class has its own table, making the data model easier to maintain and understand. +- **Scalability**: Each subclass can be extended independently without affecting the other tables, making the system more scalable. +- **Data Normalization**: Helps avoid data redundancy and keeps the schema normalized. + +### Trade-offs + +- **Multiple Joins**: Retrieving data that spans multiple subclasses may require joining multiple tables, which could lead to performance issues. +- **Increased Complexity**: Managing relationships between tables and maintaining integrity can become more complex. +- **Potential for Sparse Tables**: Subclasses with fewer attributes may end up with tables that have many null fields. + +## Related Java Design Patterns + +- **Single Table Inheritance** – A strategy where a single table is used to store all classes in an +inheritance hierarchy. It stores all attributes of the class and its subclasses in one table. +- **Singleton Pattern** – Used when a class needs to have only one instance. + + +## References and Credits + +- **Martin Fowler** - [*Patterns of Enterprise Application Architecture*](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) +- **Java Persistence with Hibernate** - [Link to book](https://www.amazon.com/Java-Persistence-Hibernate-Christian-Bauer/dp/193239469X) +- **Object-Relational Mapping on Wikipedia** - [Link to article](https://en.wikipedia.org/wiki/Object-relational_mapping) diff --git a/table-inheritance/etc/table-inheritance.urm.puml b/table-inheritance/etc/table-inheritance.urm.puml new file mode 100644 index 000000000000..966fdc8d8e5e --- /dev/null +++ b/table-inheritance/etc/table-inheritance.urm.puml @@ -0,0 +1,52 @@ +@startuml +package com.iluwatar.table.inheritance { + class App { + + App() + + main(args : String[]) {static} + } + class Car { + - numDoors : int + + Car(year : int, make : String, model : String, numDoors : int, id : int) + + getNumDoors() : int + + setNumDoors(doors : int) + + toString() : String + } + class Truck { + - loadCapacity : double + + Truck(year : int, make : String, model : String, loadCapacity : double, id : int) + + getLoadCapacity() : double + + setLoadCapacity(capacity : double) + + toString() : String + } + class Vehicle { + - id : int + - make : String + - model : String + - year : int + + Vehicle(year : int, make : String, model : String, id : int) + + getId() : int + + getMake() : String + + getModel() : String + + getYear() : int + + setId(id : int) + + setMake(make : String) + + setModel(model : String) + + setYear(year : int) + + toString() : String + } + class VehicleDatabase { + - carTable : Map + ~ logger : Logger + - truckTable : Map + - vehicleTable : Map + + VehicleDatabase() + + getCar(id : int) : Car + + getTruck(id : int) : Truck + + getVehicle(id : int) : Vehicle + + printAllVehicles() + + saveVehicle(vehicle : Vehicle) + } +} +Car --|> Vehicle +Truck --|> Vehicle +@enduml \ No newline at end of file diff --git a/table-inheritance/pom.xml b/table-inheritance/pom.xml new file mode 100644 index 000000000000..883fb8d542b3 --- /dev/null +++ b/table-inheritance/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + table-inheritance + + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.projectlombok + lombok + 1.18.36 + provided + + + + + + \ No newline at end of file diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java new file mode 100644 index 000000000000..da097ab9edec --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import java.util.logging.Logger; + +/** + * The main entry point of the application demonstrating the use of vehicles. + * + *

      The Table Inheritance pattern models a class hierarchy in a relational database by creating + * separate tables for each class in the hierarchy. These tables share a common primary key, which + * in subclass tables also serves as a foreign key referencing the primary key of the base class + * table. This linkage maintains relationships and effectively represents the inheritance structure. + * This pattern enables the organization of complex data models, particularly when subclasses have + * unique properties that must be stored in distinct tables. + */ +public class App { + /** + * Manages the storage and retrieval of Vehicle objects, including Cars and Trucks. + * + *

      This example demonstrates the **Table Inheritance** pattern, where each vehicle type (Car + * and Truck) is stored in its own separate table. The `VehicleDatabase` simulates a simple + * database that manages these entities, with each subclass (Car and Truck) being stored in its + * respective table. + * + *

      The `VehicleDatabase` contains the following tables: - `vehicleTable`: Stores all vehicle + * objects, including both `Car` and `Truck` objects. - `carTable`: Stores only `Car` objects, + * with fields specific to cars. - `truckTable`: Stores only `Truck` objects, with fields specific + * to trucks. + * + *

      The example demonstrates: 1. Saving instances of `Car` and `Truck` to their respective + * tables in the database. 2. Retrieving vehicles (both cars and trucks) from the appropriate + * table based on their ID. 3. Printing all vehicles stored in the database. 4. Showing how to + * retrieve specific types of vehicles (`Car` or `Truck`) by their IDs. + * + *

      In the **Table Inheritance** pattern, each subclass has its own table, making it easier to + * manage specific attributes of each subclass. + * + * @param args command-line arguments + */ + public static void main(String[] args) { + + final Logger logger = Logger.getLogger(App.class.getName()); + + VehicleDatabase database = new VehicleDatabase(); + + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + + database.saveVehicle(car); + database.saveVehicle(truck); + + database.printAllVehicles(); + + Vehicle vehicle = database.getVehicle(car.getId()); + Car retrievedCar = database.getCar(car.getId()); + Truck retrievedTruck = database.getTruck(truck.getId()); + + logger.info(String.format("Retrieved Vehicle: %s", vehicle)); + logger.info(String.format("Retrieved Car: %s", retrievedCar)); + logger.info(String.format("Retrieved Truck: %s", retrievedTruck)); + } +} diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java new file mode 100644 index 000000000000..0adaaa648b51 --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java @@ -0,0 +1,80 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import lombok.Getter; + +/** Represents a car with a specific number of doors. */ +@Getter +public class Car extends Vehicle { + private int numDoors; + + /** + * Constructs a Car object. + * + * @param year the manufacturing year + * @param make the make of the car + * @param model the model of the car + * @param numDoors the number of doors + * @param id the unique identifier for the car + */ + public Car(int year, String make, String model, int numDoors, int id) { + super(year, make, model, id); + if (numDoors <= 0) { + throw new IllegalArgumentException("Number of doors must be positive."); + } + this.numDoors = numDoors; + } + + /** + * Sets the number of doors for the car. + * + * @param doors the number of doors + */ + public void setNumDoors(int doors) { + if (doors <= 0) { + throw new IllegalArgumentException("Number of doors must be positive."); + } + this.numDoors = doors; + } + + @Override + public String toString() { + return "Car{" + + "id=" + + getId() + + ", make='" + + getMake() + + '\'' + + ", model='" + + getModel() + + '\'' + + ", year=" + + getYear() + + ", numberOfDoors=" + + getNumDoors() + + '}'; + } +} diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java new file mode 100644 index 000000000000..b79c5362215b --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import lombok.Getter; + +/** Represents a truck, a type of vehicle with a specific load capacity. */ +@Getter +public class Truck extends Vehicle { + private double loadCapacity; + + /** + * Constructs a Truck object with the given parameters. + * + * @param year the year of manufacture + * @param make the make of the truck + * @param model the model of the truck + * @param loadCapacity the load capacity of the truck + * @param id the unique ID of the truck + */ + public Truck(int year, String make, String model, double loadCapacity, int id) { + super(year, make, model, id); + if (loadCapacity <= 0) { + throw new IllegalArgumentException("Load capacity must be positive."); + } + this.loadCapacity = loadCapacity; + } + + /** + * Sets the load capacity of the truck. + * + * @param capacity the new load capacity + */ + public void setLoadCapacity(double capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Load capacity must be positive."); + } + this.loadCapacity = capacity; + } + + /** + * Returns a string representation of the truck. + * + * @return a string with the truck's details + */ + @Override + public String toString() { + return "Truck{" + + "id=" + + getId() + + ", make='" + + getMake() + + '\'' + + ", model='" + + getModel() + + '\'' + + ", year=" + + getYear() + + ", payloadCapacity=" + + getLoadCapacity() + + '}'; + } +} diff --git a/embedded-value/src/main/java/com/iluwatar/embedded/value/Order.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java similarity index 59% rename from embedded-value/src/main/java/com/iluwatar/embedded/value/Order.java rename to table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java index c4984dd4cc53..87db4092d3a6 100644 --- a/embedded-value/src/main/java/com/iluwatar/embedded/value/Order.java +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java @@ -22,41 +22,54 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.embedded.value; +package com.iluwatar.table.inheritance; import lombok.Getter; import lombok.Setter; -import lombok.ToString; -/** - * A POJO which represents the Order object. - */ -@ToString +/** Represents a generic vehicle with basic attributes like make, model, year, and ID. */ @Setter @Getter -public class Order { - +public class Vehicle { + + private String make; + private String model; + private int year; private int id; - private String item; - private String orderedBy; - private ShippingAddress shippingAddress; - + /** - * Constructor for Item object. - * @param item item name - * @param orderedBy item orderer - * @param shippingAddress shipping address details + * Constructs a Vehicle object with the given parameters. + * + * @param year the year of manufacture + * @param make the make of the vehicle + * @param model the model of the vehicle + * @param id the unique ID of the vehicle */ - - public Order(String item, String orderedBy, ShippingAddress shippingAddress) { - this.item = item; - this.orderedBy = orderedBy; - this.shippingAddress = shippingAddress; + public Vehicle(int year, String make, String model, int id) { + this.make = make; + this.model = model; + this.year = year; + this.id = id; } - public Order(int id, String item, String orderedBy, ShippingAddress shippingAddress) { - this(item, orderedBy, shippingAddress); - this.id = id; + /** + * Returns a string representation of the vehicle. + * + * @return a string with the vehicle's details + */ + @Override + public String toString() { + return "Vehicle{" + + "id=" + + id + + ", make='" + + make + + '\'' + + ", model='" + + model + + '\'' + + ", year=" + + year + + '}'; } - } diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java new file mode 100644 index 000000000000..0f04ab0a959e --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java @@ -0,0 +1,91 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** Manages the storage and retrieval of Vehicle objects, including Cars and Trucks. */ +public class VehicleDatabase { + + final Logger logger = Logger.getLogger(VehicleDatabase.class.getName()); + + private Map vehicleTable = new HashMap<>(); + private Map carTable = new HashMap<>(); + private Map truckTable = new HashMap<>(); + + /** + * Saves a vehicle to the database. If the vehicle is a Car or Truck, it is added to the + * respective table. + * + * @param vehicle the vehicle to save + */ + public void saveVehicle(Vehicle vehicle) { + vehicleTable.put(vehicle.getId(), vehicle); + if (vehicle instanceof Car) { + carTable.put(vehicle.getId(), (Car) vehicle); + } else if (vehicle instanceof Truck) { + truckTable.put(vehicle.getId(), (Truck) vehicle); + } + } + + /** + * Retrieves a vehicle by its ID. + * + * @param id the ID of the vehicle + * @return the vehicle with the given ID, or null if not found + */ + public Vehicle getVehicle(int id) { + return vehicleTable.get(id); + } + + /** + * Retrieves a car by its ID. + * + * @param id the ID of the car + * @return the car with the given ID, or null if not found + */ + public Car getCar(int id) { + return carTable.get(id); + } + + /** + * Retrieves a truck by its ID. + * + * @param id the ID of the truck + * @return the truck with the given ID, or null if not found + */ + public Truck getTruck(int id) { + return truckTable.get(id); + } + + /** Prints all vehicles in the database. */ + public void printAllVehicles() { + for (Vehicle vehicle : vehicleTable.values()) { + logger.info(vehicle.toString()); + } + } +} diff --git a/table-inheritance/src/test/java/AppTest.java b/table-inheritance/src/test/java/AppTest.java new file mode 100644 index 000000000000..1d8e271423f7 --- /dev/null +++ b/table-inheritance/src/test/java/AppTest.java @@ -0,0 +1,68 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.table.inheritance.App; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Logger; +import org.junit.jupiter.api.Test; + +/** Tests if the main method runs without throwing exceptions and prints expected output. */ +class AppTest { + + @Test + void testAppMainMethod() { + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outContent); + + System.setOut(printStream); + + Logger logger = Logger.getLogger(App.class.getName()); + + Handler handler = + new ConsoleHandler() { + @Override + public void publish(java.util.logging.LogRecord recordObj) { + printStream.println(getFormatter().format(recordObj)); + } + }; + handler.setLevel(java.util.logging.Level.ALL); + logger.addHandler(handler); + + App.main(new String[] {}); + + String output = outContent.toString(); + + assertTrue(output.contains("Retrieved Vehicle:")); + assertTrue(output.contains("Toyota")); // Car make + assertTrue(output.contains("Ford")); // Truck make + assertTrue(output.contains("Retrieved Car:")); + assertTrue(output.contains("Retrieved Truck:")); + } +} diff --git a/table-inheritance/src/test/java/VehicleDatabaseTest.java b/table-inheritance/src/test/java/VehicleDatabaseTest.java new file mode 100644 index 000000000000..71461f990a76 --- /dev/null +++ b/table-inheritance/src/test/java/VehicleDatabaseTest.java @@ -0,0 +1,187 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.iluwatar.table.inheritance.Car; +import com.iluwatar.table.inheritance.Truck; +import com.iluwatar.table.inheritance.Vehicle; +import com.iluwatar.table.inheritance.VehicleDatabase; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the {@link VehicleDatabase} class. Tests saving, retrieving, and printing vehicles + * of different types. + */ +class VehicleDatabaseTest { + + private VehicleDatabase vehicleDatabase; + + /** Sets up a new instance of {@link VehicleDatabase} before each test. */ + @BeforeEach + public void setUp() { + vehicleDatabase = new VehicleDatabase(); + } + + /** Tests saving a {@link Car} to the database and retrieving it. */ + @Test + void testSaveAndRetrieveCar() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + vehicleDatabase.saveVehicle(car); + + Vehicle retrievedVehicle = vehicleDatabase.getVehicle(car.getId()); + assertNotNull(retrievedVehicle); + assertEquals(car.getId(), retrievedVehicle.getId()); + assertEquals(car.getMake(), retrievedVehicle.getMake()); + assertEquals(car.getModel(), retrievedVehicle.getModel()); + assertEquals(car.getYear(), retrievedVehicle.getYear()); + + Car retrievedCar = vehicleDatabase.getCar(car.getId()); + assertNotNull(retrievedCar); + assertEquals(car.getNumDoors(), retrievedCar.getNumDoors()); + } + + /** Tests saving a {@link Truck} to the database and retrieving it. */ + @Test + void testSaveAndRetrieveTruck() { + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + vehicleDatabase.saveVehicle(truck); + + Vehicle retrievedVehicle = vehicleDatabase.getVehicle(truck.getId()); + assertNotNull(retrievedVehicle); + assertEquals(truck.getId(), retrievedVehicle.getId()); + assertEquals(truck.getMake(), retrievedVehicle.getMake()); + assertEquals(truck.getModel(), retrievedVehicle.getModel()); + assertEquals(truck.getYear(), retrievedVehicle.getYear()); + + Truck retrievedTruck = vehicleDatabase.getTruck(truck.getId()); + assertNotNull(retrievedTruck); + assertEquals(truck.getLoadCapacity(), retrievedTruck.getLoadCapacity()); + } + + /** Tests saving multiple vehicles to the database and printing them. */ + @Test + void testPrintAllVehicles() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + vehicleDatabase.saveVehicle(car); + vehicleDatabase.saveVehicle(truck); + + vehicleDatabase.printAllVehicles(); + + Vehicle retrievedCar = vehicleDatabase.getVehicle(car.getId()); + Vehicle retrievedTruck = vehicleDatabase.getVehicle(truck.getId()); + + assertNotNull(retrievedCar); + assertNotNull(retrievedTruck); + } + + /** Tests the constructor of {@link Car} with valid values. */ + @Test + void testCarConstructor() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + assertEquals(2020, car.getYear()); + assertEquals("Toyota", car.getMake()); + assertEquals("Corolla", car.getModel()); + assertEquals(4, car.getNumDoors()); + assertEquals(1, car.getId()); // Assuming the ID is auto-generated in the constructor + } + + /** Tests the constructor of {@link Car} with invalid number of doors (negative value). */ + @Test + void testCarConstructorWithInvalidNumDoors() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Car(2020, "Toyota", "Corolla", -4, 1); + }); + assertEquals("Number of doors must be positive.", exception.getMessage()); + } + + /** Tests the constructor of {@link Car} with zero doors. */ + @Test + void testCarConstructorWithZeroDoors() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Car(2020, "Toyota", "Corolla", 0, 1); + }); + assertEquals("Number of doors must be positive.", exception.getMessage()); + } + + /** Tests the constructor of {@link Truck} with invalid load capacity (negative value). */ + @Test + void testTruckConstructorWithInvalidLoadCapacity() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Truck(2018, "Ford", "F-150", -60, 2); + }); + assertEquals("Load capacity must be positive.", exception.getMessage()); + } + + /** Tests the constructor of {@link Truck} with zero load capacity. */ + @Test + void testTruckConstructorWithZeroLoadCapacity() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Truck(2018, "Ford", "F-150", 0, 2); + }); + assertEquals("Load capacity must be positive.", exception.getMessage()); + } + + /** Tests setting invalid number of doors in {@link Car} using setter (negative value). */ + @Test + void testSetInvalidNumDoors() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + car.setNumDoors(-2); + }); + assertEquals("Number of doors must be positive.", exception.getMessage()); + } + + /** Tests setting invalid load capacity in {@link Truck} using setter (negative value). */ + @Test + void testSetInvalidLoadCapacity() { + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + truck.setLoadCapacity(-10); + }); + assertEquals("Load capacity must be positive.", exception.getMessage()); + } +} diff --git a/table-module/README.md b/table-module/README.md index 9479753f771a..e69892bb7227 100644 --- a/table-module/README.md +++ b/table-module/README.md @@ -1,27 +1,36 @@ --- -title: Table Module -category: Structural +title: "Table Module Pattern in Java: Enhancing Maintainability with Organized Data Handling Modules" +shortTitle: Table Module +description: "Explore the Table Module pattern in Java with our in-depth guide. Learn how it simplifies database interaction by encapsulating data access logic, enhances code maintenance, and secures data operations." +category: Data access language: en tag: - - Data access + - Data access + - Encapsulation + - Persistence --- -## Intent -Table Module organizes domain logic with one class per table in the database, and a single instance of a class contains the various procedures that will act on the data. +## Also known as -## Explanation +* Record Set -Real world example +## Intent of Table Module Design Pattern -> When dealing with a user system, we need some operations on the user table. We can use the table module pattern in this scenario. We can create a class named UserTableModule and initialize a instance of that class to handle the business logic for all rows in the user table. +The Table Module pattern expertly encapsulates database table data access logic in a single, efficient module, ideal for Java applications. + +## Detailed Explanation of Table Module Pattern with Real-World Examples + +Real-world example + +> Imagine a library where all books are stored in a large database. The Table Module design pattern can be compared to a librarian who manages a specific section of the library, such as the fiction section. This librarian is responsible for all tasks related to that section: adding new books, updating book information, removing old books, and providing information about the books when requested. Just like the Table Module pattern encapsulates all the database access logic for a particular table, the librarian handles all operations related to the fiction section without exposing the complexities of how books are cataloged, stored, and managed to the library users. This makes it easier for the library users to interact with the fiction section, as they can rely on the librarian to efficiently manage and retrieve books without needing to understand the underlying details. In plain words -> A single instance that handles the business logic for all rows in a database table or view. +> The Table Module pattern centralizes and encapsulates database access logic for a specific table, simplifying data retrieval and manipulation while hiding database complexities. -Programmatic Example +## Programmatic Example of Table Module Pattern in Java -In the example of the user system, we need to deal with the domain logic of user login and user registration. We can use the table module pattern and create an instance of the class `UserTableModule` to handle the business logic for all rows in the user table. +In the user system example, the domain logic for user login and registration needs to be managed. By using the Table Module pattern, we can create an instance of the `UserTableModule` class to encapsulate and handle all business logic associated with the rows in the user table. Here is the basic `User` entity. @@ -51,28 +60,12 @@ public class UserTableModule { this.dataSource = userDataSource; } - /** - * Login using username and password. - * - * @param username the username of a user - * @param password the password of a user - * @return the execution result of the method - * @throws SQLException if any error - */ public int login(final String username, final String password) throws SQLException { - // Method implementation. - + // Method implementation } - /** - * Register a new user. - * - * @param user a user instance - * @return the execution result of the method - * @throws SQLException if any error - */ public int registerUser(final User user) throws SQLException { - // Method implementation. + // Method implementation } } ``` @@ -80,54 +73,116 @@ public class UserTableModule { In the class `App`, we use an instance of the `UserTableModule` to handle user login and registration. ```java -// Create data source and create the user table. -final var dataSource = createDataSource(); -createSchema(dataSource); -userTableModule = new UserTableModule(dataSource); - -//Initialize two users. -var user1 = new User(1, "123456", "123456"); -var user2 = new User(2, "test", "password"); - -//Login and register using the instance of userTableModule. -userTableModule.registerUser(user1); -userTableModule.login(user1.getUsername(), user1.getPassword()); -userTableModule.login(user2.getUsername(), user2.getPassword()); -userTableModule.registerUser(user2); -userTableModule.login(user2.getUsername(), user2.getPassword()); - -deleteSchema(dataSource); +@Slf4j +public final class App { + + private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; + + private App() {} + + public static void main(final String[] args) throws SQLException { + // Create data source and create the user table. + final var dataSource = createDataSource(); + createSchema(dataSource); + var userTableModule = new UserTableModule(dataSource); + + // Initialize two users. + var user1 = new User(1, "123456", "123456"); + var user2 = new User(2, "test", "password"); + + // Login and register using the instance of userTableModule. + userTableModule.registerUser(user1); + userTableModule.login(user1.getUsername(), user1.getPassword()); + userTableModule.login(user2.getUsername(), user2.getPassword()); + userTableModule.registerUser(user2); + userTableModule.login(user2.getUsername(), user2.getPassword()); + + deleteSchema(dataSource); + } + + private static void deleteSchema(final DataSource dataSource) + throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(UserTableModule.DELETE_SCHEMA_SQL); + } + } + + private static void createSchema(final DataSource dataSource) + throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(UserTableModule.CREATE_SCHEMA_SQL); + } + } + + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + return dataSource; + } +} ``` +In this example, the `UserTableModule` class is responsible for encapsulating all interactions with the users table in the database. This includes the logic for logging in and registering users. The User class represents the user entity with attributes such as `id`, `username`, and `password`. + +1. **Initialization**: The `UserTableModule` is initialized with a `DataSource` object which is used to establish a connection to the database. +2. **User Registration**: The `registerUser` method in `UserTableModule` handles the logic for registering a new user into the database. +3. **User Login**: The `login` method manages the logic for authenticating a user based on their username and password. +4. **Application Flow**: The `App` class demonstrates how to use the `UserTableModule` to register and log in users. The data source is created, the schema is set up, and users are registered and logged in with appropriate feedback. + The program output: -```java -12:22:13.095 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully! -12:22:13.117 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully! -12:22:13.128 [main] INFO com.iluwatar.tablemodule.UserTableModule - Fail to login! -12:22:13.136 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully! -12:22:13.144 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully! ``` +13:59:36.417 [main] INFO com.iluwatar.tablemodule.UserTableModule -- Register successfully! +13:59:36.426 [main] INFO com.iluwatar.tablemodule.UserTableModule -- Login successfully! +13:59:36.426 [main] INFO com.iluwatar.tablemodule.UserTableModule -- Fail to login! +13:59:36.426 [main] INFO com.iluwatar.tablemodule.UserTableModule -- Register successfully! +13:59:36.427 [main] INFO com.iluwatar.tablemodule.UserTableModule -- Login successfully! +``` + +This example shows how the Table Module pattern centralizes database operations for the `users` table, making the application more modular and easier to maintain. + +## When to Use the Table Module Pattern in Java + +* Use when you need to manage data access logic for a database table in a centralized module. +* Ideal for applications that interact heavily with database tables and require encapsulation of database queries. +* Particularly suitable for dynamic systems, the Table Module pattern ensures scalable database management as your Java application's schema evolves. + +## Table Module Pattern Java Tutorials + +* [Architecture patterns: Domain model and friends (Inviqa)](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends) + +## Real-World Applications of Table Module Pattern in Java -## Class diagram +* In enterprise applications where multiple modules need to interact with the same database tables. +* Web applications that require CRUD operations on database tables. +* Java-based ORM frameworks such as Hibernate or JPA utilize similar concepts for managing data access. -![](./etc/table-module.urm.png "table module") +## Benefits and Trade-offs of Table Module Pattern -## Applicability +Benefits: -Use the Table Module Pattern when +* Centralizes and encapsulates data access logic, leading to easier maintenance. +* Reduces code duplication by providing a single point of interaction for database tables. +* Enhances code readability and reduces the risk of SQL injection attacks by encapsulating query logic. -- Domain logic is simple and data is in tabular form. -- The application only uses a few shared common table-oriented data structures. +Trade-offs: -## Related patterns +* May lead to a large module if the table has many operations, potentially reducing readability. +* Can become a bottleneck if not properly optimized, especially in high-load scenarios. -- [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/) +## Related Java Design Patterns -- [Domain Model](https://java-design-patterns.com/patterns/domain-model/) +* Active Record: Unlike Table Module, Active Record combines data access and domain logic in the same class. +* [Data Access Object (DAO)](https://java-design-patterns.com/patterns/dao/): Provides an abstract interface to some type of database or other persistence mechanism, often used alongside Table Module to separate low-level data access operations from high-level business logic. +* [Data Mapper](https://java-design-patterns.com/patterns/data-mapper/): Separates the in-memory objects from the database, unlike Table Module which directly maps database tables. +* [Domain Model](https://java-design-patterns.com/patterns/domain-model/): Represents the domain logic and behavior, often used in conjunction with Table Module to handle complex business rules and data interactions. +* [Repository](https://java-design-patterns.com/patterns/repository/): Abstracts the data layer, allowing more complex queries, whereas Table Module is usually simpler and table-centric. +* [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/): Organizes business logic by procedures where each procedure handles a single request from the presentation layer, contrasting with the Table Module's data-centric approach. -## Credits +## References and Credits -* [Table Module Pattern](http://wiki3.cosc.canterbury.ac.nz/index.php/Table_module_pattern) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04) -* [Architecture patterns: domain model and friends](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends) \ No newline at end of file +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/table-module/pom.xml b/table-module/pom.xml index f931a8701af4..9e06a4d8c1bf 100644 --- a/table-module/pom.xml +++ b/table-module/pom.xml @@ -34,6 +34,14 @@ 4.0.0 table-module + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.h2database h2 diff --git a/table-module/src/main/java/com/iluwatar/tablemodule/App.java b/table-module/src/main/java/com/iluwatar/tablemodule/App.java index 1c528df8a7f5..4d222e852ad9 100644 --- a/table-module/src/main/java/com/iluwatar/tablemodule/App.java +++ b/table-module/src/main/java/com/iluwatar/tablemodule/App.java @@ -29,30 +29,22 @@ import lombok.extern.slf4j.Slf4j; import org.h2.jdbcx.JdbcDataSource; - /** - * Table Module pattern is a domain logic pattern. - * In Table Module a single class encapsulates all the domain logic for all - * records stored in a table or view. It's important to note that there is no - * translation of data between objects and rows, as it happens in Domain Model, - * hence implementation is relatively simple when compared to the Domain - * Model pattern. + * Table Module pattern is a domain logic pattern. In Table Module a single class encapsulates all + * the domain logic for all records stored in a table or view. It's important to note that there is + * no translation of data between objects and rows, as it happens in Domain Model, hence + * implementation is relatively simple when compared to the Domain Model pattern. * - *

      In this example we will use the Table Module pattern to implement register - * and login methods for the records stored in the user table. The main - * method will initialise an instance of {@link UserTableModule} and use it to - * handle the domain logic for the user table.

      + *

      In this example we will use the Table Module pattern to implement register and login methods + * for the records stored in the user table. The main method will initialise an instance of {@link + * UserTableModule} and use it to handle the domain logic for the user table. */ @Slf4j public final class App { private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; - /** - * Private constructor. - */ - private App() { - - } + /** Private constructor. */ + private App() {} /** * Program entry point. @@ -80,18 +72,16 @@ public static void main(final String[] args) throws SQLException { deleteSchema(dataSource); } - private static void deleteSchema(final DataSource dataSource) - throws SQLException { + private static void deleteSchema(final DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.DELETE_SCHEMA_SQL); } } - private static void createSchema(final DataSource dataSource) - throws SQLException { + private static void createSchema(final DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.CREATE_SCHEMA_SQL); } } diff --git a/table-module/src/main/java/com/iluwatar/tablemodule/User.java b/table-module/src/main/java/com/iluwatar/tablemodule/User.java index 1acdc93dbd7b..4af0f73dcee1 100644 --- a/table-module/src/main/java/com/iluwatar/tablemodule/User.java +++ b/table-module/src/main/java/com/iluwatar/tablemodule/User.java @@ -30,10 +30,7 @@ import lombok.Setter; import lombok.ToString; - -/** - * A user POJO that represents the data that will be read from the data source. - */ +/** A user POJO that represents the data that will be read from the data source. */ @Setter @Getter @ToString @@ -43,5 +40,4 @@ public class User { private int id; private String username; private String password; - } diff --git a/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java b/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java index 92ffb6db6766..ea0b58fc3402 100644 --- a/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java +++ b/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java @@ -29,26 +29,21 @@ import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; - /** - * This class organizes domain logic with the user table in the - * database. A single instance of this class contains the various - * procedures that will act on the data. + * This class organizes domain logic with the user table in the database. A single instance of this + * class contains the various procedures that will act on the data. */ @Slf4j public class UserTableModule { - /** - * Public element for creating schema. - */ + /** Public element for creating schema. */ public static final String CREATE_SCHEMA_SQL = - "CREATE TABLE IF NOT EXISTS USERS (ID NUMBER, USERNAME VARCHAR(30) " - + "UNIQUE,PASSWORD VARCHAR(30))"; - /** - * Public element for deleting schema. - */ + "CREATE TABLE IF NOT EXISTS USERS (ID NUMBER, USERNAME VARCHAR(30) " + + "UNIQUE,PASSWORD VARCHAR(30))"; + + /** Public element for deleting schema. */ public static final String DELETE_SCHEMA_SQL = "DROP TABLE USERS IF EXISTS"; - private final DataSource dataSource; + private final DataSource dataSource; /** * Public constructor. @@ -59,7 +54,6 @@ public UserTableModule(final DataSource userDataSource) { this.dataSource = userDataSource; } - /** * Login using username and password. * @@ -68,14 +62,11 @@ public UserTableModule(final DataSource userDataSource) { * @return the execution result of the method * @throws SQLException if any error */ - public int login(final String username, final String password) - throws SQLException { + public int login(final String username, final String password) throws SQLException { var sql = "select count(*) from USERS where username=? and password=?"; ResultSet resultSet = null; try (var connection = dataSource.getConnection(); - var preparedStatement = - connection.prepareStatement(sql) - ) { + var preparedStatement = connection.prepareStatement(sql)) { var result = 0; preparedStatement.setString(1, username); preparedStatement.setString(2, password); @@ -106,9 +97,7 @@ public int login(final String username, final String password) public int registerUser(final User user) throws SQLException { var sql = "insert into USERS (username, password) values (?,?)"; try (var connection = dataSource.getConnection(); - var preparedStatement = - connection.prepareStatement(sql) - ) { + var preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setString(1, user.getUsername()); preparedStatement.setString(2, user.getPassword()); var result = preparedStatement.executeUpdate(); diff --git a/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java b/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java index d401d36ca8f2..f17cd4722e6a 100644 --- a/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java +++ b/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.tablemodule; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that the table module example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that the table module example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java b/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java index f45c20176700..f6b31a8589bb 100644 --- a/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java +++ b/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java @@ -24,16 +24,16 @@ */ package com.iluwatar.tablemodule; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.sql.DriverManager; +import java.sql.SQLException; +import javax.sql.DataSource; import org.h2.jdbcx.JdbcDataSource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.sql.DataSource; -import java.sql.DriverManager; -import java.sql.SQLException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; class UserTableModuleTest { private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; @@ -47,7 +47,7 @@ private static DataSource createDataSource() { @BeforeEach void setUp() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.DELETE_SCHEMA_SQL); statement.execute(UserTableModule.CREATE_SCHEMA_SQL); } @@ -56,7 +56,7 @@ void setUp() throws SQLException { @AfterEach void tearDown() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.DELETE_SCHEMA_SQL); } } @@ -66,8 +66,7 @@ void loginShouldFail() throws SQLException { var dataSource = createDataSource(); var userTableModule = new UserTableModule(dataSource); var user = new User(1, "123456", "123456"); - assertEquals(0, userTableModule.login(user.getUsername(), - user.getPassword())); + assertEquals(0, userTableModule.login(user.getUsername(), user.getPassword())); } @Test @@ -76,8 +75,7 @@ void loginShouldSucceed() throws SQLException { var userTableModule = new UserTableModule(dataSource); var user = new User(1, "123456", "123456"); userTableModule.registerUser(user); - assertEquals(1, userTableModule.login(user.getUsername(), - user.getPassword())); + assertEquals(1, userTableModule.login(user.getUsername(), user.getPassword())); } @Test @@ -86,9 +84,7 @@ void registerShouldFail() throws SQLException { var userTableModule = new UserTableModule(dataSource); var user = new User(1, "123456", "123456"); userTableModule.registerUser(user); - assertThrows(SQLException.class, () -> { - userTableModule.registerUser(user); - }); + assertThrows(SQLException.class, () -> userTableModule.registerUser(user)); } @Test @@ -98,4 +94,4 @@ void registerShouldSucceed() throws SQLException { var user = new User(1, "123456", "123456"); assertEquals(1, userTableModule.registerUser(user)); } -} \ No newline at end of file +} diff --git a/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java b/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java index 458acabc6a54..ca827f3a97a0 100644 --- a/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java +++ b/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java @@ -24,103 +24,89 @@ */ package com.iluwatar.tablemodule; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + class UserTest { @Test void testCanEqual() { - assertFalse((new User(1, "janedoe", "iloveyou")) - .canEqual("Other")); + assertFalse((new User(1, "janedoe", "iloveyou")).canEqual("Other")); } @Test void testCanEqual2() { var user = new User(1, "janedoe", "iloveyou"); - assertTrue(user.canEqual(new User(1, "janedoe", - "iloveyou"))); + assertTrue(user.canEqual(new User(1, "janedoe", "iloveyou"))); } @Test void testEquals1() { var user = new User(1, "janedoe", "iloveyou"); - assertNotEquals("42", user); + assertNotEquals(user, new User(123, "abcd", "qwerty")); } @Test void testEquals2() { var user = new User(1, "janedoe", "iloveyou"); - assertEquals(user, new User(1, "janedoe", - "iloveyou")); + assertEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals3() { var user = new User(123, "janedoe", "iloveyou"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals4() { var user = new User(1, null, "iloveyou"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals5() { var user = new User(1, "iloveyou", "iloveyou"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals6() { var user = new User(1, "janedoe", "janedoe"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals7() { var user = new User(1, "janedoe", null); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals8() { var user = new User(1, null, "iloveyou"); - assertEquals(user, new User(1, null, - "iloveyou")); + assertEquals(user, new User(1, null, "iloveyou")); } @Test void testEquals9() { var user = new User(1, "janedoe", null); - assertEquals(user, new User(1, "janedoe", - null)); + assertEquals(user, new User(1, "janedoe", null)); } @Test void testHashCode1() { - assertEquals(-1758941372, (new User(1, "janedoe", - "iloveyou")).hashCode()); - + assertEquals(-1758941372, (new User(1, "janedoe", "iloveyou")).hashCode()); } @Test void testHashCode2() { - assertEquals(-1332207447, (new User(1, null, - "iloveyou")).hashCode()); + assertEquals(-1332207447, (new User(1, null, "iloveyou")).hashCode()); } @Test void testHashCode3() { - assertEquals(-426522485, (new User(1, "janedoe", - null)).hashCode()); + assertEquals(-426522485, (new User(1, "janedoe", null)).hashCode()); } @Test @@ -147,9 +133,10 @@ void testSetUsername() { @Test void testToString() { var user = new User(1, "janedoe", "iloveyou"); - assertEquals(String.format("User(id=%s, username=%s, password=%s)", + assertEquals( + String.format( + "User(id=%s, username=%s, password=%s)", user.getId(), user.getUsername(), user.getPassword()), - user.toString()); + user.toString()); } } - diff --git a/template-method/README.md b/template-method/README.md index f64617d2a147..a05d77ff03be 100644 --- a/template-method/README.md +++ b/template-method/README.md @@ -1,45 +1,44 @@ --- -title: Template method +title: "Template Method Pattern in Java: Streamlining Complex Algorithms with Predefined Scaffolds" +shortTitle: Template Method +description: "Discover the essentials of the Template Method pattern in Java, including how it simplifies code, promotes reusability, and allows flexibility in algorithm design. Perfect for developers looking to refine their object-oriented programming skills." category: Behavioral language: en tag: - - Gang of Four + - Abstraction + - Code simplification + - Decoupling + - Extensibility + - Gang of Four + - Inheritance + - Object composition + - Polymorphism + - Reusability --- -## Intent +## Intent of Template method Design Pattern -Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template -Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's -structure. +Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. -## Explanation +## Detailed Explanation of Template method Pattern with Real-World Examples Real-world example -> The general steps in stealing an item are the same. First, you pick the target, next you confuse -> him somehow and finally, you steal the item. However, there are many ways to implement these -> steps. +> A real-world analogy for the Template Method pattern can be seen in the preparation of a cup of tea or coffee. The overall process (algorithm) is the same: boil water, brew the beverage, pour into cup, and add condiments. However, the specific steps of brewing the beverage differ. For tea, you steep the tea leaves in hot water, while for coffee, you brew ground coffee beans. The Template Method pattern encapsulates the invariant steps of the process (boiling water, pouring, adding condiments) in a base class, while allowing subclasses to define the specific brewing steps, thus ensuring the overall structure of making a hot drink is consistent while allowing customization where needed. In plain words -> Template Method pattern outlines the general steps in the parent class and lets the concrete child -> implementations define the details. +> The Java Template Method pattern outlines the core steps in the parent class, allowing child classes to tailor detailed implementations, enhancing code reusability and design flexibility in Java programming. Wikipedia says -> In object-oriented programming, the template method is one of the behavioral design patterns -> identified by Gamma et al. in the book Design Patterns. The template method is a method in a -> superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of -> a number of high-level steps. These steps are themselves implemented by additional helper methods -> in the same class as the template method. +> In object-oriented programming, the template method is one of the behavioral design patterns identified by Gamma et al. in the book Design Patterns. The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method. -**Programmatic Example** +## Programmatic Example of Template Method Pattern in Java -Let's first introduce the template method class along with its concrete implementations. -To make sure that subclasses don’t override the template method, the template method (in our case -method `steal`) should be declared `final`, otherwise the skeleton defined in the base class could -be overridden in subclasses. +Our programmatic example is about thieves and stealing. The general steps in stealing an item are the same. First, you pick the target, next you confuse him somehow and finally, you steal the item. However, there are many ways to implement these steps. +Let's first introduce the template method class `StealingMethod` along with its concrete implementations `SubtleMethod` and `HitAndRunMethod`. To make sure that subclasses don’t override the template method, the template method (in our case method `steal`) should be declared `final`, otherwise the skeleton defined in the base class could be overridden in subclasses. ```java @Slf4j @@ -58,7 +57,9 @@ public abstract class StealingMethod { stealTheItem(target); } } +``` +```java @Slf4j public class SubtleMethod extends StealingMethod { @@ -77,7 +78,9 @@ public class SubtleMethod extends StealingMethod { LOGGER.info("While in close contact grab the {}'s wallet.", target); } } +``` +```java @Slf4j public class HitAndRunMethod extends StealingMethod { @@ -122,17 +125,26 @@ public class HalflingThief { And finally, we show how the halfling thief utilizes the different stealing methods. ```java +public static void main(String[] args) { var thief = new HalflingThief(new HitAndRunMethod()); thief.steal(); thief.changeMethod(new SubtleMethod()); thief.steal(); +} ``` -## Class diagram +The program output: -![alt text](./etc/template_method_urm.png "Template Method") +``` +11:06:01.721 [main] INFO com.iluwatar.templatemethod.StealingMethod -- The target has been chosen as old goblin woman. +11:06:01.723 [main] INFO com.iluwatar.templatemethod.HitAndRunMethod -- Approach the old goblin woman from behind. +11:06:01.723 [main] INFO com.iluwatar.templatemethod.HitAndRunMethod -- Grab the handbag and run away fast! +11:06:01.723 [main] INFO com.iluwatar.templatemethod.StealingMethod -- The target has been chosen as shop keeper. +11:06:01.723 [main] INFO com.iluwatar.templatemethod.SubtleMethod -- Approach the shop keeper with tears running and hug him! +11:06:01.723 [main] INFO com.iluwatar.templatemethod.SubtleMethod -- While in close contact grab the shop keeper's wallet. +``` -## Applicability +## When to Use the Template method Pattern in Java The Template Method pattern should be used @@ -140,18 +152,37 @@ The Template Method pattern should be used * When common behavior among subclasses should be factored and localized in a common class to avoid code duplication. This is a good example of "refactoring to generalize" as described by Opdyke and Johnson. You first identify the differences in the existing code and then separate the differences into new operations. Finally, you replace the differing code with a template method that calls one of these new operations * To control subclasses extensions. You can define a template method that calls "hook" operations at specific points, thereby permitting extensions only at those points -## Tutorials +## Template method Pattern Java Tutorials + +* [Template Method Design Pattern In Java (DigitalOcean)](https://www.digitalocean.com/community/tutorials/template-method-design-pattern-in-java) + +## Real-World Applications of Template method Pattern in Java + +* Java's AbstractList and AbstractSet classes in the Collections Framework use the Template Method pattern to define common algorithms for list and set operations. +* Frameworks like JUnit use Template Method to define the setup and teardown process in test cases. + +## Benefits and Trade-offs of Template method Pattern + +Benefits: + +* Promotes code reuse by defining invariant parts of an algorithm in a base class. +* Simplifies code maintenance by encapsulating common behavior in one place. +* Enhances flexibility by allowing subclasses to override specific steps of an algorithm. + +Trade-offs: -* [Template-method Pattern Tutorial](https://www.journaldev.com/1763/template-method-design-pattern-in-java) +* Can lead to an increase in the number of classes, making the system more complex. +* Requires careful design to ensure that the steps exposed to subclasses are useful and meaningful. -## Known uses +## Related Java Design Patterns -* [javax.servlet.GenericServlet.init](https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/GenericServlet.html#init--): -Method `GenericServlet.init(ServletConfig config)` calls the parameterless method `GenericServlet.init()` which is intended to be overridden in subclasses. -Method `GenericServlet.init(ServletConfig config)` is the template method in this example. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Often used with Template Method to create objects needed for specific steps of the algorithm. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): While Template Method defines the skeleton of an algorithm and lets subclasses implement specific steps, the Strategy Pattern defines a family of algorithms and makes them interchangeable. +* [Subclass Sandbox](https://java-design-patterns.com/patterns/subclass-sandbox/): Complements Template Method by ensuring that subclasses can safely override specific steps of an algorithm without causing unintended side effects. -## Credits +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/template-method/pom.xml b/template-method/pom.xml index dc75fdef0212..401af5c7270f 100644 --- a/template-method/pom.xml +++ b/template-method/pom.xml @@ -34,6 +34,14 @@ template-method + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -41,7 +49,7 @@ org.mockito - mockito-inline + mockito-core test diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java b/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java index 06dff72fa154..d7d14d52bded 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java @@ -24,9 +24,7 @@ */ package com.iluwatar.templatemethod; -/** - * Halfling thief uses {@link StealingMethod} to steal. - */ +/** Halfling thief uses {@link StealingMethod} to steal. */ public class HalflingThief { private StealingMethod method; diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java index 24c8bed104c5..a809fa688d16 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * HitAndRunMethod implementation of {@link StealingMethod}. - */ +/** HitAndRunMethod implementation of {@link StealingMethod}. */ @Slf4j public class HitAndRunMethod extends StealingMethod { diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java index db8813fa2bb9..f2479b07514d 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java @@ -1,50 +1,46 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.templatemethod; - -import lombok.extern.slf4j.Slf4j; - -/** - * StealingMethod defines skeleton for the algorithm. - */ -@Slf4j -public abstract class StealingMethod { - - protected abstract String pickTarget(); - - protected abstract void confuseTarget(String target); - - protected abstract void stealTheItem(String target); - - /** - * Steal. - */ - public final void steal() { - var target = pickTarget(); - LOGGER.info("The target has been chosen as {}.", target); - confuseTarget(target); - stealTheItem(target); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templatemethod; + +import lombok.extern.slf4j.Slf4j; + +/** StealingMethod defines skeleton for the algorithm. */ +@Slf4j +public abstract class StealingMethod { + + protected abstract String pickTarget(); + + protected abstract void confuseTarget(String target); + + protected abstract void stealTheItem(String target); + + /** Steal. */ + public final void steal() { + var target = pickTarget(); + LOGGER.info("The target has been chosen as {}.", target); + confuseTarget(target); + stealTheItem(target); + } +} diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java index 0ca294f6640c..4843fae27056 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SubtleMethod implementation of {@link StealingMethod}. - */ +/** SubtleMethod implementation of {@link StealingMethod}. */ @Slf4j public class SubtleMethod extends StealingMethod { diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java index 5e8023e64c5a..e9faed224ad2 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.templatemethod; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java index bf43d88b7c77..da39b5fdb756 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java @@ -26,56 +26,32 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import org.junit.jupiter.api.Test; -/** - * Date: 12/29/15 - 18:15 PM - * - * @author Jeroen Meulemeester - */ +/** HalflingThiefTest */ class HalflingThiefTest { - /** - * Verify if the thief uses the provided stealing method - */ + /** Verify if the thief uses the provided stealing method */ @Test void testSteal() { final var method = spy(StealingMethod.class); final var thief = new HalflingThief(method); - thief.steal(); verify(method).steal(); - String target = verify(method).pickTarget(); - verify(method).confuseTarget(target); - verify(method).stealTheItem(target); - - verifyNoMoreInteractions(method); } - /** - * Verify if the thief uses the provided stealing method, and the new method after changing it - */ + /** Verify if the thief uses the provided stealing method, and the new method after changing it */ @Test void testChangeMethod() { final var initialMethod = spy(StealingMethod.class); final var thief = new HalflingThief(initialMethod); - thief.steal(); verify(initialMethod).steal(); - String target = verify(initialMethod).pickTarget(); - verify(initialMethod).confuseTarget(target); - verify(initialMethod).stealTheItem(target); final var newMethod = spy(StealingMethod.class); thief.changeMethod(newMethod); - thief.steal(); verify(newMethod).steal(); - String newTarget = verify(newMethod).pickTarget(); - verify(newMethod).confuseTarget(newTarget); - verify(newMethod).stealTheItem(newTarget); - verifyNoMoreInteractions(initialMethod, newMethod); } -} \ No newline at end of file +} diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java index fb185e8954c8..85666667ca31 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java @@ -24,24 +24,16 @@ */ package com.iluwatar.templatemethod; -/** - * Date: 12/30/15 - 18:12 PM - * - * @author Jeroen Meulemeester - */ +/** HitAndRunMethodTest */ class HitAndRunMethodTest extends StealingMethodTest { - /** - * Create a new test for the {@link HitAndRunMethod} - */ + /** Create a new test for the {@link HitAndRunMethod} */ public HitAndRunMethodTest() { super( new HitAndRunMethod(), "old goblin woman", "The target has been chosen as old goblin woman.", "Approach the old goblin woman from behind.", - "Grab the handbag and run away fast!" - ); + "Grab the handbag and run away fast!"); } - -} \ No newline at end of file +} diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java index c8bc832e9d75..ee01df1cb384 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java @@ -38,10 +38,9 @@ import org.slf4j.LoggerFactory; /** - * Date: 12/30/15 - 18:12 PM + * StealingMethodTest * * @param Type of StealingMethod - * @author Jeroen Meulemeester */ public abstract class StealingMethodTest { @@ -57,42 +56,36 @@ void tearDown() { appender.stop(); } - /** - * The tested stealing method - */ + /** The tested stealing method */ private final M method; - /** - * The expected target - */ + /** The expected target */ private final String expectedTarget; - /** - * The expected target picking result - */ + /** The expected target picking result */ private final String expectedTargetResult; - /** - * The expected confusion method - */ + /** The expected confusion method */ private final String expectedConfuseMethod; - /** - * The expected stealing method - */ + /** The expected stealing method */ private final String expectedStealMethod; /** * Create a new test for the given stealing method, together with the expected results * - * @param method The tested stealing method - * @param expectedTarget The expected target name - * @param expectedTargetResult The expected target picking result + * @param method The tested stealing method + * @param expectedTarget The expected target name + * @param expectedTargetResult The expected target picking result * @param expectedConfuseMethod The expected confusion method - * @param expectedStealMethod The expected stealing method + * @param expectedStealMethod The expected stealing method */ - public StealingMethodTest(final M method, String expectedTarget, final String expectedTargetResult, - final String expectedConfuseMethod, final String expectedStealMethod) { + public StealingMethodTest( + final M method, + String expectedTarget, + final String expectedTargetResult, + final String expectedConfuseMethod, + final String expectedStealMethod) { this.method = method; this.expectedTarget = expectedTarget; @@ -101,17 +94,13 @@ public StealingMethodTest(final M method, String expectedTarget, final String ex this.expectedStealMethod = expectedStealMethod; } - /** - * Verify if the thief picks the correct target - */ + /** Verify if the thief picks the correct target */ @Test void testPickTarget() { assertEquals(expectedTarget, this.method.pickTarget()); } - /** - * Verify if the target confusing step goes as planned - */ + /** Verify if the target confusing step goes as planned */ @Test void testConfuseTarget() { assertEquals(0, appender.getLogSize()); @@ -121,9 +110,7 @@ void testConfuseTarget() { assertEquals(1, appender.getLogSize()); } - /** - * Verify if the stealing step goes as planned - */ + /** Verify if the stealing step goes as planned */ @Test void testStealTheItem() { assertEquals(0, appender.getLogSize()); @@ -133,9 +120,7 @@ void testStealTheItem() { assertEquals(1, appender.getLogSize()); } - /** - * Verify if the complete steal process goes as planned - */ + /** Verify if the complete steal process goes as planned */ @Test void testSteal() { this.method.steal(); @@ -146,7 +131,7 @@ void testSteal() { assertEquals(3, appender.getLogSize()); } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender() { diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java index ca89e76b040f..2288a9f8bab7 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java @@ -24,24 +24,16 @@ */ package com.iluwatar.templatemethod; -/** - * Date: 12/30/15 - 18:19 PM - * - * @author Jeroen Meulemeester - */ +/** SubtleMethodTest */ class SubtleMethodTest extends StealingMethodTest { - /** - * Create a new test for the {@link SubtleMethod} - */ + /** Create a new test for the {@link SubtleMethod} */ public SubtleMethodTest() { super( new SubtleMethod(), "shop keeper", "The target has been chosen as shop keeper.", "Approach the shop keeper with tears running and hug him!", - "While in close contact grab the shop keeper's wallet." - ); + "While in close contact grab the shop keeper's wallet."); } - -} \ No newline at end of file +} diff --git a/templateview/README.md b/templateview/README.md new file mode 100644 index 000000000000..3a53447c2cff --- /dev/null +++ b/templateview/README.md @@ -0,0 +1,144 @@ +--- +title: "Template View Pattern in Java: Streamlining Dynamic Webpage Rendering" +shortTitle: Template View +description: "Learn about the Template View design pattern in Java, which simplifies webpage rendering by separating static and dynamic content. Ideal for developers building reusable and maintainable UI components." +category: Behavioral +language: en +tag: + - Abstraction + - Code simplification + - Decoupling + - Extensibility + - Gang of Four + - Inheritance + - Polymorphism + - Reusability +--- + +## Intent of Template View Design Pattern + +Separate the structure and static parts of a webpage (or view) from its dynamic content. Template View ensures a consistent layout while allowing flexibility for different types of views. + +## Detailed Explanation of Template View Pattern with Real-World Examples + +### Real-World Example + +> Think of a blog website where each post page follows the same layout with a header, footer, and main content area. While the header and footer remain consistent, the main content differs for each blog post. The Template View pattern encapsulates the shared layout (header and footer) in a base class while delegating the rendering of the main content to subclasses. + +### In Plain Words + +> The Template View pattern provides a way to define a consistent layout in a base class while letting subclasses implement the specific, dynamic content for different views. + +### Wikipedia Says + +> While not a classic Gang of Four pattern, Template View aligns closely with the Template Method pattern, applied specifically to rendering webpages or views. It defines a skeleton for rendering, delegating dynamic parts to subclasses while keeping the structure consistent. + +## Programmatic Example of Template View Pattern in Java + +Our example involves rendering different types of views (`HomePageView` and `ContactPageView`) with a common structure consisting of a header, dynamic content, and a footer. + +### The Abstract Base Class: TemplateView + +The `TemplateView` class defines the skeleton for rendering a view. Subclasses provide implementations for rendering dynamic content. + +```java +@Slf4j +public abstract class TemplateView { + + public final void render() { + printHeader(); + renderDynamicContent(); + printFooter(); + } + + protected void printHeader() { + LOGGER.info("Rendering header..."); + } + + protected abstract void renderDynamicContent(); + + protected void printFooter() { + LOGGER.info("Rendering footer..."); + } +} +``` +### Concrete Class: HomePageView +```java +@Slf4j +public class HomePageView extends TemplateView { + + @Override + protected void renderDynamicContent() { + LOGGER.info("Welcome to the Home Page!"); + } +} +``` +### Concrete Class: ContactPageView +```java +@Slf4j +public class ContactPageView extends TemplateView { + + @Override + protected void renderDynamicContent() { + LOGGER.info("Contact us at: contact@example.com"); + } +} +``` +### Application Class: App +The `App` class demonstrates rendering different views using the Template View pattern. +```java +@Slf4j +public class App { + + public static void main(String[] args) { + TemplateView homePage = new HomePageView(); + LOGGER.info("Rendering HomePage:"); + homePage.render(); + + TemplateView contactPage = new ContactPageView(); + LOGGER.info("\nRendering ContactPage:"); + contactPage.render(); + } +} +``` +## Output of the Program +```lessRendering HomePage: +Rendering header... +Welcome to the Home Page! +Rendering footer... + +Rendering ContactPage: +Rendering header... +Contact us at: contact@example.com +Rendering footer... +``` +## When to Use the Template View Pattern in Java +- When you want to enforce a consistent structure for rendering views while allowing flexibility in dynamic content. +- When you need to separate the static layout (header, footer) from the dynamic parts of a view (main content). +- To enhance code reusability and reduce duplication in rendering logic. + +## Benefits and Trade-offs of Template View Pattern +**Benefits:** +- Code Reusability: Centralizes shared layout logic in the base class. +- Maintainability: Reduces duplication, making updates easier. +- Flexibility: Allows subclasses to customize dynamic content. + +**Trade-offs:** +- Increased Number of Classes: Requires creating separate classes for each type of view. +- Design Overhead: Might be overkill for simple applications with few views. + +## Related Java Design Patterns +- [Template Method](https://java-design-patterns.com/patterns/template-method/): A similar pattern focusing on defining a skeleton algorithm, allowing subclasses to implement specific steps. +- [Strategy Pattern](https://java-design-patterns.com/patterns/strategy/): Offers flexibility in choosing dynamic behaviors at runtime instead of hardcoding them in subclasses. +- [Decorator Pattern](https://java-design-patterns.com/patterns/decorator/): Can complement Template View for dynamically adding responsibilities to views. + +## Real World Applications of Template View Pattern +- Web frameworks like Spring MVC and Django use this concept to render views consistently. +- CMS platforms like WordPress follow this pattern for theme templates, separating layout from content. + +## References and Credits +- [Effective Java](https://amzn.to/4cGk2Jz) +- [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +- [Refactoring to Patterns](https://amzn.to/3VOO4F5) +- [Template Method Pattern](https://refactoring.guru/design-patterns/template-method) +- [Basics of Django: Model-View-Template (MVT) Architecture](https://angelogentileiii.medium.com/basics-of-django-model-view-template-mvt-architecture-8585aecffbf6) diff --git a/templateview/etc/template-view.urm.puml b/templateview/etc/template-view.urm.puml new file mode 100644 index 000000000000..503737b51686 --- /dev/null +++ b/templateview/etc/template-view.urm.puml @@ -0,0 +1,33 @@ +@startuml +package com.iluwater.templateview { + class App { + + App() + + main(args : String[]) {static} + } + + abstract class TemplateView { + - LOGGER : Logger {static} + + TemplateView() + + render() : void {final} + # printHeader() : void + # renderDynamicContent() : void {abstract} + # printFooter() : void + } + + class HomePageView { + - LOGGER : Logger {static} + + HomePageView() + + renderDynamicContent() : void + } + + class ContactPageView { + - LOGGER : Logger {static} + + ContactPageView() + + renderDynamicContent() : void + } +} + +App --> TemplateView +TemplateView <|-- HomePageView +TemplateView <|-- ContactPageView +@enduml diff --git a/templateview/etc/template_view_urm.png b/templateview/etc/template_view_urm.png new file mode 100644 index 000000000000..0c3e54132fd7 Binary files /dev/null and b/templateview/etc/template_view_urm.png differ diff --git a/templateview/etc/templateview.urm.puml b/templateview/etc/templateview.urm.puml new file mode 100644 index 000000000000..b4abc22749ca --- /dev/null +++ b/templateview/etc/templateview.urm.puml @@ -0,0 +1,29 @@ +@startuml +package com.iluwatar.templateview { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class ContactPageView { + - LOGGER : Logger {static} + + ContactPageView() + # renderDynamicContent() + } + class HomePageView { + - LOGGER : Logger {static} + + HomePageView() + # renderDynamicContent() + } + abstract class TemplateView { + - LOGGER : Logger {static} + + TemplateView() + # printFooter() + # printHeader() + + render() + # renderDynamicContent() {abstract} + } +} +ContactPageView --|> TemplateView +HomePageView --|> TemplateView +@enduml \ No newline at end of file diff --git a/templateview/pom.xml b/templateview/pom.xml new file mode 100644 index 000000000000..6474b8362b2f --- /dev/null +++ b/templateview/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + templateview + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.templateview.App + + + + + + + + + diff --git a/templateview/src/main/java/com/iluwatar/templateview/App.java b/templateview/src/main/java/com/iluwatar/templateview/App.java new file mode 100644 index 000000000000..6f99598b44af --- /dev/null +++ b/templateview/src/main/java/com/iluwatar/templateview/App.java @@ -0,0 +1,59 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import lombok.extern.slf4j.Slf4j; + +/** + * Template View defines a consistent layout for rendering views, delegating dynamic content + * rendering to subclasses. + * + *

      In this example, the {@link TemplateView} class provides the skeleton for rendering views with + * a header, dynamic content, and a footer. Subclasses {@link HomePageView} and {@link + * ContactPageView} define the specific dynamic content for their respective views. + * + *

      The {@link App} class demonstrates the usage of the Template View Pattern by rendering + * instances of {@link HomePageView} and {@link ContactPageView}. + */ +@Slf4j +public class App { + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + // Create and render the HomePageView + TemplateView homePage = new HomePageView(); + LOGGER.info("Rendering HomePage:"); + homePage.render(); + + // Create and render the ContactPageView + TemplateView contactPage = new ContactPageView(); + LOGGER.info("\nRendering ContactPage:"); + contactPage.render(); + } +} diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java b/templateview/src/main/java/com/iluwatar/templateview/ContactPageView.java similarity index 77% rename from thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java rename to templateview/src/main/java/com/iluwatar/templateview/ContactPageView.java index 24e86ed83a3c..f44e0a496951 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java +++ b/templateview/src/main/java/com/iluwatar/templateview/ContactPageView.java @@ -22,21 +22,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; +package com.iluwatar.templateview; + +import lombok.extern.slf4j.Slf4j; /** - * PotatoPeelingTask is a concrete task. + * ContactPageView implements the TemplateView and provides dynamic content specific to the contact + * page. */ -public class PotatoPeelingTask extends Task { - - private static final int TIME_PER_POTATO = 200; - - public PotatoPeelingTask(int numPotatoes) { - super(numPotatoes * TIME_PER_POTATO); - } +@Slf4j +public class ContactPageView extends TemplateView { + /** Renders dynamic content for the contact page. */ @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); + protected void renderDynamicContent() { + LOGGER.info("Contact us at: contact@example.com"); } } diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java b/templateview/src/main/java/com/iluwatar/templateview/HomePageView.java similarity index 78% rename from thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java rename to templateview/src/main/java/com/iluwatar/templateview/HomePageView.java index 423b4de3b8ee..b8a3cbe656f9 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java +++ b/templateview/src/main/java/com/iluwatar/templateview/HomePageView.java @@ -22,21 +22,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; +package com.iluwatar.templateview; + +import lombok.extern.slf4j.Slf4j; /** - * CoffeeMakingTask is a concrete task. + * HomePageView implements the TemplateView and provides dynamic content specific to the homepage. */ -public class CoffeeMakingTask extends Task { - - private static final int TIME_PER_CUP = 100; - - public CoffeeMakingTask(int numCups) { - super(numCups * TIME_PER_CUP); - } - +@Slf4j +public class HomePageView extends TemplateView { + /** Renders dynamic content for the homepage. */ @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); + protected void renderDynamicContent() { + LOGGER.info("Welcome to the Home Page!"); } } diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/Worker.java b/templateview/src/main/java/com/iluwatar/templateview/TemplateView.java similarity index 63% rename from priority-queue/src/main/java/com/iluwatar/priority/queue/Worker.java rename to templateview/src/main/java/com/iluwatar/templateview/TemplateView.java index 8bd3ba1e11e6..58291ffc9f47 100644 --- a/priority-queue/src/main/java/com/iluwatar/priority/queue/Worker.java +++ b/templateview/src/main/java/com/iluwatar/templateview/TemplateView.java @@ -22,43 +22,34 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.priority.queue; +package com.iluwatar.templateview; import lombok.extern.slf4j.Slf4j; /** - * Message Worker. + * TemplateView defines the skeleton for rendering views. Concrete subclasses will provide the + * dynamic content for specific views. */ @Slf4j -public class Worker { +public abstract class TemplateView { - private final QueueManager queueManager; - - public Worker(QueueManager queueManager) { - this.queueManager = queueManager; + /** Render the common structure of the view, delegating dynamic content to subclasses. */ + public final void render() { + printHeader(); + renderDynamicContent(); + printFooter(); } - /** - * Keep checking queue for message. - */ - @SuppressWarnings("squid:S2189") - public void run() throws Exception { - while (true) { - var message = queueManager.receiveMessage(); - if (message == null) { - LOGGER.info("No Message ... waiting"); - Thread.sleep(200); - } else { - processMessage(message); - } - } + /** Prints the common header of the view. */ + protected void printHeader() { + LOGGER.info("Rendering header..."); } - /** - * Process message. - */ - private void processMessage(Message message) { - LOGGER.info(message.toString()); - } + /** Subclasses must provide the implementation for rendering dynamic content. */ + protected abstract void renderDynamicContent(); + /** Prints the common footer of the view. */ + protected void printFooter() { + LOGGER.info("Rendering footer..."); + } } diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java b/templateview/src/test/java/com/iluwatar/templateview/AppTest.java similarity index 88% rename from thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java rename to templateview/src/test/java/com/iluwatar/templateview/AppTest.java index 5cdb36a23f48..785b093f8956 100644 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java +++ b/templateview/src/test/java/com/iluwatar/templateview/AppTest.java @@ -22,21 +22,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.threadpool; - -import org.junit.jupiter.api.Test; +package com.iluwatar.templateview; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - * - * @author ilkka - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + // Verify that main() method executes without throwing exceptions + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/templateview/src/test/java/com/iluwatar/templateview/ContactPageViewTest.java b/templateview/src/test/java/com/iluwatar/templateview/ContactPageViewTest.java new file mode 100644 index 000000000000..f906df724704 --- /dev/null +++ b/templateview/src/test/java/com/iluwatar/templateview/ContactPageViewTest.java @@ -0,0 +1,44 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; + +class ContactPageViewTest { + + @Test + void testRenderDynamicContent() { + // Create a spy for ContactPageView + ContactPageView contactPage = spy(ContactPageView.class); + + // Render dynamic content for ContactPageView + contactPage.renderDynamicContent(); + + // Verify that the correct message is logged + verify(contactPage).renderDynamicContent(); + } +} diff --git a/module/src/test/java/com/iluwatar/module/AppTest.java b/templateview/src/test/java/com/iluwatar/templateview/HomePageViewTest.java similarity index 76% rename from module/src/test/java/com/iluwatar/module/AppTest.java rename to templateview/src/test/java/com/iluwatar/templateview/HomePageViewTest.java index c385b8d8a8a1..1f546c66d8cc 100644 --- a/module/src/test/java/com/iluwatar/module/AppTest.java +++ b/templateview/src/test/java/com/iluwatar/templateview/HomePageViewTest.java @@ -22,21 +22,23 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.module; +package com.iluwatar.templateview; -import java.io.FileNotFoundException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; +import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; -/** - * Tests that Module example runs without errors. - */ -final class AppTest { +class HomePageViewTest { @Test - void shouldExecuteWithoutException() { - assertDoesNotThrow((Executable) App::main); + void testRenderDynamicContent() { + // Create a spy for HomePageView + HomePageView homePage = spy(HomePageView.class); + + // Render dynamic content for HomePageView + homePage.renderDynamicContent(); + + // Verify that the correct message is logged + verify(homePage).renderDynamicContent(); } } diff --git a/templateview/src/test/java/com/iluwatar/templateview/TemplateViewTest.java b/templateview/src/test/java/com/iluwatar/templateview/TemplateViewTest.java new file mode 100644 index 000000000000..67335e728283 --- /dev/null +++ b/templateview/src/test/java/com/iluwatar/templateview/TemplateViewTest.java @@ -0,0 +1,60 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; + +class TemplateViewTest { + + @Test + void testRenderHomePage() { + // Create a spy for HomePageView + TemplateView homePage = spy(HomePageView.class); + + // Call the render method + homePage.render(); + + // Verify that the steps of rendering are executed in the correct order + verify(homePage).printHeader(); // Header is printed + verify(homePage).renderDynamicContent(); // Dynamic content specific to home page + verify(homePage).printFooter(); // Footer is printed + } + + @Test + void testRenderContactPage() { + // Create a spy for ContactPageView + TemplateView contactPage = spy(ContactPageView.class); + + // Call the render method + contactPage.render(); + + // Verify that the steps of rendering are executed in the correct order + verify(contactPage).printHeader(); // Header is printed + verify(contactPage).renderDynamicContent(); // Dynamic content specific to contact page + verify(contactPage).printFooter(); // Footer is printed + } +} diff --git a/thread-local-storage/README.md b/thread-local-storage/README.md deleted file mode 100644 index 97506d58c314..000000000000 --- a/thread-local-storage/README.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -title: Thread-local storage -category: Concurrency -language: en -tag: -- Data access ---- - -## Intent - -Provide an ability to have a copy of a variable for each thread, making it thread-safe. - -## Explanation - -During code assembling compiler add _.tdata_ directive, -which means that variables below will store in different places from thread to thread. -This means changing variable in one thread won't affect the same variable in other thread. - -**Real world example** - -> On constructions each worker has its own shovel. When one of them broke his shovel, -> other still can continue work due to they have own instrument. - -**In plain words** - ->Each thread will have its own copy of variable. - -**Wikipedia says** - ->Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread. - -**Programmatic Example** - -To define variable in thread-local storage you just need to put it into Java's ThreadLocal, e.g: - -```java -public class Main { - - private ThreadLocal stringThreadLocal = new ThreadLocal<>(); - - public void initialize(String value) { - this.stringThreadLocal.set(value); - } -} -``` - -Below we will contact a variable from two threads at a time to see, why we need it. -Main logic that show's current variable value is imposed in class AbstractThreadLocalExample, two implementations -differs only in the **value store approach**. - -Main class: - -```java -/** - * Class with main per-thread logic. - */ -public abstract class AbstractThreadLocalExample implements Runnable { - - private static final Random RND = new Random(); - - @Override - public void run() { - //Here we stop thread randomly - LockSupport.parkNanos(RND.nextInt(1_000_000_000, 2_000_000_000)); - - System.out.println(getCurrentThreadName() + ", before value changing: " + getter().get()); - setter().accept(RND.nextInt()); - } - - /** - * Setter for our value. - * - * @return consumer - */ - protected abstract Consumer setter(); - - /** - * Getter for our value. - * - * @return supplier - */ - protected abstract Supplier getter(); - - private String getCurrentThreadName() { - return Thread.currentThread().getName(); - } -} - -``` - -And two implementations. With ThreadLocal: -```java -/** - * Example of runnable with use of {@link ThreadLocal}. - */ -public class WithThreadLocal extends AbstractThreadLocalExample { - - private ThreadLocal value; - - public WithThreadLocal(ThreadLocal value) { - this.value = value; - } - - @Override - protected Consumer setter() { - return value::set; - } - - @Override - protected Supplier getter() { - return value::get; - } -} -``` - -And the class that stores value in one shared for all threads place: -```java -/** - * Example of runnable without usage of {@link ThreadLocal}. - */ -public class WithoutThreadLocal extends AbstractThreadLocalExample { - - private Integer value; - - public WithoutThreadLocal(Integer value) { - this.value = value; - } - - @Override - protected Consumer setter() { - return integer -> value = integer; - } - - @Override - protected Supplier getter() { - return () -> value; - } -} -``` - -Guess we want to run these classes. We will construct both implementations and invoke _run_ method. -So, we want to see initial value in console (thanks to System.out.println()). Let's take a look at tests. - -```java -public class ThreadLocalTest { - - private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - - @BeforeEach - public void setUpStreams() { - System.setOut(new PrintStream(outContent)); - } - - @AfterEach - public void restoreStreams() { - System.setOut(originalOut); - } - - @Test - public void withoutThreadLocal() throws InterruptedException { - int initialValue = 1234567890; - - int threadSize = 2; - ExecutorService executor = Executors.newFixedThreadPool(threadSize); - - WithoutThreadLocal threadLocal = new WithoutThreadLocal(initialValue); - for (int i = 0; i < threadSize; i++) { - executor.submit(threadLocal); - } - executor.awaitTermination(3, TimeUnit.SECONDS); - - List lines = outContent.toString().lines().toList(); - //Matches only first finished thread output, the second has changed by first thread value - Assertions.assertFalse(lines.stream() - .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); - } - - @Test - public void withThreadLocal() throws InterruptedException { - int initialValue = 1234567890; - - int threadSize = 2; - ExecutorService executor = Executors.newFixedThreadPool(threadSize); - - WithThreadLocal threadLocal = new WithThreadLocal(ThreadLocal.withInitial(() -> initialValue)); - for (int i = 0; i < threadSize; i++) { - executor.submit(threadLocal); - } - - executor.awaitTermination(3, TimeUnit.SECONDS); - - List lines = outContent.toString().lines().toList(); - Assertions.assertTrue(lines.stream() - .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); - } -} -``` - -The output of test named withThreadLocal: -``` -pool-2-thread-2, before value changing: 1234567890 -pool-2-thread-1, before value changing: 1234567890 - -``` - -And the output of withoutThreadLocal: -``` -pool-1-thread-2, before value changing: 1234567890 -pool-1-thread-1, before value changing: 848843054 -``` -Where 1234567890 - is our initial value. We see, that in test _withoutThreadLocal_ -thread 2 got out from LockSupport#parkNanos earlier than the first and -change value in shared variable. - -## Class diagram - -```mermaid -classDiagram - class ThreadLocal - ThreadLocal : get() - ThreadLocal : initialValue() - ThreadLocal : remove() - ThreadLocal : set(T value) -``` - -## Applicability - -Use ThreadLocal when: - -- You need to run singleton with state in multiple threads -- You need to use not thread-safe classes in concurrency program - -## Tutorials -- [Baeldung](https://www.baeldung.com/java-threadlocal) - -## Known uses -- In java.lang.Thread during thread initialization -- In java.net.URL to prevent recursive provider lookups -- In org.junit.runners.BlockJUnit4ClassRunner to contain current rule -- In org.springframework:spring-web to store request context -- In org.apache.util.net.Nio2Endpoint to allow detecting if a completion handler completes inline -- In io.micrometer to avoid problems with not thread-safe NumberFormat - -## Credits -- [Usage cases](https://chao-tic.github.io/blog/2018/12/25/tls) -- [Implementation in Linux](https://uclibc.org/docs/tls.pdf) \ No newline at end of file diff --git a/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java b/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java deleted file mode 100644 index 0ab20a24e86c..000000000000 --- a/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.iluwatar; - -import java.security.SecureRandom; -import java.util.concurrent.locks.LockSupport; -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** - * Class with main logic. - */ -public abstract class AbstractThreadLocalExample implements Runnable { - - private static final SecureRandom RND = new SecureRandom(); - - private static final Integer RANDOM_THREAD_PARK_START = 1_000_000_000; - private static final Integer RANDOM_THREAD_PARK_END = 2_000_000_000; - - @Override - public void run() { - long nanosToPark = RND.nextInt(RANDOM_THREAD_PARK_START, RANDOM_THREAD_PARK_END); - LockSupport.parkNanos(nanosToPark); - - System.out.println(getThreadName() + ", before value changing: " + getter().get()); - setter().accept(RND.nextInt()); - } - - /** - * Setter for our value. - * - * @return consumer - */ - protected abstract Consumer setter(); - - /** - * Getter for our value. - * - * @return supplier - */ - protected abstract Supplier getter(); - - private String getThreadName() { - return Thread.currentThread().getName(); - } -} diff --git a/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java b/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java deleted file mode 100644 index d66440d8330c..000000000000 --- a/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.iluwatar; - -import java.util.function.Consumer; -import java.util.function.Supplier; -import lombok.AllArgsConstructor; - -/** - * Example of runnable with use of {@link ThreadLocal}. - */ -@AllArgsConstructor -public class WithThreadLocal extends AbstractThreadLocalExample { - - private final ThreadLocal value; - - /** - * Removes the current thread's value for this thread-local variable. - */ - public void remove() { - this.value.remove(); - } - - @Override - protected Consumer setter() { - return value::set; - } - - @Override - protected Supplier getter() { - return value::get; - } -} diff --git a/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java b/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java deleted file mode 100644 index 8d60659eeaac..000000000000 --- a/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.iluwatar; - -import java.util.function.Consumer; -import java.util.function.Supplier; -import lombok.AllArgsConstructor; - -/** - * Example of runnable without usage of {@link ThreadLocal}. - */ -@AllArgsConstructor -public class WithoutThreadLocal extends AbstractThreadLocalExample { - - private Integer value; - - @Override - protected Consumer setter() { - return integer -> value = integer; - } - - @Override - protected Supplier getter() { - return () -> value; - } -} diff --git a/thread-local-storage/src/test/java/ThreadLocalTest.java b/thread-local-storage/src/test/java/ThreadLocalTest.java deleted file mode 100644 index 974b7ec8bab0..000000000000 --- a/thread-local-storage/src/test/java/ThreadLocalTest.java +++ /dev/null @@ -1,67 +0,0 @@ -import com.iluwatar.WithThreadLocal; -import com.iluwatar.WithoutThreadLocal; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -public class ThreadLocalTest { - - private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - - @BeforeEach - public void setUpStreams() { - System.setOut(new PrintStream(outContent)); - } - - @AfterEach - public void restoreStreams() { - System.setOut(originalOut); - } - - @Test - public void withoutThreadLocal() throws InterruptedException { - int initialValue = 1234567890; - - int threadSize = 2; - ExecutorService executor = Executors.newFixedThreadPool(threadSize); - - WithoutThreadLocal threadLocal = new WithoutThreadLocal(initialValue); - for (int i = 0; i < threadSize; i++) { - executor.submit(threadLocal); - } - executor.awaitTermination(3, TimeUnit.SECONDS); - - List lines = outContent.toString().lines().toList(); - //Matches only first thread, the second has changed by first thread value - Assertions.assertFalse(lines.stream() - .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); - } - - @Test - public void withThreadLocal() throws InterruptedException { - int initialValue = 1234567890; - - int threadSize = 2; - ExecutorService executor = Executors.newFixedThreadPool(threadSize); - - WithThreadLocal threadLocal = new WithThreadLocal(ThreadLocal.withInitial(() -> initialValue)); - for (int i = 0; i < threadSize; i++) { - executor.submit(threadLocal); - } - - executor.awaitTermination(3, TimeUnit.SECONDS); - threadLocal.remove(); - - List lines = outContent.toString().lines().toList(); - Assertions.assertTrue(lines.stream() - .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); - } -} diff --git a/thread-pool/README.md b/thread-pool/README.md deleted file mode 100644 index 362d917548d9..000000000000 --- a/thread-pool/README.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Thread Pool -category: Concurrency -language: en -tag: - - Performance ---- - -## Intent - -It is often the case that tasks to be executed are short-lived and the number of tasks is large. -Creating a new thread for each task would make the system spend more time creating and destroying -the threads than executing the actual tasks. Thread Pool solves this problem by reusing existing -threads and eliminating the latency of creating new threads. - -## Explanation - -Real-world example - -> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes -> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so -> we establish a thread pool. - -In plain words - -> Thread Pool is a concurrency pattern where threads are allocated once and reused between tasks. - -Wikipedia says - -> In computer programming, a thread pool is a software design pattern for achieving concurrency of -> execution in a computer program. Often also called a replicated workers or worker-crew model, -> a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent -> execution by the supervising program. By maintaining a pool of threads, the model increases -> performance and avoids latency in execution due to frequent creation and destruction of threads -> for short-lived tasks. The number of available threads is tuned to the computing resources -> available to the program, such as a parallel task queue after completion of execution. - -**Programmatic Example** - -Let's first look at our task hierarchy. We have a base class and then concrete `CoffeeMakingTask` -and `PotatoPeelingTask`. - -```java -public abstract class Task { - - private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); - - private final int id; - private final int timeMs; - - public Task(final int timeMs) { - this.id = ID_GENERATOR.incrementAndGet(); - this.timeMs = timeMs; - } - - public int getId() { - return id; - } - - public int getTimeMs() { - return timeMs; - } - - @Override - public String toString() { - return String.format("id=%d timeMs=%d", id, timeMs); - } -} - -public class CoffeeMakingTask extends Task { - - private static final int TIME_PER_CUP = 100; - - public CoffeeMakingTask(int numCups) { - super(numCups * TIME_PER_CUP); - } - - @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); - } -} - -public class PotatoPeelingTask extends Task { - - private static final int TIME_PER_POTATO = 200; - - public PotatoPeelingTask(int numPotatoes) { - super(numPotatoes * TIME_PER_POTATO); - } - - @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); - } -} -``` - -Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato -peeling and coffee making. - -```java -@Slf4j -public class Worker implements Runnable { - - private final Task task; - - public Worker(final Task task) { - this.task = task; - } - - @Override - public void run() { - LOGGER.info("{} processing {}", Thread.currentThread().getName(), task.toString()); - try { - Thread.sleep(task.getTimeMs()); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} -``` - -Now we are ready to show the full example in action. - -```java - LOGGER.info("Program started"); - - // Create a list of tasks to be executed - var tasks = List.of( - new PotatoPeelingTask(3), - new PotatoPeelingTask(6), - new CoffeeMakingTask(2), - new CoffeeMakingTask(6), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(9), - new PotatoPeelingTask(3), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new CoffeeMakingTask(7), - new PotatoPeelingTask(4), - new PotatoPeelingTask(5)); - - // Creates a thread pool that reuses a fixed number of threads operating off a shared - // unbounded queue. At any point, at most nThreads threads will be active processing - // tasks. If additional tasks are submitted when all threads are active, they will wait - // in the queue until a thread is available. - var executor = Executors.newFixedThreadPool(3); - - // Allocate new worker for each task - // The worker is executed when a thread becomes - // available in the thread pool - tasks.stream().map(Worker::new).forEach(executor::execute); - // All tasks were executed, now shutdown - executor.shutdown(); - while (!executor.isTerminated()) { - Thread.yield(); - } - LOGGER.info("Program finished"); -``` - -## Class diagram - -![alt text](./etc/thread_pool_urm.png "Thread Pool") - -## Applicability - -Use the Thread Pool pattern when - -* You have a large number of short-lived tasks to be executed in parallel - -## Credits - -* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0134685997&linkId=e1b9ddd5e669591642c4f30d40cd9f6b) -* [Java Concurrency in Practice](https://www.amazon.com/gp/product/0321349601/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321349601&linkId=fbedb3bad3c6cbead5afa56eea39ed59) diff --git a/thread-pool/etc/thread-pool.urm.puml b/thread-pool/etc/thread-pool.urm.puml deleted file mode 100644 index 251033c8157c..000000000000 --- a/thread-pool/etc/thread-pool.urm.puml +++ /dev/null @@ -1,37 +0,0 @@ -@startuml -package com.iluwatar.threadpool { - class App { - - LOGGER : Logger {static} - + App() - + main(args : String[]) {static} - } - class CoffeeMakingTask { - - TIME_PER_CUP : int {static} - + CoffeeMakingTask(numCups : int) - + toString() : String - } - class PotatoPeelingTask { - - TIME_PER_POTATO : int {static} - + PotatoPeelingTask(numPotatoes : int) - + toString() : String - } - abstract class Task { - - ID_GENERATOR : AtomicInteger {static} - - id : int - - timeMs : int - + Task(timeMs : int) - + getId() : int - + getTimeMs() : int - + toString() : String - } - class Worker { - - LOGGER : Logger {static} - - task : Task - + Worker(task : Task) - + run() - } -} -Worker --> "-task" Task -CoffeeMakingTask --|> Task -PotatoPeelingTask --|> Task -@enduml \ No newline at end of file diff --git a/thread-pool/etc/thread_pool_urm.png b/thread-pool/etc/thread_pool_urm.png deleted file mode 100644 index 3d433824f95a..000000000000 Binary files a/thread-pool/etc/thread_pool_urm.png and /dev/null differ diff --git a/thread-pool/pom.xml b/thread-pool/pom.xml deleted file mode 100644 index f58f771c0b42..000000000000 --- a/thread-pool/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - thread-pool - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.threadpool.App - - - - - - - - - diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java b/thread-pool/src/main/java/com/iluwatar/threadpool/App.java deleted file mode 100644 index a2c3f9e3a25a..000000000000 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import lombok.extern.slf4j.Slf4j; - -/** - * Thread Pool pattern is where a number of threads are created to perform a number of tasks, which - * are usually organized in a queue. The results from the tasks being executed might also be placed - * in a queue, or the tasks might return no result. Typically, there are many more tasks than - * threads. As soon as a thread completes its task, it will request the next task from the queue - * until all tasks have been completed. The thread can then terminate, or sleep until there are new - * tasks available. - * - *

      In this example we create a list of tasks presenting work to be done. Each task is then - * wrapped into a {@link Worker} object that implements {@link Runnable}. We create an {@link - * ExecutorService} with fixed number of threads (Thread Pool) and use them to execute the {@link - * Worker}s. - */ -@Slf4j -public class App { - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - - LOGGER.info("Program started"); - - // Create a list of tasks to be executed - var tasks = List.of( - new PotatoPeelingTask(3), - new PotatoPeelingTask(6), - new CoffeeMakingTask(2), - new CoffeeMakingTask(6), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(9), - new PotatoPeelingTask(3), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new CoffeeMakingTask(7), - new PotatoPeelingTask(4), - new PotatoPeelingTask(5)); - - // Creates a thread pool that reuses a fixed number of threads operating off a shared - // unbounded queue. At any point, at most nThreads threads will be active processing - // tasks. If additional tasks are submitted when all threads are active, they will wait - // in the queue until a thread is available. - var executor = Executors.newFixedThreadPool(3); - - // Allocate new worker for each task - // The worker is executed when a thread becomes - // available in the thread pool - tasks.stream().map(Worker::new).forEach(executor::execute); - // All tasks were executed, now shutdown - executor.shutdown(); - while (!executor.isTerminated()) { - Thread.yield(); - } - LOGGER.info("Program finished"); - } -} diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java deleted file mode 100644 index 3c3cd950d323..000000000000 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import static java.time.Duration.ofMillis; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTimeout; - -import java.util.ArrayList; -import java.util.Objects; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.function.IntFunction; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import org.junit.jupiter.api.Test; - -/** - * Date: 12/30/15 - 18:22 PM Test for Tasks using a Thread Pool - * - * @param Type of Task - * @author Jeroen Meulemeester - */ -public abstract class TaskTest { - - /** - * The number of tasks used during the concurrency test - */ - private static final int TASK_COUNT = 128 * 1024; - - /** - * The number of threads used during the concurrency test - */ - private static final int THREAD_COUNT = 8; - - /** - * The task factory, used to create new test items - */ - private final IntFunction factory; - - /** - * The expected time needed to run the task 1 single time, in milli seconds - */ - private final int expectedExecutionTime; - - /** - * Create a new test instance - * - * @param factory The task factory, used to create new test items - * @param expectedExecutionTime The expected time needed to run the task 1 time, in milli seconds - */ - public TaskTest(final IntFunction factory, final int expectedExecutionTime) { - this.factory = factory; - this.expectedExecutionTime = expectedExecutionTime; - } - - /** - * Verify if the generated id is unique for each task, even if the tasks are created in separate - * threads - */ - @Test - void testIdGeneration() throws Exception { - assertTimeout(ofMillis(10000), () -> { - final var service = Executors.newFixedThreadPool(THREAD_COUNT); - - final var tasks = IntStream.range(0, TASK_COUNT) - .>mapToObj(i -> () -> factory.apply(1).getId()) - .collect(Collectors.toCollection(ArrayList::new)); - - final var ids = service.invokeAll(tasks) - .stream() - .map(TaskTest::get) - .filter(Objects::nonNull) - .toList(); - - service.shutdownNow(); - - final var uniqueIdCount = ids.stream() - .distinct() - .count(); - - assertEquals(TASK_COUNT, ids.size()); - assertEquals(TASK_COUNT, uniqueIdCount); - }); - } - - /** - * Verify if the time per execution of a task matches the actual time required to execute the task - * a given number of times - */ - @Test - void testTimeMs() { - for (var i = 0; i < 10; i++) { - assertEquals(this.expectedExecutionTime * i, this.factory.apply(i).getTimeMs()); - } - } - - /** - * Verify if the task has some sort of {@link T#toString()}, different from 'null' - */ - @Test - void testToString() { - assertNotNull(this.factory.apply(0).toString()); - } - - /** - * Extract the result from a future or returns 'null' when an exception occurred - * - * @param future The future we want the result from - * @param The result type - * @return The result or 'null' when a checked exception occurred - */ - private static O get(Future future) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - return null; - } - } - -} diff --git a/throttling/README.md b/throttling/README.md index 5c338dfe5bfc..d35c346deea0 100644 --- a/throttling/README.md +++ b/throttling/README.md @@ -1,46 +1,50 @@ --- -title: Throttling -category: Behavioral +title: "Throttling Pattern in Java: Optimizing Resource Usage in High-Demand Applications" +shortTitle: Throttling +description: "Explore the Throttling design pattern in Java to manage application stability and prevent system overload. Learn how rate limiting ensures consistent performance and system resilience. Ideal for developers and software architects." +category: Resource management language: en tag: - - Performance - - Cloud distributed + - API design + - Fault tolerance + - Performance + - Resilience + - Scalability --- -## Intent +## Also known as -Ensure that a given client is not able to access service resources more than the assigned limit. +* Rate Limiting -## Explanation +## Intent of Throttling Design Pattern + +The Throttling Pattern, also known as Rate Limiting, limits the number of requests a system can process within a given time frame to prevent overload and ensure stability. It is crucial for resource management in Java applications. + +## Detailed Explanation of Throttling Pattern with Real-World Examples Real-world example -> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender. -> The bartender immediately sees that the young human shouldn't consume too many drinks too fast -> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can -> be higher. +> Imagine a popular amusement park that limits the number of visitors who can enter per hour to prevent overcrowding. This ensures that all visitors can enjoy the park without long wait times and maintain a pleasant experience. Similarly, the Throttling design pattern in software controls the rate of requests to a system, preventing it from being overwhelmed and ensuring consistent performance for all users. In plain words -> Throttling pattern is used to rate-limit access to a resource. +> Throttling pattern is used to rate-limit access to a resource. [Microsoft documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) says -> Control the consumption of resources used by an instance of an application, an individual tenant, -> or an entire service. This can allow the system to continue to function and meet service level -> agreements, even when an increase in demand places an extreme load on resources. +> Control the consumption of resources used by an instance of an application, an individual tenant, or an entire service. This can allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources. -**Programmatic Example** +## Programmatic Example of Throttling Pattern in Java -`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of -calls per `BarCustomer`. +In this Java example, we demonstrate throttling. A young human and an old dwarf walk into a bar. They start ordering beers from the bartender. The bartender immediately sees that the young human shouldn't consume too many drinks too fast and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can be higher. + +`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of calls per `BarCustomer`. ```java +@Getter public class BarCustomer { - @Getter private final String name; - @Getter private final int allowedCallsPerSecond; public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) { @@ -52,63 +56,64 @@ public class BarCustomer { callsCount.addTenant(name); } } +``` +```java @Slf4j public final class CallsCount { - private final Map tenantCallsCount = new ConcurrentHashMap<>(); + private final Map tenantCallsCount = new ConcurrentHashMap<>(); - public void addTenant(String tenantName) { - tenantCallsCount.putIfAbsent(tenantName, new AtomicLong(0)); - } + public void addTenant(String tenantName) { + tenantCallsCount.putIfAbsent(tenantName, new AtomicLong(0)); + } - public void incrementCount(String tenantName) { - tenantCallsCount.get(tenantName).incrementAndGet(); - } + public void incrementCount(String tenantName) { + tenantCallsCount.get(tenantName).incrementAndGet(); + } - public long getCount(String tenantName) { - return tenantCallsCount.get(tenantName).get(); - } + public long getCount(String tenantName) { + return tenantCallsCount.get(tenantName).get(); + } - public void reset() { - tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); - LOGGER.info("reset counters"); - } + public void reset() { + tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); + LOGGER.info("reset counters"); + } } ``` -Next, the service that the tenants are calling is introduced. To track the call count, a throttler -timer is used. +Next, the service that the tenants are calling is introduced. To track the call count, a throttler timer is used. ```java public interface Throttler { - - void start(); + void start(); } +``` +```java public class ThrottleTimerImpl implements Throttler { - private final int throttlePeriod; - private final CallsCount callsCount; - - public ThrottleTimerImpl(int throttlePeriod, CallsCount callsCount) { - this.throttlePeriod = throttlePeriod; - this.callsCount = callsCount; - } - - @Override - public void start() { - new Timer(true).schedule(new TimerTask() { - @Override - public void run() { - callsCount.reset(); - } - }, 0, throttlePeriod); - } + private final int throttlePeriod; + private final CallsCount callsCount; + + public ThrottleTimerImpl(int throttlePeriod, CallsCount callsCount) { + this.throttlePeriod = throttlePeriod; + this.callsCount = callsCount; + } + + @Override + public void start() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + callsCount.reset(); + } + }, 0, throttlePeriod); + } } ``` -`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't -know that the beer serving rate is limited by their appearances. +`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't know that the beer serving rate is limited by their appearances. ```java class Bartender { @@ -129,7 +134,7 @@ class Bartender { return -1; } callsCount.incrementCount(tenantName); - LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1); + LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1); return getRandomCustomerId(); } @@ -139,40 +144,45 @@ class Bartender { } ``` -Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2 -calls per second and the old dwarf to 4. +Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2 calls per second and the old dwarf to 4. ```java -public static void main(String[] args) { - var callsCount = new CallsCount(); - var human = new BarCustomer("young human", 2, callsCount); - var dwarf = new BarCustomer("dwarf soldier", 4, callsCount); +@Slf4j +public class App { - var executorService = Executors.newFixedThreadPool(2); + public static void main(String[] args) { + var callsCount = new CallsCount(); + var human = new BarCustomer("young human", 2, callsCount); + var dwarf = new BarCustomer("dwarf soldier", 4, callsCount); - executorService.execute(() -> makeServiceCalls(human, callsCount)); - executorService.execute(() -> makeServiceCalls(dwarf, callsCount)); + var executorService = Executors.newFixedThreadPool(2); - executorService.shutdown(); - try { - executorService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOGGER.error("Executor service terminated: {}", e.getMessage()); - } -} + executorService.execute(() -> makeServiceCalls(human, callsCount)); + executorService.execute(() -> makeServiceCalls(dwarf, callsCount)); -private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { - var timer = new ThrottleTimerImpl(1000, callsCount); - var service = new Bartender(timer, callsCount); - // Sleep is introduced to keep the output in check and easy to view and analyze the results. - IntStream.range(0, 50).forEach(i -> { - service.orderDrink(barCustomer); + executorService.shutdown(); try { - Thread.sleep(100); + if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } } catch (InterruptedException e) { - LOGGER.error("Thread interrupted: {}", e.getMessage()); + executorService.shutdownNow(); } - }); + } + + private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { + var timer = new ThrottleTimerImpl(1000, callsCount); + var service = new Bartender(timer, callsCount); + // Sleep is introduced to keep the output in check and easy to view and analyze the results. + IntStream.range(0, 50).forEach(i -> { + service.orderDrink(barCustomer); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: {}", e.getMessage()); + } + }); + } } ``` @@ -203,18 +213,38 @@ An excerpt from the example's console output: 18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today! ``` -## Class diagram +## When to Use the Throttling Pattern in Java + +* You need to protect resources from being overwhelmed by too many requests. +* You want to ensure fair usage of a service among multiple users. +* You need to maintain the quality of service under high load conditions. + +## Real-World Applications of Throttling Pattern in Java + +* APIs of major cloud providers like AWS, Google Cloud, and Azure use throttling to manage resource usage. +* Web services to prevent denial-of-service (DoS) attacks by limiting the number of requests from a single IP address. +* Online platforms like social media sites and e-commerce websites to ensure even distribution of server load. + +## Benefits and Trade-offs of Throttling Pattern + +Benefits: + +* Prevents resource exhaustion, ensuring system stability. +* Helps in maintaining consistent performance and quality of service. +* Improves fault tolerance by avoiding system crashes under high load. -![alt text](./etc/throttling_urm.png "Throttling pattern class diagram") +Trade-offs: -## Applicability +* May cause increased latency or delay in request processing. +* Requires careful tuning to balance between resource protection and user experience. +* Could lead to denial of service to legitimate users if not configured correctly. -The Throttling pattern should be used: +## Related Java Design Patterns -* When service access needs to be restricted not to have high impact on the performance of the service. -* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client. +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Works in tandem with throttling to prevent repeated attempts to access an overloaded service. +* Bulkhead: Isolates different parts of the system to limit the impact of throttling on other components. -## Credits +## References and Credits -* [Throttling pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) -* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications (Microsoft patterns & practices)](https://www.amazon.com/gp/product/B00ITGHBBS/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B00ITGHBBS&linkId=12aacdd0cec04f372e7152689525631a) +* [Throttling pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/throttling) +* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications](https://amzn.to/4dLvowg) diff --git a/throttling/pom.xml b/throttling/pom.xml index e6bda0867414..91551e01d6e5 100644 --- a/throttling/pom.xml +++ b/throttling/pom.xml @@ -34,6 +34,14 @@ 4.0.0 throttling + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/throttling/src/main/java/com/iluwatar/throttling/App.java b/throttling/src/main/java/com/iluwatar/throttling/App.java index f66d75fb53bc..7b25b0a6b1a9 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/App.java +++ b/throttling/src/main/java/com/iluwatar/throttling/App.java @@ -34,12 +34,11 @@ * Throttling pattern is a design pattern to throttle or limit the use of resources or even a * complete service by users or a particular tenant. This can allow systems to continue to function * and meet service level agreements, even when an increase in demand places load on resources. - *

      - * In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time - * based throttling, i.e. only a certain number of calls are allowed per second. - *

      - * ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed. - * ({@link Bartender}) is the service which is consumed by the tenants and is throttled. + * + *

      In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a + * time based throttling, i.e. only a certain number of calls are allowed per second. ({@link + * BarCustomer}) is the service tenant class having a name and the number of calls allowed. ({@link + * Bartender}) is the service which is consumed by the tenants and is throttled. */ @Slf4j public class App { @@ -69,20 +68,20 @@ public static void main(String[] args) { } } - /** - * Make calls to the bartender. - */ + /** Make calls to the bartender. */ private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { var timer = new ThrottleTimerImpl(1000, callsCount); var service = new Bartender(timer, callsCount); // Sleep is introduced to keep the output in check and easy to view and analyze the results. - IntStream.range(0, 50).forEach(i -> { - service.orderDrink(barCustomer); - try { - Thread.sleep(100); - } catch (InterruptedException e) { - LOGGER.error("Thread interrupted: {}", e.getMessage()); - } - }); + IntStream.range(0, 50) + .forEach( + i -> { + service.orderDrink(barCustomer); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: {}", e.getMessage()); + } + }); } } diff --git a/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java b/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java index 81615f1b9b3b..da0dc8eda53c 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java +++ b/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java @@ -27,14 +27,11 @@ import java.security.InvalidParameterException; import lombok.Getter; -/** - * BarCustomer is a tenant with a name and a number of allowed calls per second. - */ +/** BarCustomer is a tenant with a name and a number of allowed calls per second. */ +@Getter public class BarCustomer { - @Getter private final String name; - @Getter private final int allowedCallsPerSecond; /** diff --git a/throttling/src/main/java/com/iluwatar/throttling/Bartender.java b/throttling/src/main/java/com/iluwatar/throttling/Bartender.java index 7653fcec07d0..a89a80c678cf 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/Bartender.java +++ b/throttling/src/main/java/com/iluwatar/throttling/Bartender.java @@ -30,8 +30,8 @@ import org.slf4j.LoggerFactory; /** - * Bartender is a service which accepts a BarCustomer (tenant) and throttles - * the resource based on the time given to the tenant. + * Bartender is a service which accepts a BarCustomer (tenant) and throttles the resource based on + * the time given to the tenant. */ class Bartender { @@ -45,6 +45,7 @@ public Bartender(Throttler timer, CallsCount callsCount) { /** * Orders a drink from the bartender. + * * @return customer id which is randomly generated */ public int orderDrink(BarCustomer barCustomer) { diff --git a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java index 0c84926c5fea..2545a9943087 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java +++ b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java @@ -29,11 +29,7 @@ import java.util.concurrent.atomic.AtomicLong; import lombok.extern.slf4j.Slf4j; -/** - * A class to keep track of the counter of different Tenants. - * - * @author drastogi - */ +/** A class to keep track of the counter of different Tenants. */ @Slf4j public final class CallsCount { private final Map tenantCallsCount = new ConcurrentHashMap<>(); @@ -66,9 +62,7 @@ public long getCount(String tenantName) { return tenantCallsCount.get(tenantName).get(); } - /** - * Resets the count of all the tenants in the map. - */ + /** Resets the count of all the tenants in the map. */ public void reset() { tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); LOGGER.info("reset counters"); diff --git a/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java index 8f77dc9882ba..5eff99486000 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java @@ -28,11 +28,7 @@ import java.util.Timer; import java.util.TimerTask; -/** - * Implementation of throttler interface. This class resets the counter every second. - * @author drastogi - * - */ +/** Implementation of throttler interface. This class resets the counter every second. */ public class ThrottleTimerImpl implements Throttler { private final int throttlePeriod; @@ -43,17 +39,18 @@ public ThrottleTimerImpl(int throttlePeriod, CallsCount callsCount) { this.callsCount = callsCount; } - /** - * A timer is initiated with this method. The timer runs every second and resets the - * counter. - */ + /** A timer is initiated with this method. The timer runs every second and resets the counter. */ @Override public void start() { - new Timer(true).schedule(new TimerTask() { - @Override - public void run() { - callsCount.reset(); - } - }, 0, throttlePeriod); + new Timer(true) + .schedule( + new TimerTask() { + @Override + public void run() { + callsCount.reset(); + } + }, + 0, + throttlePeriod); } } diff --git a/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java index 95f5b9f13ef7..2600155bed2c 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java @@ -24,11 +24,7 @@ */ package com.iluwatar.throttling.timer; -/** - * An interface for defining the structure of different types of throttling ways. - * @author drastogi - * - */ +/** An interface for defining the structure of different types of throttling ways. */ public interface Throttler { void start(); diff --git a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java index 73663ea46c51..3cff5c201481 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.throttling; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java b/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java index e21b1c930629..a2eb12ba0ea4 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java @@ -24,20 +24,17 @@ */ package com.iluwatar.throttling; -import org.junit.jupiter.api.Test; -import java.security.InvalidParameterException; - import static org.junit.jupiter.api.Assertions.assertThrows; -/** - * TenantTest to test the creation of Tenant with valid parameters. - */ +import java.security.InvalidParameterException; +import org.junit.jupiter.api.Test; + +/** TenantTest to test the creation of Tenant with valid parameters. */ class BarCustomerTest { @Test void constructorTest() { - assertThrows(InvalidParameterException.class, () -> { - new BarCustomer("sirBrave", -1, new CallsCount()); - }); + assertThrows( + InvalidParameterException.class, () -> new BarCustomer("sirBrave", -1, new CallsCount())); } } diff --git a/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java b/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java index 9d241eb6de1e..0fcb034a1563 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java @@ -30,9 +30,7 @@ import java.util.stream.IntStream; import org.junit.jupiter.api.Test; -/** - * B2BServiceTest class to test the B2BService - */ +/** B2BServiceTest class to test the B2BService */ class BartenderTest { private final CallsCount callsCount = new CallsCount(); @@ -40,7 +38,8 @@ class BartenderTest { @Test void dummyCustomerApiTest() { var tenant = new BarCustomer("pirate", 2, callsCount); - // In order to assure that throttling limits will not be reset, we use an empty throttling implementation + // In order to assure that throttling limits will not be reset, we use an empty throttling + // implementation var timer = (Throttler) () -> {}; var service = new Bartender(timer, callsCount); diff --git a/tolerant-reader/README.md b/tolerant-reader/README.md index 58fd9799e323..720764214dd4 100644 --- a/tolerant-reader/README.md +++ b/tolerant-reader/README.md @@ -1,215 +1,209 @@ --- -title: Tolerant Reader -category: Integration +title: "Tolerant Reader Pattern in Java: Enhancing API Resilience and Compatibility" +shortTitle: Tolerant Reader +description: "Discover how the Tolerant Reader pattern can boost your API's resilience by ignoring unrecognized data, ensuring backward compatibility and seamless integration. Learn through examples and best practices on implementing this robust communication mechanism." +category: Resilience language: en tag: - - Decoupling + - API design + - Decoupling + - Fault tolerance + - Integration --- -## Intent +## Also known as -Tolerant Reader is an integration pattern that helps creating robust communication systems. The idea -is to be as tolerant as possible when reading data from another service. This way, when the -communication schema changes, the readers must not break. +* Lenient Consumer -## Explanation +## Intent of Tolerant Reader Design Pattern -Real world example +The Tolerant Reader pattern enhances system resilience to changes in data structures by strategically ignoring unrecognized elements, promoting robust API design. -> We are persisting rainbowfish objects to file and later on they need to be restored. What makes it -> problematic is that rainbowfish data structure is versioned and evolves over time. New version of -> rainbowfish needs to be able to restore old versions as well. +## Detailed Explanation of Tolerant Reader Pattern with Real-World Examples + +Real-world example + +> Imagine a postal system that delivers letters and packages to recipients. In this system, postal workers deliver mail regardless of additional information or stickers that might be present on the envelopes or packages. If a package has extra labels or instructions that the postal system does not recognize, the postal worker ignores these and focuses only on the essential information like the address. This approach ensures that the delivery process remains functional even when senders use different formats or include unnecessary details, similar to how the Tolerant Reader pattern works in software by ignoring unrecognized data elements to maintain functionality and compatibility. In plain words -> Tolerant Reader pattern is used to create robust communication mechanisms between services. +> Utilize the Tolerant Reader pattern to establish robust and resilient communication between services, ensuring data compatibility and integration. [Robustness Principle](https://java-design-patterns.com/principles/#robustness-principle) says > Be conservative in what you do, be liberal in what you accept from others. -**Programmatic Example** +## Programmatic Example of Tolerant Reader Pattern in Java + +We are persisting `RainbowFish` objects to file. Later on they need to be restored. What makes it problematic is that `RainbowFish` data structure is versioned and evolves over time. New version of `RainbowFish` needs to be able to restore old versions as well. Here's the versioned `RainbowFish`. Notice how the second version introduces additional properties. ```java +@Getter +@RequiredArgsConstructor public class RainbowFish implements Serializable { - private static final long serialVersionUID = 1L; - - private final String name; - private final int age; - private final int lengthMeters; - private final int weightTons; - - /** - * Constructor. - */ - public RainbowFish(String name, int age, int lengthMeters, int weightTons) { - this.name = name; - this.age = age; - this.lengthMeters = lengthMeters; - this.weightTons = weightTons; - } - - public String getName() { - return name; - } - - public int getAge() { - return age; - } - - public int getLengthMeters() { - return lengthMeters; - } - - public int getWeightTons() { - return weightTons; - } + private static final long serialVersionUID = 1L; + + private final String name; + private final int age; + private final int lengthMeters; + private final int weightTons; } +``` +```java +@Getter public class RainbowFishV2 extends RainbowFish { - private static final long serialVersionUID = 1L; - - private boolean sleeping; - private boolean hungry; - private boolean angry; - - public RainbowFishV2(String name, int age, int lengthMeters, int weightTons) { - super(name, age, lengthMeters, weightTons); - } - - /** - * Constructor. - */ - public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping, - boolean hungry, boolean angry) { - this(name, age, lengthMeters, weightTons); - this.sleeping = sleeping; - this.hungry = hungry; - this.angry = angry; - } - - public boolean getSleeping() { - return sleeping; - } - - public boolean getHungry() { - return hungry; - } - - public boolean getAngry() { - return angry; - } + @Serial + private static final long serialVersionUID = 1L; + + private boolean sleeping; + private boolean hungry; + private boolean angry; + + public RainbowFishV2(String name, int age, int lengthMeters, int weightTons) { + super(name, age, lengthMeters, weightTons); + } + + public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping, + boolean hungry, boolean angry) { + this(name, age, lengthMeters, weightTons); + this.sleeping = sleeping; + this.hungry = hungry; + this.angry = angry; + } } ``` -Next we introduce the `RainbowFishSerializer`. This is the class that implements the Tolerant Reader -pattern. +Next we introduce the `RainbowFishSerializer`. This is the class that implements the Tolerant Reader pattern. ```java +@NoArgsConstructor public final class RainbowFishSerializer { - private RainbowFishSerializer() { - } - - public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException { - var map = Map.of( - "name", rainbowFish.getName(), - "age", String.format("%d", rainbowFish.getAge()), - "lengthMeters", String.format("%d", rainbowFish.getLengthMeters()), - "weightTons", String.format("%d", rainbowFish.getWeightTons()) - ); - - try (var fileOut = new FileOutputStream(filename); - var objOut = new ObjectOutputStream(fileOut)) { - objOut.writeObject(map); + public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException { + var map = Map.of( + "name", rainbowFish.getName(), + "age", String.format("%d", rainbowFish.getAge()), + "lengthMeters", String.format("%d", rainbowFish.getLengthMeters()), + "weightTons", String.format("%d", rainbowFish.getWeightTons()) + ); + + try (var fileOut = new FileOutputStream(filename); + var objOut = new ObjectOutputStream(fileOut)) { + objOut.writeObject(map); + } } - } - - public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException { - var map = Map.of( - "name", rainbowFish.getName(), - "age", String.format("%d", rainbowFish.getAge()), - "lengthMeters", String.format("%d", rainbowFish.getLengthMeters()), - "weightTons", String.format("%d", rainbowFish.getWeightTons()), - "angry", Boolean.toString(rainbowFish.getAngry()), - "hungry", Boolean.toString(rainbowFish.getHungry()), - "sleeping", Boolean.toString(rainbowFish.getSleeping()) - ); - - try (var fileOut = new FileOutputStream(filename); - var objOut = new ObjectOutputStream(fileOut)) { - objOut.writeObject(map); + + public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException { + var map = Map.of( + "name", rainbowFish.getName(), + "age", String.format("%d", rainbowFish.getAge()), + "lengthMeters", String.format("%d", rainbowFish.getLengthMeters()), + "weightTons", String.format("%d", rainbowFish.getWeightTons()), + "angry", Boolean.toString(rainbowFish.getAngry()), + "hungry", Boolean.toString(rainbowFish.getHungry()), + "sleeping", Boolean.toString(rainbowFish.getSleeping()) + ); + + try (var fileOut = new FileOutputStream(filename); + var objOut = new ObjectOutputStream(fileOut)) { + objOut.writeObject(map); + } } - } - public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException { - Map map; + public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException { + Map map; - try (var fileIn = new FileInputStream(filename); - var objIn = new ObjectInputStream(fileIn)) { - map = (Map) objIn.readObject(); - } + try (var fileIn = new FileInputStream(filename); + var objIn = new ObjectInputStream(fileIn)) { + map = (Map) objIn.readObject(); + } - return new RainbowFish( - map.get("name"), - Integer.parseInt(map.get("age")), - Integer.parseInt(map.get("lengthMeters")), - Integer.parseInt(map.get("weightTons")) - ); - } + return new RainbowFish( + map.get("name"), + Integer.parseInt(map.get("age")), + Integer.parseInt(map.get("lengthMeters")), + Integer.parseInt(map.get("weightTons")) + ); + } } ``` -And finally here's the full example in action. +And finally, here's the full example in action. ```java +public static void main(String[] args) throws IOException, ClassNotFoundException { + // Write V1 var fishV1 = new RainbowFish("Zed", 10, 11, 12); LOGGER.info("fishV1 name={} age={} length={} weight={}", fishV1.getName(), - fishV1.getAge(), fishV1.getLengthMeters(), fishV1.getWeightTons()); + fishV1.getAge(), fishV1.getLengthMeters(), fishV1.getWeightTons()); RainbowFishSerializer.writeV1(fishV1, "fish1.out"); - + // Read V1 var deserializedRainbowFishV1 = RainbowFishSerializer.readV1("fish1.out"); LOGGER.info("deserializedFishV1 name={} age={} length={} weight={}", - deserializedRainbowFishV1.getName(), deserializedRainbowFishV1.getAge(), - deserializedRainbowFishV1.getLengthMeters(), deserializedRainbowFishV1.getWeightTons()); - + deserializedRainbowFishV1.getName(), deserializedRainbowFishV1.getAge(), + deserializedRainbowFishV1.getLengthMeters(), deserializedRainbowFishV1.getWeightTons()); + // Write V2 var fishV2 = new RainbowFishV2("Scar", 5, 12, 15, true, true, true); LOGGER.info( - "fishV2 name={} age={} length={} weight={} sleeping={} hungry={} angry={}", - fishV2.getName(), fishV2.getAge(), fishV2.getLengthMeters(), fishV2.getWeightTons(), - fishV2.getHungry(), fishV2.getAngry(), fishV2.getSleeping()); + "fishV2 name={} age={} length={} weight={} sleeping={} hungry={} angry={}", + fishV2.getName(), fishV2.getAge(), fishV2.getLengthMeters(), fishV2.getWeightTons(), + fishV2.isHungry(), fishV2.isAngry(), fishV2.isSleeping()); RainbowFishSerializer.writeV2(fishV2, "fish2.out"); - + // Read V2 with V1 method var deserializedFishV2 = RainbowFishSerializer.readV1("fish2.out"); LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}", - deserializedFishV2.getName(), deserializedFishV2.getAge(), - deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons()); + deserializedFishV2.getName(), deserializedFishV2.getAge(), + deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons()); +} ``` Program output: ``` -fishV1 name=Zed age=10 length=11 weight=12 -deserializedFishV1 name=Zed age=10 length=11 weight=12 -fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true -deserializedFishV2 name=Scar age=5 length=12 weight=15 +15:38:00.602 [main] INFO com.iluwatar.tolerantreader.App -- fishV1 name=Zed age=10 length=11 weight=12 +15:38:00.618 [main] INFO com.iluwatar.tolerantreader.App -- deserializedFishV1 name=Zed age=10 length=11 weight=12 +15:38:00.618 [main] INFO com.iluwatar.tolerantreader.App -- fishV2 name=Scar age=5 length=12 weight=15 sleeping=true hungry=true angry=true +15:38:00.619 [main] INFO com.iluwatar.tolerantreader.App -- deserializedFishV2 name=Scar age=5 length=12 weight=15 ``` -## Class diagram +## When to Use the Tolerant Reader Pattern in Java + +* Apply the Tolerant Reader pattern when your system consumes data from evolving external sources, maintaining efficiency and data integrity. +* Applicable when backward compatibility is required in API design. +* Suitable for integration scenarios where different systems exchange data and evolve independently. + +## Real-World Applications of Tolerant Reader Pattern in Java + +* JSON or XML parsers that skip unknown elements. +* API clients in microservices architectures that interact with multiple versions of a service. + +## Benefits and Trade-offs of Tolerant Reader Pattern + +Benefits: + +* Increases the robustness and flexibility of the system. +* Allows independent evolution of producers and consumers in a distributed system. +* Simplifies versioning by enabling backward compatibility. -![alt text](./etc/tolerant_reader_urm.png "Tolerant Reader") +Trade-offs: -## Applicability +* May result in silent failures if important data is ignored. +* Can complicate debugging and tracing of issues due to missing or unrecognized data. -Use the Tolerant Reader pattern when +## Related Java Design Patterns -* The communication schema can evolve and change and yet the receiving side should not break +* [Adapter](https://java-design-patterns.com/patterns/adapter/): Both patterns deal with data transformation and integration, but the Adapter Pattern focuses on converting interfaces, while Tolerant Reader focuses on ignoring unrecognized data. +* [Facade](https://java-design-patterns.com/patterns/facade/): Simplifies interactions with complex systems, similar to how Tolerant Reader simplifies data consumption by ignoring irrelevant data. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Can be used in conjunction with Tolerant Reader to dynamically switch between different data handling strategies. -## Credits +## References and Credits -* [Martin Fowler - Tolerant Reader](http://martinfowler.com/bliki/TolerantReader.html) -* [Service Design Patterns: Fundamental Design Solutions for SOAP/WSDL and RESTful Web Services](https://www.amazon.com/gp/product/032154420X/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=032154420X&linkId=94f9516e747ac2b449a959d5b096c73c) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Service Design Patterns: Fundamental Design Solutions for SOAP/WSDL and RESTful Web Services](https://amzn.to/4dNIfOx) +* [Tolerant Reader (Martin Fowler)](http://martinfowler.com/bliki/TolerantReader.html) diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index c6716bb845cd..0053bf282249 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -34,6 +34,14 @@ tolerant-reader + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java index cf4a24a52bba..c7ced392c662 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java @@ -28,7 +28,7 @@ import lombok.extern.slf4j.Slf4j; /** - * Tolerant Reader is an integration pattern that helps creating robust communication systems. The + * Tolerant Reader is an integration pattern that helps to create robust communication systems. The * idea is to be as tolerant as possible when reading data from another service. This way, when the * communication schema changes, the readers must not break. * @@ -43,31 +43,44 @@ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) throws IOException, ClassNotFoundException { // Write V1 var fishV1 = new RainbowFish("Zed", 10, 11, 12); - LOGGER.info("fishV1 name={} age={} length={} weight={}", fishV1.getName(), - fishV1.getAge(), fishV1.getLengthMeters(), fishV1.getWeightTons()); + LOGGER.info( + "fishV1 name={} age={} length={} weight={}", + fishV1.getName(), + fishV1.getAge(), + fishV1.getLengthMeters(), + fishV1.getWeightTons()); RainbowFishSerializer.writeV1(fishV1, "fish1.out"); // Read V1 var deserializedRainbowFishV1 = RainbowFishSerializer.readV1("fish1.out"); - LOGGER.info("deserializedFishV1 name={} age={} length={} weight={}", - deserializedRainbowFishV1.getName(), deserializedRainbowFishV1.getAge(), - deserializedRainbowFishV1.getLengthMeters(), deserializedRainbowFishV1.getWeightTons()); + LOGGER.info( + "deserializedFishV1 name={} age={} length={} weight={}", + deserializedRainbowFishV1.getName(), + deserializedRainbowFishV1.getAge(), + deserializedRainbowFishV1.getLengthMeters(), + deserializedRainbowFishV1.getWeightTons()); // Write V2 var fishV2 = new RainbowFishV2("Scar", 5, 12, 15, true, true, true); LOGGER.info( "fishV2 name={} age={} length={} weight={} sleeping={} hungry={} angry={}", - fishV2.getName(), fishV2.getAge(), fishV2.getLengthMeters(), fishV2.getWeightTons(), - fishV2.isHungry(), fishV2.isAngry(), fishV2.isSleeping()); + fishV2.getName(), + fishV2.getAge(), + fishV2.getLengthMeters(), + fishV2.getWeightTons(), + fishV2.isHungry(), + fishV2.isAngry(), + fishV2.isSleeping()); RainbowFishSerializer.writeV2(fishV2, "fish2.out"); // Read V2 with V1 method var deserializedFishV2 = RainbowFishSerializer.readV1("fish2.out"); - LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}", - deserializedFishV2.getName(), deserializedFishV2.getAge(), - deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons()); + LOGGER.info( + "deserializedFishV2 name={} age={} length={} weight={}", + deserializedFishV2.getName(), + deserializedFishV2.getAge(), + deserializedFishV2.getLengthMeters(), + deserializedFishV2.getWeightTons()); } } diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java index 6429e64f3601..d6eae6a443fe 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java @@ -24,22 +24,20 @@ */ package com.iluwatar.tolerantreader; +import java.io.Serial; import java.io.Serializable; import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * RainbowFish is the initial schema. - */ +/** RainbowFish is the initial schema. */ @Getter @RequiredArgsConstructor public class RainbowFish implements Serializable { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private final String name; private final int age; private final int lengthMeters; private final int weightTons; - } diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java index 62410bde22f6..d96697df43e7 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java @@ -30,6 +30,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Map; +import lombok.NoArgsConstructor; /** * RainbowFishSerializer provides methods for reading and writing {@link RainbowFish} objects to @@ -37,59 +38,62 @@ * RainbowFish} objects. This way the reader does not break even though new properties are added to * the schema. */ +@NoArgsConstructor public final class RainbowFishSerializer { public static final String LENGTH_METERS = "lengthMeters"; public static final String WEIGHT_TONS = "weightTons"; - private RainbowFishSerializer() { - } - - /** - * Write V1 RainbowFish to file. - */ + /** Write V1 RainbowFish to file. */ public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException { - var map = Map.of( - "name", rainbowFish.getName(), - "age", String.format("%d", rainbowFish.getAge()), - LENGTH_METERS, String.format("%d", rainbowFish.getLengthMeters()), - WEIGHT_TONS, String.format("%d", rainbowFish.getWeightTons()) - ); + var map = + Map.of( + "name", + rainbowFish.getName(), + "age", + String.format("%d", rainbowFish.getAge()), + LENGTH_METERS, + String.format("%d", rainbowFish.getLengthMeters()), + WEIGHT_TONS, + String.format("%d", rainbowFish.getWeightTons())); try (var fileOut = new FileOutputStream(filename); - var objOut = new ObjectOutputStream(fileOut)) { + var objOut = new ObjectOutputStream(fileOut)) { objOut.writeObject(map); } } - /** - * Write V2 RainbowFish to file. - */ + /** Write V2 RainbowFish to file. */ public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException { - var map = Map.of( - "name", rainbowFish.getName(), - "age", String.format("%d", rainbowFish.getAge()), - LENGTH_METERS, String.format("%d", rainbowFish.getLengthMeters()), - WEIGHT_TONS, String.format("%d", rainbowFish.getWeightTons()), - "angry", Boolean.toString(rainbowFish.isAngry()), - "hungry", Boolean.toString(rainbowFish.isHungry()), - "sleeping", Boolean.toString(rainbowFish.isSleeping()) - ); + var map = + Map.of( + "name", + rainbowFish.getName(), + "age", + String.format("%d", rainbowFish.getAge()), + LENGTH_METERS, + String.format("%d", rainbowFish.getLengthMeters()), + WEIGHT_TONS, + String.format("%d", rainbowFish.getWeightTons()), + "angry", + Boolean.toString(rainbowFish.isAngry()), + "hungry", + Boolean.toString(rainbowFish.isHungry()), + "sleeping", + Boolean.toString(rainbowFish.isSleeping())); try (var fileOut = new FileOutputStream(filename); - var objOut = new ObjectOutputStream(fileOut)) { + var objOut = new ObjectOutputStream(fileOut)) { objOut.writeObject(map); } } - /** - * Read V1 RainbowFish from file. - */ + /** Read V1 RainbowFish from file. */ public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException { Map map; try (var fileIn = new FileInputStream(filename); - var objIn = new ObjectInputStream(fileIn)) { + var objIn = new ObjectInputStream(fileIn)) { map = (Map) objIn.readObject(); } @@ -97,7 +101,6 @@ public static RainbowFish readV1(String filename) throws IOException, ClassNotFo map.get("name"), Integer.parseInt(map.get("age")), Integer.parseInt(map.get(LENGTH_METERS)), - Integer.parseInt(map.get(WEIGHT_TONS)) - ); + Integer.parseInt(map.get(WEIGHT_TONS))); } } diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java index d8439aa25d49..78c49c16a369 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java @@ -24,15 +24,14 @@ */ package com.iluwatar.tolerantreader; +import java.io.Serial; import lombok.Getter; -/** - * RainbowFishV2 is the evolved schema. - */ +/** RainbowFishV2 is the evolved schema. */ @Getter public class RainbowFishV2 extends RainbowFish { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private boolean sleeping; private boolean hungry; @@ -42,15 +41,18 @@ public RainbowFishV2(String name, int age, int lengthMeters, int weightTons) { super(name, age, lengthMeters, weightTons); } - /** - * Constructor. - */ - public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping, - boolean hungry, boolean angry) { + /** Constructor. */ + public RainbowFishV2( + String name, + int age, + int lengthMeters, + int weightTons, + boolean sleeping, + boolean hungry, + boolean angry) { this(name, age, lengthMeters, weightTons); this.sleeping = sleeping; this.hungry = hungry; this.angry = angry; } - } diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java index 50adcdeda3aa..c9c2533f43fe 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java @@ -31,14 +31,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } @BeforeEach diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java index ae0eadbd66cd..8b1ebda936a8 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java @@ -34,38 +34,24 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -/** - * Date: 12/30/15 - 18:39 PM - * - * @author Jeroen Meulemeester - */ - +/** RainbowFishSerializerTest */ class RainbowFishSerializerTest { - /** - * Create a temporary folder, used to generate files in during this test - */ - @TempDir - static Path testFolder; + /** Create a temporary folder, used to generate files in during this test */ + @TempDir static Path testFolder; @BeforeEach void beforeEach() { assertTrue(Files.isDirectory(testFolder)); } - /** - * Rainbow fish version 1 used during the tests - */ + /** Rainbow fish version 1 used during the tests */ private static final RainbowFish V1 = new RainbowFish("version1", 1, 2, 3); - /** - * Rainbow fish version 2 used during the tests - */ + /** Rainbow fish version 2 used during the tests */ private static final RainbowFishV2 V2 = new RainbowFishV2("version2", 4, 5, 6, true, false, true); - /** - * Verify if a fish, written as version 1 can be read back as version 1 - */ + /** Verify if a fish, written as version 1 can be read back as version 1 */ @Test void testWriteV1ReadV1() throws Exception { final var outputPath = Files.createFile(testFolder.resolve("outputFile")); @@ -77,12 +63,9 @@ void testWriteV1ReadV1() throws Exception { assertEquals(V1.getAge(), fish.getAge()); assertEquals(V1.getLengthMeters(), fish.getLengthMeters()); assertEquals(V1.getWeightTons(), fish.getWeightTons()); - } - /** - * Verify if a fish, written as version 2 can be read back as version 1 - */ + /** Verify if a fish, written as version 2 can be read back as version 1 */ @Test void testWriteV2ReadV1() throws Exception { final var outputPath = Files.createFile(testFolder.resolve("outputFile2")); @@ -95,5 +78,4 @@ void testWriteV2ReadV1() throws Exception { assertEquals(V2.getLengthMeters(), fish.getLengthMeters()); assertEquals(V2.getWeightTons(), fish.getWeightTons()); } - } diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java index 44bef47eb803..bb250edc56cd 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java @@ -28,16 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/30/15 - 18:34 PM - * - * @author Jeroen Meulemeester - */ +/** RainbowFishTest */ class RainbowFishTest { - /** - * Verify if the getters of a {@link RainbowFish} return the expected values - */ + /** Verify if the getters of a {@link RainbowFish} return the expected values */ @Test void testValues() { final var fish = new RainbowFish("name", 1, 2, 3); @@ -46,5 +40,4 @@ void testValues() { assertEquals(2, fish.getLengthMeters()); assertEquals(3, fish.getWeightTons()); } - -} \ No newline at end of file +} diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java index 49c9912de478..1c478f2a31ab 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java @@ -30,16 +30,10 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/30/15 - 18:35 PM - * - * @author Jeroen Meulemeester - */ +/** RainbowFishV2Test */ class RainbowFishV2Test { - /** - * Verify if the getters of a {@link RainbowFish} return the expected values - */ + /** Verify if the getters of a {@link RainbowFish} return the expected values */ @Test void testValues() { final var fish = new RainbowFishV2("name", 1, 2, 3, false, true, false); @@ -51,5 +45,4 @@ void testValues() { assertTrue(fish.isHungry()); assertFalse(fish.isAngry()); } - -} \ No newline at end of file +} diff --git a/trampoline/README.md b/trampoline/README.md index 8316351dd828..cb4162bafe6d 100644 --- a/trampoline/README.md +++ b/trampoline/README.md @@ -1,53 +1,46 @@ --- -title: Trampoline -category: Behavioral +title: "Trampoline Pattern in Java: Mastering Recursion Without Stack Overflow" +shortTitle: Trampoline +description: "Discover how to implement the Trampoline pattern in Java to efficiently manage recursive functions and prevent stack overflow errors, with real-world examples and programming insights." +category: Functional language: en tag: - - Performance + - Code simplification + - Functional decomposition + - Performance + - Recursion --- -## Intent +## Also known as -Trampoline pattern is used for implementing algorithms recursively in Java without blowing the stack -and to interleave the execution of functions without hard coding them together. +* Bounce +* Tail-Call Optimization -## Explanation +## Intent of Trampoline Design Pattern -Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer -style. For example, calculating Fibonacci accumulating sum and factorials. In these kinds of -problems, recursion is more straightforward than its loop counterpart. Furthermore, recursion may -need less code and looks more concise. There is a saying that every recursion problem can be solved -using a loop with the cost of writing code that is more difficult to understand. +The Trampoline Pattern in Java optimizes recursive function calls by converting them into iterative loops, avoiding stack overflow errors. -However, recursion-type solutions have one big caveat. For each recursive call, it typically needs -an intermediate value stored and there is a limited amount of stack memory available. Running out of -stack memory creates a stack overflow error and halts the program execution. - -Trampoline pattern is a trick that allows defining recursive algorithms in Java without blowing the -stack. +## Detailed Explanation of Trampoline Pattern with Real-World Examples Real-world example -> A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern. +> Imagine you are organizing a relay race. Each runner passes the baton to the next runner until the race is complete. However, if each runner had to physically run back to the starting line to pass the baton to the next runner, the race would be inefficient and error-prone. Instead, runners pass the baton directly to the next runner in line, who continues the race seamlessly. +> +> The Trampoline pattern in programming works similarly by ensuring that each recursive step is handed off efficiently without having to return to the start, preventing a stack overflow (similar to our runners never having to backtrack). In plain words -> Trampoline pattern allows recursion without running out of stack memory. +> The Trampoline pattern in Java allows efficient recursion without running out of stack memory, optimizing deep recursive calls for better performance and stack safety. Wikipedia says -> In Java, trampoline refers to using reflection to avoid using inner classes, for example in event -> listeners. The time overhead of a reflection call is traded for the space overhead of an inner -> class. Trampolines in Java usually involve the creation of a GenericListener to pass events to -> an outer class. +> In Java, trampoline refers to using reflection to avoid using inner classes, for example in event listeners. The time overhead of a reflection call is traded for the space overhead of an inner class. Trampolines in Java usually involve the creation of a GenericListener to pass events to an outer class. -**Programmatic Example** +## Programmatic Example of Trampoline Pattern in Java Here's the `Trampoline` implementation in Java. -When `get` is called on the returned Trampoline, internally it will iterate calling `jump` on the -returned `Trampoline` as long as the concrete instance returned is `Trampoline`, stopping once the -returned instance is `done`. +When `get` is called on the returned Trampoline, internally it will iterate calling `jump` on the returned `Trampoline` as long as the concrete instance returned is `Trampoline`, stopping once the returned instance is `done`. ```java public interface Trampoline { @@ -102,17 +95,22 @@ public interface Trampoline { Using the `Trampoline` to get Fibonacci values. ```java -public static void main(String[] args) { - LOGGER.info("Start calculating war casualties"); - var result = loop(10, 1).result(); - LOGGER.info("The number of orcs perished in the war: {}", result); -} +@Slf4j +public class TrampolineApp { + + public static void main(String[] args) { + LOGGER.info("Start calculating war casualties"); + var result = loop(10, 1).result(); + LOGGER.info("The number of orcs perished in the war: {}", result); -public static Trampoline loop(int times, int prod) { - if (times == 0) { - return Trampoline.done(prod); - } else { - return Trampoline.more(() -> loop(times - 1, prod * times)); + } + + public static Trampoline loop(int times, int prod) { + if (times == 0) { + return Trampoline.done(prod); + } else { + return Trampoline.more(() -> loop(times - 1, prod * times)); + } } } ``` @@ -124,27 +122,49 @@ Program output: 19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800 ``` -## Class diagram +## When to Use the Trampoline Pattern in Java -![alt text](./etc/trampoline.urm.png "Trampoline pattern class diagram") +Use the Trampoline pattern when -## Applicability +* When dealing with algorithms that use recursion heavily and risk running into stack overflow errors. +* When tail-call optimization is not supported by the Java language natively. -Use the Trampoline pattern when +## Trampoline Pattern Java Tutorials -* For implementing tail-recursive functions. This pattern allows to switch on a stackless operation. -* For interleaving execution of two or more functions on the same thread. +* [Laziness, trampolines, monoids and other functional amenities: This is not your father's Java(Mario Fusco)](https://www.slideshare.net/mariofusco/lazine) +* [Trampoline.java (totallylazy)](https://github.com/bodar/totallylazy/blob/master/src/com/googlecode/totallylazy/Trampoline.java) +* [Trampoline: Java Glossary (mindprod.com)](http://mindprod.com/jgloss/trampoline.html) +* [Trampolining: A practical guide for awesome Java Developers (John McClean)](https://medium.com/@johnmcclean/trampolining-a-practical-guide-for-awesome-java-developers-4b657d9c3076) +* [What is a trampoline function? (Stack Overflow)](https://stackoverflow.com/questions/189725/what-is-a-trampoline-function) -## Known uses +## Real-World Applications of Trampoline Pattern in Java +* Implementing algorithms that require deep recursion, such as certain tree traversals, combinatorial algorithms, and mathematical computations. +* Functional programming libraries and frameworks where tail-call optimization is necessary for performance and stack safety. * [cyclops-react](https://github.com/aol/cyclops-react) -## Credits +## Benefits and Trade-offs of Trampoline Pattern + +Benefits: + +* Prevents stack overflow by converting deep recursion into iteration. +* Enhances performance by avoiding the overhead of deep recursive calls. +* Simplifies the code by making recursive calls look like a sequence of steps. + +Trade-offs: + +* May introduce additional complexity in terms of understanding and implementing the trampoline mechanism. +* Requires converting naturally recursive algorithms into a continuation-passing style. + +## Related Java Design Patterns + +* [Iterator](https://java-design-patterns.com/patterns/iterator/): Both patterns aim to transform potentially recursive operations into iterative processes, though the iterator pattern is more general-purpose. +* [State](https://java-design-patterns.com/patterns/state/): Like the Trampoline, the State pattern can also handle complex state transitions, which can sometimes involve recursive-like state changes. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): This pattern can be related in terms of defining a family of algorithms (or continuations in the case of the Trampoline) and making them interchangeable. + +## References and Credits -* [Trampolining: a practical guide for awesome Java Developers](https://medium.com/@johnmcclean/trampolining-a-practical-guide-for-awesome-java-developers-4b657d9c3076) -* [Trampoline in java ](http://mindprod.com/jgloss/trampoline.html) -* [Laziness, trampolines, monoids and other functional amenities: this is not your father's Java](https://www.slideshare.net/mariofusco/lazine) -* [Trampoline implementation](https://github.com/bodar/totallylazy/blob/master/src/com/googlecode/totallylazy/Trampoline.java) -* [What is a trampoline function?](https://stackoverflow.com/questions/189725/what-is-a-trampoline-function) -* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://www.amazon.com/gp/product/1617293563/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617293563&linkId=ad53ae6f9f7c0982e759c3527bd2595c) -* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://www.amazon.com/gp/product/1617291994/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617291994&linkId=e3e5665b0732c59c9d884896ffe54f4f) +* [Functional Programming in Java](https://amzn.to/3JUIc5Q) +* [Functional Programming for Java Developers: Tools for Better Concurrency, Abstraction, and Agility](https://amzn.to/4dRu4rJ) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3QCmGXs) +* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://amzn.to/3yxdu0g) diff --git a/trampoline/pom.xml b/trampoline/pom.xml index 61ddcc0401fb..6f58141557c0 100644 --- a/trampoline/pom.xml +++ b/trampoline/pom.xml @@ -34,6 +34,14 @@ trampoline + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java index 2dd0bc171ff6..e73f54f2e697 100644 --- a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java +++ b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java @@ -29,19 +29,18 @@ /** * Trampoline pattern allows to define recursive algorithms by iterative loop. * - *

      When get is called on the returned Trampoline, internally it will iterate calling ‘jump’ - * on the returned Trampoline as long as the concrete instance returned is {@link - * #more(Trampoline)}, stopping once the returned instance is {@link #done(Object)}. + *

      When get is called on the returned Trampoline, internally it will iterate calling ‘jump’ on + * the returned Trampoline as long as the concrete instance returned is {@link #more(Trampoline)}, + * stopping once the returned instance is {@link #done(Object)}. * - *

      Essential we convert looping via recursion into iteration, - * the key enabling mechanism is the fact that {@link #more(Trampoline)} is a lazy operation. + *

      Essential we convert looping via recursion into iteration, the key enabling mechanism is the + * fact that {@link #more(Trampoline)} is a lazy operation. * - * @param is type for returning result. + * @param is type for returning result. */ public interface Trampoline { T get(); - /** * Jump to next stage. * @@ -51,7 +50,6 @@ default Trampoline jump() { return this; } - default T result() { return get(); } @@ -75,7 +73,6 @@ static Trampoline done(final T result) { return () -> result; } - /** * Create a Trampoline that has more work to do. * diff --git a/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java b/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java index 37c83c998cfb..a6b6a60e8a71 100644 --- a/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java +++ b/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java @@ -29,26 +29,20 @@ /** * Trampoline pattern allows to define recursive algorithms by iterative loop. * - *

      It is possible to implement algorithms recursively in Java without blowing the stack - * and to interleave the execution of functions without hard coding them together or even using - * threads. + *

      It is possible to implement algorithms recursively in Java without blowing the stack and to + * interleave the execution of functions without hard coding them together or even using threads. */ @Slf4j public class TrampolineApp { - /** - * Main program for showing pattern. It does loop with factorial function. - */ + /** Main program for showing pattern. It does loop with factorial function. */ public static void main(String[] args) { LOGGER.info("Start calculating war casualties"); var result = loop(10, 1).result(); LOGGER.info("The number of orcs perished in the war: {}", result); - } - /** - * Manager for pattern. Define it with a factorial function. - */ + /** Manager for pattern. Define it with a factorial function. */ public static Trampoline loop(int times, int prod) { if (times == 0) { return Trampoline.done(prod); diff --git a/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java b/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java index 71db6c582d57..6b55849463d3 100644 --- a/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java +++ b/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test for trampoline pattern. - */ +/** Test for trampoline pattern. */ class TrampolineAppTest { @Test @@ -38,5 +36,4 @@ void testTrampolineWithFactorialFunction() { long result = TrampolineApp.loop(10, 1).result(); assertEquals(3_628_800, result); } - -} \ No newline at end of file +} diff --git a/transaction-script/README.md b/transaction-script/README.md index fea19a412214..dacc95a0449a 100644 --- a/transaction-script/README.md +++ b/transaction-script/README.md @@ -1,28 +1,42 @@ --- -title: Transaction Script -category: Behavioral +title: "Transaction Script Pattern in Java: Simplifying Business Logic with Consolidated Scripts" +shortTitle: Transaction Script +description: "Explore the Transaction Script design pattern for Java applications. Learn how to organize simple business logic into efficient scripts with real-world examples and improve your coding efficiency." +category: Data access language: en tag: - - Data access + - Business + - Data access + - Domain + - Persistence + - Transactions --- -## Intent +## Also known as -Transaction Script organizes business logic by procedures where each procedure handles a single -request from the presentation. +* Scripted Transactions -## Explanation +## Intent of Transaction Script Design Pattern -Real world example +The Transaction Script pattern in Java organizes business logic by procedures where each procedure handles a single request from the presentation. -> You need to create a hotel room booking system. Since the requirements are quite simple we intend -> to use the Transaction Script pattern here. +## Detailed Explanation of Transaction Script Pattern with Real-World Examples + +Real-world example + +> An analogous real-world example of the Transaction Script design pattern can be seen in a bank teller's daily operations. Imagine a bank where each transaction, such as depositing money, withdrawing cash, or transferring funds between accounts, is handled by a specific script-like procedure. Each procedure takes in the necessary details (e.g., account numbers, amounts) and performs the transaction in a straightforward, step-by-step manner. This ensures that each transaction is processed correctly and independently, without the need for a complex system of rules and interactions. This simple, organized approach allows bank tellers to efficiently manage a variety of transactions throughout the day. In plain words > Transaction Script organizes business logic into transactions that the system needs to carry out. -Programmatic example +Wikipedia says + +> The Transaction Script design pattern is a straightforward way to organize business logic in applications, particularly suitable for scenarios where each request from the presentation layer can be handled by a single procedure. This pattern is often used in simple applications or in systems where rapid development and ease of understanding are crucial. Each transaction script is responsible for a particular task, such as processing an order or calculating a result, and typically interacts directly with the database. + +## Programmatic Example of Transaction Script Pattern in Java + +Our Transaction Script pattern in Java example is about booking hotel rooms. The `Hotel` class takes care of booking and cancelling room reservations. @@ -76,42 +90,186 @@ public class Hotel { } ``` -The `Hotel` class has two methods, one for booking and cancelling a room respectively. Each one of -them handles a single transaction in the system, making `Hotel` implement the Transaction Script -pattern. +The `Hotel` class has two methods, one for booking and cancelling a room respectively. Each one of them handles a single transaction in the system, making `Hotel` implement the Transaction Script pattern. + +The `bookRoom` method consolidates all the needed steps like checking if the room is already booked or not, if not booked then books the room and updates the database by using the DAO. + +The `cancelRoom` method consolidates steps like checking if the room is booked or not, if booked then calculates the refund amount and updates the database using the DAO. + +Here is the `App` class with `main` method for running the example. + +```java +public class App { + + private static final String H2_DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) throws Exception { + + final var dataSource = createDataSource(); + deleteSchema(dataSource); + createSchema(dataSource); + final var dao = new HotelDaoImpl(dataSource); + + // Add rooms + addRooms(dao); + + // Print room booking status + getRoomStatus(dao); + + var hotel = new Hotel(dao); + + // Book rooms + hotel.bookRoom(1); + hotel.bookRoom(2); + hotel.bookRoom(3); + hotel.bookRoom(4); + hotel.bookRoom(5); + hotel.bookRoom(6); + + // Cancel booking for a few rooms + hotel.cancelRoomBooking(1); + hotel.cancelRoomBooking(3); + hotel.cancelRoomBooking(5); + + getRoomStatus(dao); + + deleteSchema(dataSource); + } + + private static void getRoomStatus(HotelDaoImpl dao) throws Exception { + try (var customerStream = dao.getAll()) { + customerStream.forEach((customer) -> LOGGER.info(customer.toString())); + } + } + + private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); + } + } + + private static void createSchema(DataSource dataSource) throws Exception { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); + } catch (Exception e) { + throw new Exception(e.getMessage(), e); + } + } + + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setUrl(H2_DB_URL); + return dataSource; + } + + private static void addRooms(HotelDaoImpl hotelDao) throws Exception { + for (var room : generateSampleRooms()) { + hotelDao.add(room); + } + } + + private static List generateSampleRooms() { + final var room1 = new Room(1, "Single", 50, false); + final var room2 = new Room(2, "Double", 80, false); + final var room3 = new Room(3, "Queen", 120, false); + final var room4 = new Room(4, "King", 150, false); + final var room5 = new Room(5, "Single", 50, false); + final var room6 = new Room(6, "Double", 80, false); + return List.of(room1, room2, room3, room4, room5, room6); + } +} +``` + +The `App.java` file has the `main` entry point of the application. It demonstrates the use of the Transaction Script pattern in a hotel management context. Here's a step-by-step breakdown of what happens: + +1. A new H2 database data source is created using the `createDataSource()` method. This data source is used to interact with the in-memory H2 database. + +2. The existing schema (if any) in the database is deleted using the `deleteSchema(dataSource)` method. This is done to ensure a clean state before the application starts. + +3. A new schema is created in the database using the `createSchema(dataSource)` method. The schema includes the necessary tables and relationships for the application. + +4. An instance of `HotelDaoImpl` is created, which serves as the Data Access Object (DAO). This object is responsible for handling the database operations related to the hotel rooms. + +5. Sample rooms are added to the hotel using the `addRooms(dao)` method. This method generates a list of sample rooms and adds them to the hotel via the DAO. + +6. The booking status of all rooms is printed using the `getRoomStatus(dao)` method. + +7. An instance of `Hotel` is created with the DAO as a parameter. This object represents the hotel and provides methods to book and cancel room bookings. + +8. Several rooms are booked using the `hotel.bookRoom(roomNumber)` method. + +9. Some of the bookings are then cancelled using the `hotel.cancelRoomBooking(roomNumber)` method. + +10. The booking status of all rooms is printed again to reflect the changes. + +11. Finally, the schema in the database is deleted again using the `deleteSchema(dataSource)` method, cleaning up the state of the database. + +Console output: + +``` +14:22:20.050 [main] INFO com.iluwatar.transactionscript.App -- Room(id=1, roomType=Single, price=50, booked=false) +14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=2, roomType=Double, price=80, booked=false) +14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=3, roomType=Queen, price=120, booked=false) +14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=4, roomType=King, price=150, booked=false) +14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=5, roomType=Single, price=50, booked=false) +14:22:20.051 [main] INFO com.iluwatar.transactionscript.App -- Room(id=6, roomType=Double, price=80, booked=false) +14:22:20.058 [main] INFO com.iluwatar.transactionscript.Hotel -- Booking cancelled for room number: 1 +14:22:20.058 [main] INFO com.iluwatar.transactionscript.Hotel -- 50 is refunded +14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- Booking cancelled for room number: 3 +14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- 120 is refunded +14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- Booking cancelled for room number: 5 +14:22:20.059 [main] INFO com.iluwatar.transactionscript.Hotel -- 50 is refunded +14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=1, roomType=Single, price=50, booked=false) +14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=2, roomType=Double, price=80, booked=true) +14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=3, roomType=Queen, price=120, booked=false) +14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=4, roomType=King, price=150, booked=true) +14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=5, roomType=Single, price=50, booked=false) +14:22:20.060 [main] INFO com.iluwatar.transactionscript.App -- Room(id=6, roomType=Double, price=80, booked=true) +``` + +This pattern is suitable for simple business logic and can be easily understood and maintained. + +## When to Use the Transaction Script Pattern in Java + +* Use when business logic is simple and can be easily organized into individual procedures. +* Suitable for applications with simple transaction requirements or where the logic doesn't justify complex architectures like Domain Model. + +## Transaction Script Pattern Java Tutorials -The `bookRoom` method consolidates all the needed steps like checking if the room is already booked -or not, if not booked then books the room and updates the database by using the DAO. +* [Transaction Script Pattern (DZone)](https://dzone.com/articles/transaction-script-pattern#:~:text=Transaction%20Script%20(TS)%20is%20the,need%20big%20architecture%20behind%20them.) +* [Transaction Script (InformIT)](https://www.informit.com/articles/article.aspx?p=1398617) -The `cancelRoom` method consolidates steps like checking if the room is booked or not, -if booked then calculates the refund amount and updates the database using the DAO. +## Real-World Applications of Transaction Script Pattern in Java -## Class diagram +* Early-stage startups and small-scale applications where rapid development is crucial. +* Enterprise applications with well-defined procedures like banking transactions or e-commerce order processing. +* Legacy systems where business logic is already written as scripts. -![alt text](./etc/transaction-script.png "Transaction script model") +## Benefits and Trade-offs of Transaction Script Pattern -## Applicability +Benefits: -Use the Transaction Script pattern when the application has only a small amount of logic and that -logic won't be extended in the future. +* Leveraging the Transaction Script pattern enhances code simplicity and accelerates development cycles, especially in startup environments. +* Simple and straightforward to implement. +* Easy to understand and maintain for straightforward business logic. +* Fast development cycle for small applications. -## Consequences +Trade-offs: -* As the business logic gets more complicated, -it gets progressively harder to keep the transaction script -in a well-designed state. -* Code duplication between transaction scripts can occur. -* Normally not easy to refactor transactions script to other domain logic -patterns. +* Can lead to duplicated code if not carefully managed. +* Not suitable for complex business logic; can become unmanageable as the application grows. +* Harder to test in isolation compared to more structured approaches like Domain Model. -## Related patterns +## Related Java Design Patterns -* Domain Model -* Table Module -* Service Layer +* [Domain Model](https://java-design-patterns.com/patterns/domain-model/): Unlike Transaction Script, Domain Model organizes business logic around the data model and is better suited for complex business rules. +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/): Often used together with Transaction Script to define an application's boundary and encapsulate the business logic. +* [Table Module](https://java-design-patterns.com/patterns/table-module/): Similar to Transaction Script but organizes logic using a single class per table rather than a procedure per request. -## Credits +## References and Credits -* [Transaction Script Pattern](https://dzone.com/articles/transaction-script-pattern#:~:text=Transaction%20Script%20(TS)%20is%20the,need%20big%20architecture%20behind%20them.) -* [Transaction Script](https://www.informit.com/articles/article.aspx?p=1398617) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/transaction-script/pom.xml b/transaction-script/pom.xml index af6d4571a4f3..9246dbd32aaf 100644 --- a/transaction-script/pom.xml +++ b/transaction-script/pom.xml @@ -34,6 +34,14 @@ 4.0.0 transaction-script + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.h2database h2 diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java index 909943ce41d4..41d8cc4982f6 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java @@ -31,18 +31,18 @@ import org.slf4j.LoggerFactory; /** - * Transaction Script (TS) is one of the simplest domain logic pattern. - * It needs less work to implement than other domain logic patterns and therefore - * it’s perfect fit for smaller applications that don't need big architecture behind them. + * Transaction Script (TS) is one of the simplest domain logic pattern. It needs less work to + * implement than other domain logic patterns, and therefore it’s perfect fit for smaller + * applications that don't need big architecture behind them. * - *

      In this example we will use the TS pattern to implement booking and cancellation - * methods for a Hotel management App. The main method will initialise an instance of - * {@link Hotel} and add rooms to it. After that it will book and cancel a couple of rooms - * and that will be printed by the logger.

      + *

      In this example we will use the TS pattern to implement booking and cancellation methods for a + * Hotel management App. The main method will initialise an instance of {@link Hotel} and add rooms + * to it. After that it will book and cancel a couple of rooms and that will be printed by the + * logger. * - *

      The thing we have to note here is that all the operations related to booking or cancelling - * a room like checking the database if the room exists, checking the booking status or the - * room, calculating refund price are all clubbed inside a single transaction script method.

      + *

      The thing we have to note here is that all the operations related to booking or cancelling a + * room like checking the database if the room exists, checking the booking status or the room, + * calculating refund price are all clubbed inside a single transaction script method. */ public class App { @@ -50,9 +50,9 @@ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Program entry point. - * Initialises an instance of Hotel and adds rooms to it. - * Carries out booking and cancel booking transactions. + * Program entry point. Initialises an instance of Hotel and adds rooms to it. Carries out booking + * and cancel booking transactions. + * * @param args command line arguments * @throws Exception if any error occurs */ @@ -87,7 +87,6 @@ public static void main(String[] args) throws Exception { getRoomStatus(dao); deleteSchema(dataSource); - } private static void getRoomStatus(HotelDaoImpl dao) throws Exception { @@ -98,14 +97,14 @@ private static void getRoomStatus(HotelDaoImpl dao) throws Exception { private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); } } private static void createSchema(DataSource dataSource) throws Exception { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); } catch (Exception e) { throw new Exception(e.getMessage(), e); diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java index f0d077952c89..0e1ae0d7b94e 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Hotel class to implement TS pattern. - */ +/** Hotel class to implement TS pattern. */ @Slf4j public class Hotel { diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java index b698ab114aa0..8675c50989e0 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java @@ -27,9 +27,7 @@ import java.util.Optional; import java.util.stream.Stream; -/** - * DAO interface for hotel transactions. - */ +/** DAO interface for hotel transactions. */ public interface HotelDao { Stream getAll() throws Exception; diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java index e64ad15489b8..13173a01f041 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java @@ -36,9 +36,7 @@ import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; -/** - * Implementation of database operations for Hotel class. - */ +/** Implementation of database operations for Hotel class. */ @Slf4j public class HotelDaoImpl implements HotelDao { @@ -54,28 +52,31 @@ public Stream getAll() throws Exception { var connection = getConnection(); var statement = connection.prepareStatement("SELECT * FROM ROOMS"); // NOSONAR var resultSet = statement.executeQuery(); // NOSONAR - return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, - Spliterator.ORDERED) { - - @Override - public boolean tryAdvance(Consumer action) { - try { - if (!resultSet.next()) { - return false; - } - action.accept(createRoom(resultSet)); - return true; - } catch (Exception e) { - throw new RuntimeException(e); // NOSONAR - } - } - }, false).onClose(() -> { - try { - mutedClose(connection, statement, resultSet); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - }); + return StreamSupport.stream( + new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + + @Override + public boolean tryAdvance(Consumer action) { + try { + if (!resultSet.next()) { + return false; + } + action.accept(createRoom(resultSet)); + return true; + } catch (Exception e) { + throw new RuntimeException(e); // NOSONAR + } + } + }, + false) + .onClose( + () -> { + try { + mutedClose(connection, statement, resultSet); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + }); } catch (Exception e) { throw new Exception(e.getMessage(), e); } @@ -86,7 +87,7 @@ public Optional getById(int id) throws Exception { ResultSet resultSet = null; try (var connection = getConnection(); - var statement = connection.prepareStatement("SELECT * FROM ROOMS WHERE ID = ?")) { + var statement = connection.prepareStatement("SELECT * FROM ROOMS WHERE ID = ?")) { statement.setInt(1, id); resultSet = statement.executeQuery(); @@ -111,7 +112,7 @@ public Boolean add(Room room) throws Exception { } try (var connection = getConnection(); - var statement = connection.prepareStatement("INSERT INTO ROOMS VALUES (?,?,?,?)")) { + var statement = connection.prepareStatement("INSERT INTO ROOMS VALUES (?,?,?,?)")) { statement.setInt(1, room.getId()); statement.setString(2, room.getRoomType()); statement.setInt(3, room.getPrice()); @@ -126,10 +127,9 @@ public Boolean add(Room room) throws Exception { @Override public Boolean update(Room room) throws Exception { try (var connection = getConnection(); - var statement = - connection - .prepareStatement("UPDATE ROOMS SET ROOM_TYPE = ?, PRICE = ?, BOOKED = ?" - + " WHERE ID = ?")) { + var statement = + connection.prepareStatement( + "UPDATE ROOMS SET ROOM_TYPE = ?, PRICE = ?, BOOKED = ?" + " WHERE ID = ?")) { statement.setString(1, room.getRoomType()); statement.setInt(2, room.getPrice()); statement.setBoolean(3, room.isBooked()); @@ -143,7 +143,7 @@ public Boolean update(Room room) throws Exception { @Override public Boolean delete(Room room) throws Exception { try (var connection = getConnection(); - var statement = connection.prepareStatement("DELETE FROM ROOMS WHERE ID = ?")) { + var statement = connection.prepareStatement("DELETE FROM ROOMS WHERE ID = ?")) { statement.setInt(1, room.getId()); return statement.executeUpdate() > 0; } catch (Exception e) { @@ -167,7 +167,8 @@ private void mutedClose(Connection connection, PreparedStatement statement, Resu } private Room createRoom(ResultSet resultSet) throws Exception { - return new Room(resultSet.getInt("ID"), + return new Room( + resultSet.getInt("ID"), resultSet.getString("ROOM_TYPE"), resultSet.getInt("PRICE"), resultSet.getBoolean("BOOKED")); diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java index 93716d2a0ba2..ea0a8e1d3b11 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java @@ -30,9 +30,7 @@ import lombok.Setter; import lombok.ToString; -/** - * A room POJO that represents the data that will be read from the data source. - */ +/** A room POJO that represents the data that will be read from the data source. */ @Setter @Getter @ToString @@ -44,5 +42,4 @@ public class Room { private String roomType; private int price; private boolean booked; - } diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java index 29cad58620ef..08d581fc7699 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java @@ -24,16 +24,12 @@ */ package com.iluwatar.transactionscript; -/** - * Customer Schema SQL Class. - */ +/** Customer Schema SQL Class. */ public final class RoomSchemaSql { public static final String CREATE_SCHEMA_SQL = "CREATE TABLE ROOMS (ID NUMBER, ROOM_TYPE VARCHAR(100), PRICE INT, BOOKED VARCHAR(100))"; public static final String DELETE_SCHEMA_SQL = "DROP TABLE ROOMS IF EXISTS"; - private RoomSchemaSql() { - } - + private RoomSchemaSql() {} } diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java index ac33b852dc63..291c6847bf31 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.transactionscript; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Transaction script example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Transaction script example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java index 2f587397d873..cce65f594170 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java @@ -44,9 +44,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -/** - * Tests {@link HotelDaoImpl}. - */ +/** Tests {@link HotelDaoImpl}. */ class HotelDaoImplTest { private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; @@ -61,15 +59,13 @@ class HotelDaoImplTest { @BeforeEach void createSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); } } - /** - * Represents the scenario where DB connectivity is present. - */ + /** Represents the scenario where DB connectivity is present. */ @Nested class ConnectionSuccess { @@ -87,9 +83,7 @@ void setUp() throws Exception { Assertions.assertTrue(result); } - /** - * Represents the scenario when DAO operations are being performed on a non existing room. - */ + /** Represents the scenario when DAO operations are being performed on a non-existing room. */ @Nested class NonExistingRoom { @@ -135,8 +129,7 @@ void retrieveShouldReturnNoRoom() throws Exception { } /** - * Represents a scenario where DAO operations are being performed on an already existing - * room. + * Represents a scenario where DAO operations are being performed on an already existing room. */ @Nested class ExistingRoom { @@ -161,8 +154,8 @@ void deletionShouldBeSuccessAndRoomShouldBeNonAccessible() throws Exception { } @Test - void updationShouldBeSuccessAndAccessingTheSameRoomShouldReturnUpdatedInformation() throws - Exception { + void updationShouldBeSuccessAndAccessingTheSameRoomShouldReturnUpdatedInformation() + throws Exception { final var newRoomType = "Double"; final var newPrice = 80; final var newBookingStatus = false; @@ -209,16 +202,12 @@ private DataSource mockedDatasource() throws SQLException { @Test void addingARoomFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.add(new Room(2, "Double", 80, false)); - }); + assertThrows(Exception.class, () -> dao.add(new Room(2, "Double", 80, false))); } @Test void deletingARoomFailsWithExceptionAsFeedbackToTheClient() { - assertThrows(Exception.class, () -> { - dao.delete(existingRoom); - }); + assertThrows(Exception.class, () -> dao.delete(existingRoom)); } @Test @@ -226,25 +215,21 @@ void updatingARoomFailsWithFeedbackToTheClient() { final var newRoomType = "Double"; final var newPrice = 80; final var newBookingStatus = false; - assertThrows(Exception.class, () -> { - dao.update(new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus)); - }); + assertThrows( + Exception.class, + () -> + dao.update(new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus))); } @Test void retrievingARoomByIdFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.getById(existingRoom.getId()); - }); + assertThrows(Exception.class, () -> dao.getById(existingRoom.getId())); } @Test void retrievingAllRoomsFailsWithExceptionAsFeedbackToClient() { - assertThrows(Exception.class, () -> { - dao.getAll(); - }); + assertThrows(Exception.class, () -> dao.getAll()); } - } /** @@ -255,7 +240,7 @@ void retrievingAllRoomsFailsWithExceptionAsFeedbackToClient() { @AfterEach void deleteSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); } } diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java index e8a7f6a0b8e4..b555cce8ca98 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java @@ -30,13 +30,12 @@ import java.util.List; import javax.sql.DataSource; +import lombok.SneakyThrows; import org.h2.jdbcx.JdbcDataSource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests {@link Hotel} - */ +/** Tests {@link Hotel} */ class HotelTest { private static final String H2_DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; @@ -52,68 +51,64 @@ void setUp() throws Exception { dao = new HotelDaoImpl(dataSource); addRooms(dao); hotel = new Hotel(dao); - } @Test void bookingRoomShouldChangeBookedStatusToTrue() throws Exception { hotel.bookRoom(1); + assertTrue(dao.getById(1).isPresent()); assertTrue(dao.getById(1).get().isBooked()); } - @Test() + @Test void bookingRoomWithInvalidIdShouldRaiseException() { - assertThrows(Exception.class, () -> { - hotel.bookRoom(getNonExistingRoomId()); - }); + assertThrows(Exception.class, () -> hotel.bookRoom(getNonExistingRoomId())); } - @Test() + @Test + @SneakyThrows void bookingRoomAgainShouldRaiseException() { - assertThrows(Exception.class, () -> { - hotel.bookRoom(1); - hotel.bookRoom(1); - }); + hotel.bookRoom(1); + assertThrows(Exception.class, () -> hotel.bookRoom(1), "Room already booked!"); } @Test void NotBookingRoomShouldNotChangeBookedStatus() throws Exception { + assertTrue(dao.getById(1).isPresent()); assertFalse(dao.getById(1).get().isBooked()); } @Test void cancelRoomBookingShouldChangeBookedStatus() throws Exception { hotel.bookRoom(1); + assertTrue(dao.getById(1).isPresent()); assertTrue(dao.getById(1).get().isBooked()); + hotel.cancelRoomBooking(1); + assertTrue(dao.getById(1).isPresent()); assertFalse(dao.getById(1).get().isBooked()); } @Test void cancelRoomBookingWithInvalidIdShouldRaiseException() { - assertThrows(Exception.class, () -> { - hotel.cancelRoomBooking(getNonExistingRoomId()); - }); + assertThrows(Exception.class, () -> hotel.cancelRoomBooking(getNonExistingRoomId())); } @Test void cancelRoomBookingForUnbookedRoomShouldRaiseException() { - assertThrows(Exception.class, () -> { - hotel.cancelRoomBooking(1); - }); + assertThrows(Exception.class, () -> hotel.cancelRoomBooking(1)); } - private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); } } private static void createSchema(DataSource dataSource) throws Exception { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); } catch (Exception e) { throw new Exception(e.getMessage(), e); diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java index a1c9fa7ff829..e567d393e6b7 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests {@link Room}. - */ +/** Tests {@link Room}. */ class RoomTest { private Room room; @@ -90,7 +88,10 @@ void equalsWithSameObjects() { @Test void testToString() { - assertEquals(String.format("Room(id=%s, roomType=%s, price=%s, booked=%s)", - room.getId(), room.getRoomType(), room.getPrice(), room.isBooked()), room.toString()); + assertEquals( + String.format( + "Room(id=%s, roomType=%s, price=%s, booked=%s)", + room.getId(), room.getRoomType(), room.getPrice(), room.isBooked()), + room.toString()); } } diff --git a/twin/README.md b/twin/README.md index 338542a9905b..b2913e0a963f 100644 --- a/twin/README.md +++ b/twin/README.md @@ -1,73 +1,65 @@ --- -title: Twin +title: "Twin Pattern in Java: Doubling Functionality with Synchronized Twins" +shortTitle: Twin +description: "Explore the Twin design pattern in Java with examples. Learn how to implement flexible, decoupled systems without multiple inheritance for enhanced modularity and system resilience. Ideal for software developers looking to advance their coding practices." category: Structural language: en tag: - - Extensibility + - Decoupling + - Object composition + - Performance + - Resilience --- -## Intent -Twin pattern is a design pattern which provides a standard solution to simulate multiple -inheritance in java +## Intent of Twin Design Pattern -## Explanation +The Twin design pattern in Java provides a way to handle multiple, related classes in a manner that allows them to work together without inheriting from a common base class. + +## Detailed Explanation of Twin Pattern with Real-World Examples Real-world example -> Consider a game with a ball that needs features of two types, Game Item, and threads to function -> smoothly in the game. We can use two objects, with one object compatible with the first type and -> the other compatible with the second type. -> The pair of objects together can function as one ball in the game. +> An analogous real-world example of the Twin design pattern can be found in the relationship between a driver and a driving simulator. Imagine a driver (the first class) and a driving simulator (the second class) that both need to interact with the same set of vehicle controls (steering, acceleration, braking) and receive the same feedback (speed, engine status). +> +> Despite performing similar functions, the driver and the simulator cannot share a common base class because they operate in fundamentally different environments—one in the physical world and the other in a virtual environment. Instead, they are "twinned" to ensure consistent interaction with the vehicle controls and feedback mechanisms. This setup allows improvements or changes to be made to the simulator without affecting the driver and vice versa, maintaining the system's overall flexibility and resilience. In plain words -> It provides a way to form two closely coupled sub-classes that can act as a twin class having two ends. +> It provides a way to form two closely coupled subclasses that can act as a twin class having two ends. Wikipedia says -> In software engineering, the Twin pattern is a software design pattern that allows developers -> to model multiple inheritance in programming languages that do not support multiple inheritance. -> This pattern avoids many of the problems with multiple inheritance. +> The Twin pattern is a software design pattern that allows developers to simulate multiple inheritance in languages that don't support it. Instead of creating a single class inheriting from multiple parents, two closely linked subclasses are created, each inheriting from one of the parents. These subclasses are mutually dependent, working together as a pair to achieve the desired functionality. This approach avoids the complications and inefficiencies often associated with multiple inheritance, while still allowing the reuse of functionalities from different classes. -**Programmatic Example** +## Programmatic Example of Twin Pattern in Java -Take our game ball example from above. Consider we have a game in which the ball needs to be both a `GameItem` and `Thread`. -First of all, we have the `GameItem` class given below and the `Thread` class. +Consider a game where a ball needs to function as both a `GameItem` and a `Thread`. Instead of inheriting from both, we use the Twin pattern with two closely linked objects: `BallItem` and `BallThread`. +Here is the `GameItem` class: ```java - @Slf4j public abstract class GameItem { - public void draw() { LOGGER.info("draw"); doDraw(); } - public abstract void doDraw(); - - public abstract void click(); } - ``` -Then, we have subclasses `BallItem` and `BallThread` inheriting them, respectively. +`BallItem` and `BallThread` subclasses: ```java - @Slf4j public class BallItem extends GameItem { - private boolean isSuspended; - @Setter private BallThread twin; @Override public void doDraw() { - LOGGER.info("doDraw"); } @@ -77,9 +69,7 @@ public class BallItem extends GameItem { @Override public void click() { - isSuspended = !isSuspended; - if (isSuspended) { twin.suspendMe(); } else { @@ -87,23 +77,17 @@ public class BallItem extends GameItem { } } } +``` - +```java @Slf4j public class BallThread extends Thread { - @Setter private BallItem twin; - private volatile boolean isSuspended; - private volatile boolean isRunning = true; - /** - * Run the thread. - */ public void run() { - while (isRunning) { if (!isSuspended) { twin.draw(); @@ -132,31 +116,116 @@ public class BallThread extends Thread { this.isSuspended = true; } } +``` -``` - -Now, when we need the ball, we can instantiate objects from both the `BallThread` and `BallItem` as a pair and pass them to its pair object so they can act together as appropriate. +To use these classes together: ```java +public class App { + + public static void main(String[] args) throws Exception { + + var ballItem = new BallItem(); + var ballThread = new BallThread(); + + ballItem.setTwin(ballThread); + ballThread.setTwin(ballItem); -var ballItem = new BallItem(); -var ballThread = new BallThread(); + ballThread.start(); -ballItem.setTwin(ballThread); -ballThread.setTwin(ballItem); + waiting(); + ballItem.click(); + + waiting(); + + ballItem.click(); + + waiting(); + + // exit + ballThread.stopMe(); + } + + private static void waiting() throws Exception { + Thread.sleep(750); + } +} +``` + +Let's break down what happens in `App`. + +1. An instance of `BallItem` and `BallThread` are created. +2. The `BallItem` and `BallThread` instances are set as twins of each other. This means that each instance has a reference to the other. +3. The `BallThread` is started. This begins the execution of the `run` method in the `BallThread` class, which continuously calls the `draw` and `move` methods of the `BallItem` (its twin) as long as the `BallThread` is not suspended. +4. The program waits for 750 milliseconds. This is done to allow the `BallThread` to execute its `run` method a few times. +5. The `click` method of the `BallItem` is called. This toggles the `isSuspended` state of the `BallItem` and its twin `BallThread`. If the `BallThread` was running, it gets suspended. If it was suspended, it resumes running. +6. Steps 4 and 5 are repeated twice. This means the `BallThread` is suspended and resumed once. +7. Finally, the `stopMe` method of the `BallThread` is called to stop its execution. + +Console output: + +``` +14:29:33.778 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw +14:29:33.780 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw +14:29:33.780 [Thread-0] INFO com.iluwatar.twin.BallItem -- move +14:29:34.035 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw +14:29:34.035 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw +14:29:34.035 [Thread-0] INFO com.iluwatar.twin.BallItem -- move +14:29:34.291 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw +14:29:34.291 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw +14:29:34.291 [Thread-0] INFO com.iluwatar.twin.BallItem -- move +14:29:34.533 [main] INFO com.iluwatar.twin.BallThread -- Begin to suspend BallThread +14:29:35.285 [main] INFO com.iluwatar.twin.BallThread -- Begin to resume BallThread +14:29:35.308 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw +14:29:35.308 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw +14:29:35.308 [Thread-0] INFO com.iluwatar.twin.BallItem -- move +14:29:35.564 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw +14:29:35.564 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw +14:29:35.565 [Thread-0] INFO com.iluwatar.twin.BallItem -- move +14:29:35.817 [Thread-0] INFO com.iluwatar.twin.GameItem -- draw +14:29:35.817 [Thread-0] INFO com.iluwatar.twin.BallItem -- doDraw +14:29:35.817 [Thread-0] INFO com.iluwatar.twin.BallItem -- move ``` +This setup allows `BallItem` and `BallThread` to act together as a single cohesive unit in the game, leveraging the capabilities of both `GameItem` and `Thread` without multiple inheritance. + +## When to Use the Twin Pattern in Java + +* Use when you need to decouple classes that share common functionality but cannot inherit from a common base class due to various reasons such as the use of different frameworks or languages. +* Useful in performance-critical applications where inheritance might introduce unnecessary overhead. +* Applicable in systems requiring resilience through the ability to replace or update one of the twins without affecting the other. + +## Twin Pattern Java Tutorials + +* [Twin – A Design Pattern for Modeling Multiple Inheritance (Hanspeter Mössenböck)](http://www.ssw.uni-linz.ac.at/Research/Papers/Moe99/Paper.pdf) + +## Real-World Applications of Twin Pattern in Java + +* User interfaces where different frameworks are used for rendering and logic. +* Systems integrating legacy code with new implementations where direct inheritance is not feasible. + +## Benefits and Trade-offs of Twin Pattern + +Benefits: + +* Reduces coupling between classes, promoting modularity and easier maintenance. +* Improves flexibility and reuse of classes across different frameworks or languages. +* Enhances performance by avoiding the overhead associated with inheritance. + +Trade-offs: -## Class diagram -![alt text](./etc/twin.png "Twin") +* Can lead to code duplication if not managed properly. +* Increased complexity in managing the interaction between twin classes. -## Applicability -Use the Twin idiom when +## Related Java Design Patterns -* To simulate multiple inheritance in a language that does not support this feature. -* To avoid certain problems of multiple inheritance such as name clashes. +* [Adapter](https://java-design-patterns.com/patterns/adapter/): Both patterns deal with compatibility issues, but Adapter focuses on converting interfaces while Twin deals with class collaboration without inheritance. +* [Bridge](https://java-design-patterns.com/patterns/bridge/): Similar in decoupling abstraction from implementation, but Twin specifically avoids inheritance. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Manages object access, similar to how Twin handles interaction, but Proxy typically focuses on control and logging. -## Credits +## References and Credits -* [Twin – A Design Pattern for Modeling Multiple Inheritance](http://www.ssw.uni-linz.ac.at/Research/Papers/Moe99/Paper.pdf) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/twin/pom.xml b/twin/pom.xml index 6e76aa318473..e5bb9755230b 100644 --- a/twin/pom.xml +++ b/twin/pom.xml @@ -34,6 +34,14 @@ twin + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/twin/src/main/java/com/iluwatar/twin/App.java b/twin/src/main/java/com/iluwatar/twin/App.java index 7804a5f7ead2..65ffadf36a3b 100644 --- a/twin/src/main/java/com/iluwatar/twin/App.java +++ b/twin/src/main/java/com/iluwatar/twin/App.java @@ -32,7 +32,6 @@ * BallThread} class represent the twin objects to coordinate with each other (via the twin * reference) like a single class inheriting from {@link GameItem} and {@link Thread}. */ - public class App { /** diff --git a/twin/src/main/java/com/iluwatar/twin/BallItem.java b/twin/src/main/java/com/iluwatar/twin/BallItem.java index 64c34dca5079..04d6b97c3483 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallItem.java +++ b/twin/src/main/java/com/iluwatar/twin/BallItem.java @@ -29,7 +29,7 @@ /** * This class represents a Ball which extends {@link GameItem} and implements the logic for ball - * item, like move and draw. It hold a reference of {@link BallThread} to delegate the suspend and + * item, like move and draw. It holds a reference of {@link BallThread} to delegate the suspend and * resume task. */ @Slf4j @@ -37,8 +37,7 @@ public class BallItem extends GameItem { private boolean isSuspended; - @Setter - private BallThread twin; + @Setter private BallThread twin; @Override public void doDraw() { @@ -62,4 +61,3 @@ public void click() { } } } - diff --git a/twin/src/main/java/com/iluwatar/twin/BallThread.java b/twin/src/main/java/com/iluwatar/twin/BallThread.java index c6989cd51b5b..7768d3ebbb99 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallThread.java +++ b/twin/src/main/java/com/iluwatar/twin/BallThread.java @@ -29,22 +29,18 @@ /** * This class is a UI thread for drawing the {@link BallItem}, and provide the method for suspend - * and resume. It hold the reference of {@link BallItem} to delegate the draw task. + * and resume. It holds the reference of {@link BallItem} to delegate the draw task. */ - @Slf4j public class BallThread extends Thread { - @Setter - private BallItem twin; + @Setter private BallItem twin; private volatile boolean isSuspended; private volatile boolean isRunning = true; - /** - * Run the thread. - */ + /** Run the thread. */ public void run() { while (isRunning) { @@ -75,4 +71,3 @@ public void stopMe() { this.isSuspended = true; } } - diff --git a/twin/src/main/java/com/iluwatar/twin/GameItem.java b/twin/src/main/java/com/iluwatar/twin/GameItem.java index d557fc4abef8..1cdb025ff0f6 100644 --- a/twin/src/main/java/com/iluwatar/twin/GameItem.java +++ b/twin/src/main/java/com/iluwatar/twin/GameItem.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * GameItem is a common class which provides some common methods for game object. - */ +/** GameItem is a common class which provides some common methods for game object. */ @Slf4j public abstract class GameItem { - /** - * Template method, do some common logic before draw. - */ + /** Template method, do some common logic before draw. */ public void draw() { LOGGER.info("draw"); doDraw(); @@ -42,6 +38,5 @@ public void draw() { public abstract void doDraw(); - public abstract void click(); } diff --git a/twin/src/test/java/com/iluwatar/twin/AppTest.java b/twin/src/test/java/com/iluwatar/twin/AppTest.java index 029d217610b7..8835f9185fbe 100644 --- a/twin/src/test/java/com/iluwatar/twin/AppTest.java +++ b/twin/src/test/java/com/iluwatar/twin/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.twin; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java index d13ff6698826..df869e92c9d2 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java @@ -41,11 +41,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Date: 12/30/15 - 18:44 PM - * - * @author Jeroen Meulemeester - */ +/** BallItemTest */ class BallItemTest { private InMemoryAppender appender; @@ -68,12 +64,14 @@ void testClick() { final var inOrder = inOrder(ballThread); - IntStream.range(0, 10).forEach(i -> { - ballItem.click(); - inOrder.verify(ballThread).suspendMe(); - ballItem.click(); - inOrder.verify(ballThread).resumeMe(); - }); + IntStream.range(0, 10) + .forEach( + i -> { + ballItem.click(); + inOrder.verify(ballThread).suspendMe(); + ballItem.click(); + inOrder.verify(ballThread).resumeMe(); + }); inOrder.verifyNoMoreInteractions(); } @@ -105,10 +103,8 @@ void testMove() { assertEquals(1, appender.getLogSize()); } - /** - * Logging Appender Implementation - */ - class InMemoryAppender extends AppenderBase { + /** Logging Appender Implementation */ + static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender() { @@ -129,5 +125,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index 4175458cb2aa..6ad431ff649e 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -37,85 +37,81 @@ import org.junit.jupiter.api.Test; -/** - * Date: 12/30/15 - 18:55 PM - * - * @author Jeroen Meulemeester - */ +/** BallThreadTest */ class BallThreadTest { - /** - * Verify if the {@link BallThread} can be resumed - */ + /** Verify if the {@link BallThread} can be resumed */ @Test void testSuspend() { - assertTimeout(ofMillis(5000), () -> { - final var ballThread = new BallThread(); + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); - final var ballItem = mock(BallItem.class); - ballThread.setTwin(ballItem); + final var ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); - ballThread.start(); - sleep(200); - verify(ballItem, atLeastOnce()).draw(); - verify(ballItem, atLeastOnce()).move(); - ballThread.suspendMe(); + ballThread.start(); + sleep(200); + verify(ballItem, atLeastOnce()).draw(); + verify(ballItem, atLeastOnce()).move(); + ballThread.suspendMe(); - sleep(1000); + sleep(1000); - ballThread.stopMe(); - ballThread.join(); + ballThread.stopMe(); + ballThread.join(); - verifyNoMoreInteractions(ballItem); - }); + verifyNoMoreInteractions(ballItem); + }); } - /** - * Verify if the {@link BallThread} can be resumed - */ + /** Verify if the {@link BallThread} can be resumed */ @Test void testResume() { - assertTimeout(ofMillis(5000), () -> { - final var ballThread = new BallThread(); + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); - final var ballItem = mock(BallItem.class); - ballThread.setTwin(ballItem); + final var ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); - ballThread.suspendMe(); - ballThread.start(); + ballThread.suspendMe(); + ballThread.start(); - sleep(1000); + sleep(1000); - verifyNoMoreInteractions(ballItem); + verifyNoMoreInteractions(ballItem); - ballThread.resumeMe(); - sleep(300); - verify(ballItem, atLeastOnce()).draw(); - verify(ballItem, atLeastOnce()).move(); + ballThread.resumeMe(); + sleep(300); + verify(ballItem, atLeastOnce()).draw(); + verify(ballItem, atLeastOnce()).move(); - ballThread.stopMe(); - ballThread.join(); + ballThread.stopMe(); + ballThread.join(); - verifyNoMoreInteractions(ballItem); - }); + verifyNoMoreInteractions(ballItem); + }); } - /** - * Verify if the {@link BallThread} is interruptible - */ + /** Verify if the {@link BallThread} is interruptible */ @Test void testInterrupt() { - assertTimeout(ofMillis(5000), () -> { - final var ballThread = new BallThread(); - final var exceptionHandler = mock(UncaughtExceptionHandler.class); - ballThread.setUncaughtExceptionHandler(exceptionHandler); - ballThread.setTwin(mock(BallItem.class)); - ballThread.start(); - ballThread.interrupt(); - ballThread.join(); - - verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class)); - verifyNoMoreInteractions(exceptionHandler); - }); + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); + final var exceptionHandler = mock(UncaughtExceptionHandler.class); + ballThread.setUncaughtExceptionHandler(exceptionHandler); + ballThread.setTwin(mock(BallItem.class)); + ballThread.start(); + ballThread.interrupt(); + ballThread.join(); + + verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class)); + verifyNoMoreInteractions(exceptionHandler); + }); } -} \ No newline at end of file +} diff --git a/type-object/README.md b/type-object/README.md new file mode 100644 index 000000000000..dbdd6a405c65 --- /dev/null +++ b/type-object/README.md @@ -0,0 +1,340 @@ +--- +title: "Type Object Pattern in Java: Enhancing Flexibility with Dynamic Class Definitions" +shortTitle: Type Object +description: "Discover how the Type Object Pattern in Java allows for dynamic and flexible class creation without altering existing code. Ideal for developers looking to understand and apply this powerful design pattern in real-world Java applications." +category: Creational +language: en +tag: + - Abstraction + - Code simplification + - Data processing + - Game programming + - Extensibility + - Instantiation + - Object composition + - Polymorphism +--- + +## Also known as + +* Type Descriptor +* Type Safe Enumeration + +## Intent of Type Object Design Pattern + +Allow creation of flexible and extensible sets of related types. + +## Detailed Explanation of Type Object Pattern with Real-World Examples + +Real-world example + +> An analogous real-world example of the Type Object pattern can be seen in a role-playing game (RPG) character customization system. In such a game, players can choose from various character classes like Warrior, Mage, and Archer, each with its unique set of abilities and attributes. The Type Object pattern allows the game to define these character classes and their behaviors dynamically. Instead of hardcoding the details of each class, the game uses a flexible system where new character types can be added or existing ones modified without changing the underlying game logic. This extensibility lets the developers introduce new character classes through updates or expansions, keeping the game fresh and engaging for players. + +In plain words + +> Explore how the Java Type Object pattern enables dynamic creation and management of flexible and extensible sets of related classes, ideal for Java developers seeking modularity without modifying existing codebase. + +gameprogrammingpatterns.com says + +> Define a type object class and a typed object class. Each type object instance represents a different logical type. Each typed object stores a reference to the type object that describes its type. + +## Programmatic Example of Type Object Pattern in Java + +The Type Object pattern is a design pattern that allows for the creation of flexible and reusable objects by creating a class with a field that represents the 'type' of the object. This design pattern proves invaluable for scenarios where anticipated Java types are undefined upfront, or when modifications or additions are required, ensuring efficient Java development without frequent recompilations. + +In the provided code, the Type Object pattern is implemented in a mini candy-crush game. The game has many different candies, which may change over time as the game is upgraded. + +Let's break down the key components of this implementation: + +1. **Candy Class**: This class represents the 'type' object in this pattern. Each `Candy` has a `name`, `parent`, `points`, and `type`. The `type` is an enum that can be either `CRUSHABLE_CANDY` or `REWARD_FRUIT`. + +```java +class Candy { + String name; + Candy parent; + String parentName; + int points; + Type type; + + Candy(String name, String parentName, Type type, int points) { + // constructor implementation + } + + int getPoints() { + // implementation + } + + Type getType() { + // implementation + } + + void setPoints(int a) { + // implementation + } +} +``` + +2. **JsonParser Class**: This class is responsible for parsing the JSON file that contains the details about the candies. It creates a `Candy` object for each candy in the JSON file and stores them in a `Hashtable`. + +```java +public class JsonParser { + Hashtable candies; + + JsonParser() { + this.candies = new Hashtable<>(); + } + + void parse() throws JsonParseException { + // implementation + } + + void setParentAndPoints() { + // implementation + } +} +``` + +3. **Cell Class**: This class represents a cell in the game matrix. Each cell contains a candy that can be crushed. It also contains information on how crushing can be done, how the matrix is to be reconfigured, and how points are to be gained. + +```java +class Cell { + Candy candy; + int positionX; + int positionY; + + Cell() { + // implementation + } + + Cell(Candy candy, int positionX, int positionY) { + // implementation + } + + void crush(CellPool pool, Cell[][] cellMatrix) { + // implementation + } + + // other methods... +} +``` + +4. **CandyGame Class**: This class contains the rules for the continuation of the game. + +```java +class CandyGame { + Cell[][] cells; + CellPool pool; + int totalPoints; + + CandyGame(int num, CellPool pool) { + // implementation + } + + boolean continueRound() { + // implementation + } + + // other methods... +} +``` + +5. **CellPool Class**: This class is a pool that reuses the candy cells that have been crushed instead of creating new ones repeatedly. + +```java +class CellPool { + int pointer; + List pool; + Candy[] randomCode; + + CellPool(int num) { + // implementation + } + + void addNewCell(Cell c) { + // implementation + } + + Candy[] assignRandomCandytypes() { + // implementation + } + + Cell getNewCell() { + // implementation + } +} +``` + +6. **App Class**: This class contains the main method that starts the game. + +```java +@Slf4j +public class App { + public static void main(String[] args) { + var givenTime = 50; //50ms + var toWin = 500; //points + var pointsWon = 0; + var numOfRows = 3; + var start = System.currentTimeMillis(); + var end = System.currentTimeMillis(); + var round = 0; + while (pointsWon < toWin && end - start < givenTime) { + round++; + var pool = new CellPool(numOfRows * numOfRows + 5); + var cg = new CandyGame(numOfRows, pool); + if (round > 1) { + LOGGER.info("Refreshing.."); + } else { + LOGGER.info("Starting game.."); + } + cg.printGameStatus(); + end = System.currentTimeMillis(); + cg.round((int) (end - start), givenTime); + pointsWon += cg.totalPoints; + end = System.currentTimeMillis(); + } + LOGGER.info("Game Over"); + if (pointsWon >= toWin) { + LOGGER.info("" + pointsWon); + LOGGER.info("You win!!"); + } else { + LOGGER.info("" + pointsWon); + LOGGER.info("Sorry, you lose!"); + } + } +} +``` + +Let's break down what happens in `App` class. + +1. The `main` method is the entry point of the application. It starts by initializing several variables: + - `givenTime` is set to 50 milliseconds. This is the time limit for the game. + - `toWin` is set to 500 points. This is the target score to win the game. + - `pointsWon` is initialized to 0. This variable keeps track of the total points won so far. + - `numOfRows` is set to 3. This is the number of rows in the game grid. + - `start` and `end` are both set to the current system time in milliseconds. These variables are used to track the elapsed time. + - `round` is initialized to 0. This variable keeps track of the current round number. + +2. The game enters a loop that continues until either the player has won enough points (`pointsWon >= toWin`) or the time limit has been reached (`end - start < givenTime`). + +3. At the start of each round, a new `CellPool` and `CandyGame` are created. The `CellPool` is initialized with a size based on the number of cells in the game grid (`numOfRows * numOfRows + 5`). The `CandyGame` is initialized with the number of rows and the `CellPool`. + +4. If it's not the first round, a message "Refreshing.." is logged. If it is the first round, a message "Starting game.." is logged. + +5. The current game status is printed by calling `cg.printGameStatus()`. + +6. The `end` time is updated to the current system time. + +7. The game round is played by calling `cg.round((int) (end - start), givenTime)`. The elapsed time and the time limit are passed as arguments. + +8. The points won in the round are added to the total points. + +9. The `end` time is updated again to the current system time. + +10. After the loop, a "Game Over" message is logged. + +11. If the total points won is greater than or equal to the target score, a winning message is logged. Otherwise, a losing message is logged. + +This is a simplified version of a game similar to Candy Crush, where the player tries to score as many points as possible within a given time limit. The game is played in rounds, and the player's score and the elapsed time are tracked throughout the game. + +Console output: + +``` +14:36:14.453 [main] INFO com.iluwatar.typeobject.App -- Starting game.. +14:36:14.455 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- cherry | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- mango | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- mango | +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.458 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.459 [main] INFO com.iluwatar.typeobject.CandyGame -- +20 points! +... +... +... +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- cherry | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- mango | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +20 points! +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- cherry | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- green jellybean | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- mango | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- purple popsicle | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- orange gum | +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.CandyGame -- +14:36:14.465 [main] INFO com.iluwatar.typeobject.App -- Game Over +14:36:14.465 [main] INFO com.iluwatar.typeobject.App -- 660 +14:36:14.465 [main] INFO com.iluwatar.typeobject.App -- You win!! +``` + +In this implementation, the Type Object pattern allows for the flexible creation of `Candy` objects. The type of each candy is determined at runtime by parsing a JSON file, which makes it easy to add, modify, or remove candy types without having to recompile the code. + +## When to Use the Type Object Pattern in Java + +This pattern can be used when: + +* Use when you need to create an extensible set of related classes without modifying existing code. +* Ideal for scenarios where types and their behaviors need to be defined at runtime or in a flexible manner. +* Suitable for situations where the number of types is large and may change over time. +* The difference between the different 'types' of objects is the data, not the behaviour. + +## Type Object Pattern Java Tutorials + +* [Types as Objects Pattern (Jon Pearce)](http://www.cs.sjsu.edu/~pearce/modules/patterns/analysis/top.htm) + +## Real-World Applications of Type Object Pattern in Java + +* Java Collections Framework: Utilizing various collection types like List, Set, and Map. +* Graphics Libraries: Defining different shapes with specific properties and behaviors. +* Game Development: Creating different types of characters or items with unique attributes and behaviors. + +## Benefits and Trade-offs of Type Object Pattern + +Benefits: + +* Increases flexibility and extensibility of the code. +* Simplifies the addition of new types without modifying existing code. +* Enhances code readability by organizing related behaviors and properties. + +Trade-offs: + +* Can increase complexity if not managed properly. +* May lead to performance overhead due to dynamic type checking and handling. + +## Related Java Design Patterns + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Often used in conjunction with Type Object to create instances of the types. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Similar in that it defines a family of algorithms or behaviors, but focuses more on interchangeable behaviors. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Can be used to create new instances by copying existing ones, supporting dynamic and flexible type creation. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Type Object (Game Programming Patterns)](http://gameprogrammingpatterns.com/type-object.html) diff --git a/type-object/etc/type-object.urm.puml b/type-object/etc/type-object.urm.puml new file mode 100644 index 000000000000..42ac137e83d5 --- /dev/null +++ b/type-object/etc/type-object.urm.puml @@ -0,0 +1,77 @@ +@startuml +package com.iluwatar.typeobject { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class Candy { + ~ name : String + ~ parent : Candy + ~ parentName : String + - points : int + - type : Type + ~ Candy(name : String, parentName : String, type : Type, points : int) + ~ getName() : String + ~ getParent() : Candy + ~ getParentName() : String + ~ getPoints() : int + ~ getType() : Type + + setPoints(points : int) + } + ~enum Type { + + CRUSHABLE_CANDY {static} + + REWARD_FRUIT {static} + + valueOf(name : String) : Type {static} + + values() : Type[] {static} + } + class CandyGame { + - LOGGER : Logger {static} + ~ cells : Cell[][] + ~ pool : CellPool + ~ totalPoints : int + ~ CandyGame(num : int, pool : CellPool) + ~ adjacentCells(y : int, x : int) : List + ~ continueRound() : boolean + ~ handleChange(points : int) + ~ numOfSpaces(num : int) : String {static} + ~ printGameStatus() + ~ round(timeSoFar : int, totalTime : int) + } + class Cell { + ~ candy : Candy + ~ positionX : int + ~ positionY : int + + Cell() + + Cell(candy : Candy, positionX : int, positionY : int) + ~ crush(pool : CellPool, cellMatrix : Cell[][]) + ~ fillThisSpace(pool : CellPool, cellMatrix : Cell[][]) + ~ handleCrush(c : Cell, pool : CellPool, cellMatrix : Cell[][]) + ~ interact(c : Cell, pool : CellPool, cellMatrix : Cell[][]) : int + } + class CellPool { + + CANDY : String {static} + + FRUIT : String {static} + - LOGGER : Logger {static} + - RANDOM : SecureRandom {static} + ~ pointer : int + ~ pool : List + ~ randomCode : Candy[] + ~ CellPool(num : int) + ~ addNewCell(c : Cell) + ~ assignRandomCandytypes() : Candy[] + ~ getNewCell() : Cell + } + class JsonParser { + ~ candies : Hashtable + ~ JsonParser() + ~ parse() + ~ setParentAndPoints() + } +} +Candy --> "-type" Type +Cell --> "-candy" Candy +Candy --> "-parent" Candy +CandyGame --> "-pool" CellPool +CellPool --> "-pool" Cell +@enduml \ No newline at end of file diff --git a/type-object/etc/typeobjectpattern.urm.png b/type-object/etc/typeobjectpattern.urm.png new file mode 100644 index 000000000000..477dac5f2a2e Binary files /dev/null and b/type-object/etc/typeobjectpattern.urm.png differ diff --git a/typeobjectpattern/etc/typeobjectpattern.urm.puml b/type-object/etc/typeobjectpattern.urm.puml similarity index 100% rename from typeobjectpattern/etc/typeobjectpattern.urm.puml rename to type-object/etc/typeobjectpattern.urm.puml diff --git a/typeobjectpattern/pom.xml b/type-object/pom.xml similarity index 90% rename from typeobjectpattern/pom.xml rename to type-object/pom.xml index 6fe65fee84a9..a0809f9a8425 100644 --- a/typeobjectpattern/pom.xml +++ b/type-object/pom.xml @@ -32,8 +32,16 @@ java-design-patterns 1.26.0-SNAPSHOT - typeobjectpattern + type-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.google.code.gson gson diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java b/type-object/src/main/java/com/iluwatar/typeobject/App.java similarity index 70% rename from typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java rename to type-object/src/main/java/com/iluwatar/typeobject/App.java index d120baca0f2c..15b262a60ca3 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/App.java @@ -27,23 +27,22 @@ import lombok.extern.slf4j.Slf4j; /** - *

      Type object pattern is the pattern we use when the OOP concept of creating a base class and + * Type object pattern is the pattern we use when the OOP concept of creating a base class and * inheriting from it just doesn't work for the case in hand. This happens when we either don't know * what types we will need upfront, or want to be able to modify or add new types conveniently w/o * recompiling repeatedly. The pattern provides a solution by allowing flexible creation of required - * objects by creating one class, which has a field which represents the 'type' of the object.

      - *

      In this example, we have a mini candy-crush game in action. There are many different candies - * in the game, which may change over time, as we may want to upgrade the game. To make the object - * creation convenient, we have a class {@link Candy} which has a field name, parent, points and - * Type. We have a json file {@link candy} which contains the details about the candies, and this is - * parsed to get all the different candies in {@link JsonParser}. The {@link Cell} class is what the - * game matrix is made of, which has the candies that are to be crushed, and contains information on - * how crushing can be done, how the matrix is to be reconfigured and how points are to be gained. - * The {@link CellPool} class is a pool which reuses the candy cells that have been crushed instead - * of making new ones repeatedly. The {@link CandyGame} class has the rules for the continuation of - * the game and the {@link App} class has the game itself.

      + * objects by creating one class, which has a field which represents the 'type' of the object. In + * this example, we have a mini candy-crush game in action. There are many different candies in the + * game, which may change over time, as we may want to upgrade the game. To make the object creation + * convenient, we have a class {@link Candy} which has a field name, parent, points and Type. We + * have a json file {@link candy} which contains the details about the candies, and this is parsed + * to get all the different candies in {@link JsonParser}. The {@link Cell} class is what the game + * matrix is made of, which has the candies that are to be crushed, and contains information on how + * crushing can be done, how the matrix is to be reconfigured and how points are to be gained. The + * {@link CellPool} class is a pool which reuses the candy cells that have been crushed instead of + * making new ones repeatedly. The {@link CandyGame} class has the rules for the continuation of the + * game and the {@link App} class has the game itself. */ - @Slf4j public class App { @@ -53,8 +52,8 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var givenTime = 50; //50ms - var toWin = 500; //points + var givenTime = 50; // 50ms + var toWin = 500; // points var pointsWon = 0; var numOfRows = 3; var start = System.currentTimeMillis(); diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java b/type-object/src/main/java/com/iluwatar/typeobject/Candy.java similarity index 98% rename from typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java rename to type-object/src/main/java/com/iluwatar/typeobject/Candy.java index 78a613da4394..0edaea9f77fc 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/Candy.java @@ -44,8 +44,7 @@ enum Type { Candy parent; String parentName; - @Setter - private int points; + @Setter private int points; private final Type type; Candy(String name, String parentName, Type type, int points) { @@ -55,5 +54,4 @@ enum Type { this.points = points; this.parentName = parentName; } - } diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java b/type-object/src/main/java/com/iluwatar/typeobject/CandyGame.java similarity index 91% rename from typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java rename to type-object/src/main/java/com/iluwatar/typeobject/CandyGame.java index 9e3568e657c1..40412fbbd170 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/CandyGame.java @@ -33,10 +33,9 @@ * The CandyGame class contains the rules for the continuation of the game and has the game matrix * (field 'cells') and totalPoints gained during the game. */ - @Slf4j +@SuppressWarnings("java:S3776") // "Cognitive Complexity of methods should not be too high" public class CandyGame { - Cell[][] cells; CellPool pool; int totalPoints; @@ -65,8 +64,11 @@ void printGameStatus() { var candyName = cell[j].candy.name; if (candyName.length() < 20) { var totalSpaces = 20 - candyName.length(); - LOGGER.info(numOfSpaces(totalSpaces / 2) + cell[j].candy.name - + numOfSpaces(totalSpaces - totalSpaces / 2) + "|"); + LOGGER.info( + numOfSpaces(totalSpaces / 2) + + cell[j].candy.name + + numOfSpaces(totalSpaces - totalSpaces / 2) + + "|"); } else { LOGGER.info(candyName + "|"); } @@ -84,17 +86,19 @@ List adjacentCells(int y, int x) { if (x == 0) { adjacent.add(this.cells[y][1]); } - if (y == cells.length - 1) { + if (y == cells.length - 1 && cells.length > 1) { adjacent.add(this.cells[cells.length - 2][x]); } - if (x == cells.length - 1) { + + if (x == cells.length - 1 && cells.length > 1) { adjacent.add(this.cells[y][cells.length - 2]); } + if (y > 0 && y < cells.length - 1) { adjacent.add(this.cells[y - 1][x]); adjacent.add(this.cells[y + 1][x]); } - if (x > 0 && x < cells.length - 1) { + if (y >= 0 && y < cells.length && x > 0 && x < cells[y].length - 1) { adjacent.add(this.cells[y][x - 1]); adjacent.add(this.cells[y][x + 1]); } @@ -168,5 +172,4 @@ void round(int timeSoFar, int totalTime) { end = System.currentTimeMillis(); } } - } diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java b/type-object/src/main/java/com/iluwatar/typeobject/Cell.java similarity index 94% rename from typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java rename to type-object/src/main/java/com/iluwatar/typeobject/Cell.java index 7437e5eb10a6..d66eec8eed03 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/Cell.java @@ -40,7 +40,7 @@ public class Cell { int positionY; void crush(CellPool pool, Cell[][] cellMatrix) { - //take out from this position and put back in pool + // take out from this position and put back in pool pool.addNewCell(this); this.fillThisSpace(pool, cellMatrix); } @@ -67,8 +67,8 @@ void handleCrush(Cell c, CellPool pool, Cell[][] cellMatrix) { } int interact(Cell c, CellPool pool, Cell[][] cellMatrix) { - if (this.candy.getType().equals(Type.REWARD_FRUIT) || c.candy.getType() - .equals(Type.REWARD_FRUIT)) { + if (this.candy.getType().equals(Type.REWARD_FRUIT) + || c.candy.getType().equals(Type.REWARD_FRUIT)) { return 0; } else { if (this.candy.name.equals(c.candy.name)) { diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java b/type-object/src/main/java/com/iluwatar/typeobject/CellPool.java similarity index 90% rename from typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java rename to type-object/src/main/java/com/iluwatar/typeobject/CellPool.java index eb2e748977e6..18f94fe7416c 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/CellPool.java @@ -29,13 +29,14 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; +import lombok.extern.slf4j.Slf4j; /** * The CellPool class allows the reuse of crushed cells instead of creation of new cells each time. * The reused cell is given a new candy to hold using the randomCode field which holds all the * candies available. */ - +@Slf4j public class CellPool { private static final SecureRandom RANDOM = new SecureRandom(); public static final String FRUIT = "fruit"; @@ -49,8 +50,8 @@ public class CellPool { try { this.randomCode = assignRandomCandytypes(); } catch (Exception e) { - e.printStackTrace(); - //manually initialising this.randomCode + LOGGER.error("Error occurred: ", e); + // manually initialising this.randomCode this.randomCode = new Candy[5]; randomCode[0] = new Candy("cherry", FRUIT, Type.REWARD_FRUIT, 20); randomCode[1] = new Candy("mango", FRUIT, Type.REWARD_FRUIT, 20); @@ -73,7 +74,7 @@ Cell getNewCell() { } void addNewCell(Cell c) { - c.candy = randomCode[RANDOM.nextInt(randomCode.length)]; //changing candytype to new + c.candy = randomCode[RANDOM.nextInt(randomCode.length)]; // changing candytype to new this.pool.add(c); pointer++; } @@ -81,12 +82,12 @@ void addNewCell(Cell c) { Candy[] assignRandomCandytypes() throws JsonParseException { var jp = new JsonParser(); jp.parse(); - var randomCode = new Candy[jp.candies.size() - 2]; //exclude generic types 'fruit' and 'candy' + var randomCode = new Candy[jp.candies.size() - 2]; // exclude generic types 'fruit' and 'candy' var i = 0; for (var e = jp.candies.keys(); e.hasMoreElements(); ) { var s = e.nextElement(); if (!s.equals(FRUIT) && !s.equals(CANDY)) { - //not generic + // not generic randomCode[i] = jp.candies.get(s); i++; } diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java b/type-object/src/main/java/com/iluwatar/typeobject/JsonParser.java similarity index 96% rename from typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java rename to type-object/src/main/java/com/iluwatar/typeobject/JsonParser.java index 80d079f29075..a7c66bd9477b 100644 --- a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/JsonParser.java @@ -31,10 +31,7 @@ import java.io.InputStreamReader; import java.util.Hashtable; -/** - * The JsonParser class helps parse the json file candy.json to get all the different candies. - */ - +/** The JsonParser class helps parse the json file candy.json to get all the different candies. */ public class JsonParser { Hashtable candies; @@ -76,5 +73,4 @@ void setParentAndPoints() { } } } - } diff --git a/typeobjectpattern/src/main/resources/candy.json b/type-object/src/main/resources/candy.json similarity index 100% rename from typeobjectpattern/src/main/resources/candy.json rename to type-object/src/main/resources/candy.json diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java b/type-object/src/test/java/com/iluwatar/typeobject/CandyGameTest.java similarity index 96% rename from typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java rename to type-object/src/test/java/com/iluwatar/typeobject/CandyGameTest.java index 8ae3feb4b8f7..07310771df81 100644 --- a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java +++ b/type-object/src/test/java/com/iluwatar/typeobject/CandyGameTest.java @@ -29,10 +29,7 @@ import com.iluwatar.typeobject.Candy.Type; import org.junit.jupiter.api.Test; -/** - * The CandyGameTest class tests the methods in the {@link CandyGame} class. - */ - +/** The CandyGameTest class tests the methods in the {@link CandyGame} class. */ class CandyGameTest { @Test @@ -66,5 +63,4 @@ void continueRoundTest() { var noneLeft = cg.continueRound(); assertTrue(fruitInLastRow && matchingCandy && !noneLeft); } - } diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellPoolTest.java b/type-object/src/test/java/com/iluwatar/typeobject/CellPoolTest.java similarity index 95% rename from typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellPoolTest.java rename to type-object/src/test/java/com/iluwatar/typeobject/CellPoolTest.java index 9496db769ff1..ae73fa0f6046 100644 --- a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellPoolTest.java +++ b/type-object/src/test/java/com/iluwatar/typeobject/CellPoolTest.java @@ -29,10 +29,7 @@ import java.util.Hashtable; import org.junit.jupiter.api.Test; -/** - * The CellPoolTest class tests the methods in the {@link CellPool} class. - */ - +/** The CellPoolTest class tests the methods in the {@link CellPool} class. */ class CellPoolTest { @Test @@ -48,5 +45,4 @@ void assignRandomCandyTypesTest() { } assertTrue(ht.size() == 5 && parentTypes == 0); } - } diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java b/type-object/src/test/java/com/iluwatar/typeobject/CellTest.java similarity index 97% rename from typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java rename to type-object/src/test/java/com/iluwatar/typeobject/CellTest.java index 28e51d860832..58236f5e6e65 100644 --- a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java +++ b/type-object/src/test/java/com/iluwatar/typeobject/CellTest.java @@ -30,9 +30,7 @@ import com.iluwatar.typeobject.Candy.Type; import org.junit.jupiter.api.Test; -/** - * The CellTest class tests the methods in the {@link Cell} class. - */ +/** The CellTest class tests the methods in the {@link Cell} class. */ class CellTest { @Test diff --git a/unit-of-work/README.md b/unit-of-work/README.md index 56c1a6b7742b..4b82b7c8ce05 100644 --- a/unit-of-work/README.md +++ b/unit-of-work/README.md @@ -1,36 +1,37 @@ --- -title: Unit Of Work -category: Architectural +title: "Unit of Work Pattern in Java: Orchestrating Efficient Transaction Management" +shortTitle: Unit of Work +description: "Discover how the Unit of Work pattern can streamline your Java applications. This guide offers a comprehensive explanation, real-world applications, and code examples to enhance your project’s efficiency and maintain data integrity." +category: Data access language: en tag: - - Data access - - Performance + - Data access + - Decoupling + - Persistence + - Transactions --- -## Intent +## Intent of Unit Of Work Design Pattern -When a business transaction is completed, all the updates are sent as one big unit of work to be -persisted in one go to minimize database round-trips. +Learn how the Java Unit of Work pattern expertly manages and maintains a list of objects impacted by business transactions, coordinating database changes and solving concurrency issues effectively. -## Explanation +## Detailed Explanation of Unit Of Work Pattern with Real-World Examples Real-world example -> Arms dealer has a database containing weapon information. Merchants all over the town are -> constantly updating this information and it causes a high load on the database server. To make the -> load more manageable we apply to Unit of Work pattern to send many small updates in batches. +> Consider a library scenario where a librarian meticulously tracks all books borrowed and returned, utilizing the Unit of Work design pattern to update the inventory system efficiently. Instead of updating the library's inventory system every time a single transaction occurs, the librarian keeps a list of all the changes and updates the system once at the end of the day. This approach ensures that all changes are processed together, maintaining the integrity of the inventory and reducing the number of individual updates needed. This is analogous to the Unit of Work pattern in software, where all changes to a set of objects are tracked and committed as a single transaction to maintain consistency and efficiency. In plain words -> Unit of Work merges many small database updates in a single batch to optimize the number of -> round-trips. +> The Unit of Work pattern tracks changes to objects during a transaction and commits all changes as a single unit to ensure consistency and efficiency. [MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says -> Maintains a list of objects affected by a business transaction and coordinates the writing out of -> changes and the resolution of concurrency problems. +> Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. -**Programmatic Example** +## Programmatic Example of Unit of Work Pattern in Java + +Arms dealer has a database containing weapon information. Merchants all over the town are constantly updating this information causing a high load on the database server. To make the load more manageable we apply to Unit of Work pattern to send many small updates in batches. Here's the `Weapon` entity that is being persisted in the database. @@ -43,9 +44,7 @@ public class Weapon { } ``` -The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern. -It maintains a map of database operations (`context`) that need to be done and when `commit` is -called it applies them in a single batch. +The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern. It maintains a map of database operations (`context`) that need to be done and when `commit` is called it applies them in a single batch. ```java public interface IUnitOfWork { @@ -62,7 +61,9 @@ public interface IUnitOfWork { void commit(); } +``` +```java @Slf4j @RequiredArgsConstructor public class ArmsDealer implements IUnitOfWork { @@ -98,12 +99,9 @@ public class ArmsDealer implements IUnitOfWork { context.put(operation, weaponsToOperate); } - /** - * All UnitOfWork operations are batched and executed together on commit only. - */ @Override public void commit() { - if (context == null || context.size() == 0) { + if (context == null || context.isEmpty()) { return; } LOGGER.info("Commit started"); @@ -149,19 +147,22 @@ public class ArmsDealer implements IUnitOfWork { Here is how the whole app is put together. ```java -// create some weapons -var enchantedHammer = new Weapon(1, "enchanted hammer"); -var brokenGreatSword = new Weapon(2, "broken great sword"); -var silverTrident = new Weapon(3, "silver trident"); - -// create repository -var weaponRepository = new ArmsDealer(new HashMap>(), new WeaponDatabase()); - -// perform operations on the weapons -weaponRepository.registerNew(enchantedHammer); -weaponRepository.registerModified(silverTrident); -weaponRepository.registerDeleted(brokenGreatSword); -weaponRepository.commit(); +public static void main(String[] args) { + // create some weapons + var enchantedHammer = new Weapon(1, "enchanted hammer"); + var brokenGreatSword = new Weapon(2, "broken great sword"); + var silverTrident = new Weapon(3, "silver trident"); + + // create repository + var weaponRepository = new ArmsDealer(new HashMap<>(), + new WeaponDatabase()); + + // perform operations on the weapons + weaponRepository.registerNew(enchantedHammer); + weaponRepository.registerModified(silverTrident); + weaponRepository.registerDeleted(brokenGreatSword); + weaponRepository.commit(); +} ``` Here is the console output. @@ -177,25 +178,46 @@ Here is the console output. 21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished. ``` -## Class diagram +## When to Use the Unit Of Work Pattern in Java + +* The Unit of Work pattern is ideal for managing multiple database operations in Java that must be executed as a single transaction, ensuring data consistency and integrity. +* Ideal in scenarios where changes to the business objects must be tracked and saved in a coordinated manner. +* Useful when working with object-relational mapping (ORM) frameworks in Java such as Hibernate. + +## Unit Of Work Pattern Java Tutorials + +* [Repository and Unit of Work Pattern (Wolfgang Ofner)](https://www.programmingwithwolfgang.com/repository-and-unit-of-work-pattern/) +* [Unit Of Work Design Pattern (Code Project)](https://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern) +* [Unit of Work - a Design Pattern (Mono)](https://mono.software/2017/01/13/unit-of-work-a-design-pattern/) + +## Real-World Applications of Unit of Work Pattern in Java + +* Implementations in Java-based ORM frameworks like Hibernate. +* Enterprise applications where multiple database operations need to be atomic. +* Complex transactional systems where multiple objects are modified and persisted together. + +## Benefits and Trade-offs of Unit Of Work Pattern -![alt text](./etc/unit-of-work.urm.png "unit-of-work") +Benefits: -## Applicability +* Ensures data integrity by managing transactions effectively. +* Reduces the number of database calls by batching them together. +* Simplifies the persistence logic by decoupling transaction management from the business logic. -Use the Unit Of Work pattern when +Trade-offs: -* To optimize the time taken for database transactions. -* To send changes to database as a unit of work which ensures atomicity of the transaction. -* To reduce the number of database calls. +* Can introduce complexity in managing the life cycle of objects within the unit of work. +* Potential performance overhead if not managed properly, especially with large datasets. -## Tutorials +## Related Java Design Patterns -* [Repository and Unit of Work Pattern](https://www.programmingwithwolfgang.com/repository-and-unit-of-work-pattern/) -* [Unit of Work - a Design Pattern](https://mono.software/2017/01/13/unit-of-work-a-design-pattern/) +* [Identity Map](https://java-design-patterns.com/patterns/identity-map/): Helps to ensure that each object is only loaded once per transaction, reducing redundancy and improving performance. +* [Repository](https://java-design-patterns.com/patterns/repository/): Often used in conjunction with Unit of Work to abstract the persistence logic and provide a cleaner way to access data. +* [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/): While different in its procedural approach, it can complement Unit of Work by managing transactional logic at a higher level. -## Credits +## References and Credits -* [Design Pattern - Unit Of Work Pattern](https://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern) -* [Unit Of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Unit Of Work (Martin Fowler)](https://martinfowler.com/eaaCatalog/unitOfWork.html) diff --git a/unit-of-work/pom.xml b/unit-of-work/pom.xml index 670c97e8baaa..357ffdf854ec 100644 --- a/unit-of-work/pom.xml +++ b/unit-of-work/pom.xml @@ -34,6 +34,14 @@ 4.0.0 unit-of-work + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java index 6ed0efd8541a..5713d4144a81 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java @@ -25,18 +25,14 @@ package com.iluwatar.unitofwork; import java.util.HashMap; -import java.util.List; -/** - * {@link App} Application demonstrating unit of work pattern. - */ +/** {@link App} Application demonstrating unit of work pattern. */ public class App { /** * Program entry point. * * @param args no argument sent */ - public static void main(String[] args) { // create some weapons var enchantedHammer = new Weapon(1, "enchanted hammer"); @@ -44,8 +40,7 @@ public static void main(String[] args) { var silverTrident = new Weapon(3, "silver trident"); // create repository - var weaponRepository = new ArmsDealer(new HashMap>(), - new WeaponDatabase()); + var weaponRepository = new ArmsDealer(new HashMap<>(), new WeaponDatabase()); // perform operations on the weapons weaponRepository.registerNew(enchantedHammer); diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java index 9c77c13a1fc0..c6a05026f9bf 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java @@ -30,9 +30,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * {@link ArmsDealer} Weapon repository that supports unit of work for weapons. - */ +/** {@link ArmsDealer} Weapon repository that supports unit of work for weapons. */ @Slf4j @RequiredArgsConstructor public class ArmsDealer implements UnitOfWork { @@ -50,7 +48,6 @@ public void registerNew(Weapon weapon) { public void registerModified(Weapon weapon) { LOGGER.info("Registering {} for modify in context.", weapon.getName()); register(weapon, UnitActions.MODIFY.getActionValue()); - } @Override @@ -68,12 +65,10 @@ private void register(Weapon weapon, String operation) { context.put(operation, weaponsToOperate); } - /** - * All UnitOfWork operations are batched and executed together on commit only. - */ + /** All UnitOfWork operations are batched and executed together on commit only. */ @Override public void commit() { - if (context == null || context.size() == 0) { + if (context == null || context.isEmpty()) { return; } LOGGER.info("Commit started"); diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java index ce69e96939ba..d81ea3996284 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Enum representing unit actions. - */ +/** Enum representing unit actions. */ @Getter @RequiredArgsConstructor public enum UnitActions { diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java index 5b7a9413a735..648ec36024ef 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java @@ -31,9 +31,7 @@ */ public interface UnitOfWork { - /** - * Any register new operation occurring on UnitOfWork is only going to be performed on commit. - */ + /** Any register new operation occurring on UnitOfWork is only going to be performed on commit. */ void registerNew(T entity); /** @@ -46,9 +44,6 @@ public interface UnitOfWork { */ void registerDeleted(T entity); - /** - * All UnitOfWork operations batched together executed in commit only. - */ + /** All UnitOfWork operations batched together executed in commit only. */ void commit(); - -} \ No newline at end of file +} diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java index d91e73e20bcc..5ceb55ad9e8a 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * {@link Weapon} is an entity. - */ +/** {@link Weapon} is an entity. */ @Getter @RequiredArgsConstructor public class Weapon { diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java index d6c5422c0f5b..4ea15d46b075 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java @@ -24,20 +24,18 @@ */ package com.iluwatar.unitofwork; -/** - * Act as database for weapon records. - */ +/** Act as database for weapon records. */ public class WeaponDatabase { public void insert(Weapon weapon) { - //Some insert logic to DB + // Some insert logic to DB } public void modify(Weapon weapon) { - //Some modify logic to DB + // Some modify logic to DB } public void delete(Weapon weapon) { - //Some delete logic to DB + // Some delete logic to DB } } diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java index 342035db7b57..6f4557698164 100644 --- a/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * AppTest - */ +/** AppTest */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java index c870a1f312ab..da06e2d76974 100644 --- a/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java @@ -36,17 +36,14 @@ import java.util.Map; import org.junit.jupiter.api.Test; -/** - * tests {@link ArmsDealer} - */ - +/** tests {@link ArmsDealer} */ class ArmsDealerTest { private final Weapon weapon1 = new Weapon(1, "battle ram"); private final Weapon weapon2 = new Weapon(1, "wooden lance"); private final Map> context = new HashMap<>(); private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class); - private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);; + private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase); @Test void shouldSaveNewStudentWithoutWritingToDb() { diff --git a/update-method/README.md b/update-method/README.md index 5630621fa2fd..bd1fa2f75995 100644 --- a/update-method/README.md +++ b/update-method/README.md @@ -1,33 +1,223 @@ ---- -title: Update Method +--- +title: "Update Method Pattern in Java: Enhancing Game Loop Efficiency with Systematic Updates" +shortTitle: Update Method +description: "Explore the Update Method design pattern for Java, ideal for real-time games and applications. Learn how it optimizes performance by updating objects frame-by-frame to maintain synchronized, efficient operations." category: Behavioral language: en -tag: - - Game programming ---- - -## Intent -Update method pattern simulates a collection of independent objects by telling each to process one frame of behavior at a time. +tag: + - Abstraction + - Data processing + - Decoupling + - Event-driven + - Game programming + - Polymorphism +--- -## Explanation -The game world maintains a collection of objects. Each object implements an update method that simulates one frame of the object’s behavior. Each frame, the game updates every object in the collection. +## Also known as -To learn more about how the game loop runs and when the update methods are invoked, please refer to Game Loop Pattern. +* Update Mechanism -## Class diagram -![alt text](./etc/update-method.urm.png "Update Method pattern class diagram") +## Intent of Update Method Design Pattern -## Applicability -If the Game Loop pattern is the best thing since sliced bread, then the Update Method pattern is its butter. A wide swath of games featuring live entities that the player interacts with use this pattern in some form or other. If the game has space marines, dragons, Martians, ghosts, or athletes, there’s a good chance it uses this pattern. +The Update Method pattern in Java simulates a collection of independent objects by telling each to process one frame of behavior at a time. -However, if the game is more abstract and the moving pieces are less like living actors and more like pieces on a chessboard, this pattern is often a poor fit. In a game like chess, you don’t need to simulate all of the pieces concurrently, and you probably don’t need to tell the pawns to update themselves every frame. +## Detailed Explanation of Update Method Pattern with Real-World Examples -Update methods work well when: +Real-world example -- Your game has a number of objects or systems that need to run simultaneously. -- Each object’s behavior is mostly independent of the others. -- The objects need to be simulated over time. +> A real-world example of the Update Method design pattern is a weather monitoring system. In this system, multiple display devices (such as a mobile app, a website widget, and a wall-mounted digital display) need to show the current weather conditions. These displays subscribe to updates from a central weather station, which collects data from various sensors (temperature, humidity, wind speed, etc.). When the weather station detects new data, it triggers an update method that pushes the new information to all subscribed display devices, ensuring they all show the latest weather conditions simultaneously. This ensures that all displays are synchronized and updated without the need for each device to independently check for updates. -## Credits - +In plain words + +> The Update Method design pattern processes system object behavior one frame at a time + +gameprogrammingpatterns.com says + +> The game world maintains a collection of objects. Each object implements an update method that simulates one frame of the object’s behavior. Each frame, the game updates every object in the collection. + +## Programmatic Example of Update Method Pattern in Java + +The Update Method design pattern is a behavioral pattern that simulates a collection of independent game or application objects by telling each to process one frame of behavior at a time. This pattern is commonly used in game development, where each object in the game world needs to be updated once per frame. + +The `World` class represents the game world. It maintains a list of entities (`List entities`) and a boolean flag (`isRunning`) to indicate whether the game is running. + +```java +public class World { + + protected List entities; + protected volatile boolean isRunning; + + public World() { + entities = new ArrayList<>(); + isRunning = false; + } + // Other properties and methods... +} +``` + +The `gameLoop` method is the main game loop. It continuously processes user input, updates the game state, and renders the next frame as long as the game is running. + +```java +private void gameLoop() { + while (isRunning) { + processInput(); + update(); + render(); + } +} +``` + +The `processInput` method simulates handling user input. In this case, it simply introduces a random time lag to simulate real-life game situations. + +```java +private void processInput() { + try { + int lag = new SecureRandom().nextInt(200) + 50; + Thread.sleep(lag); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + Thread.currentThread().interrupt(); + } +} +``` + +The `update` method is where the Update Method pattern is implemented. It iterates over all entities in the game world and calls their `update` method, allowing each entity to process one frame of behavior. + +```java +private void update() { + for (var entity : entities) { + entity.update(); + } +} +``` + +The `render` method is responsible for rendering the next frame. In this example, it does nothing as it's not related to the pattern. + +```java +private void render() { + // Does Nothing +} +``` + +The `run` and `stop` methods are used to start and stop the game loop. + +```java +public void run() { + LOGGER.info("Start game."); + isRunning = true; + var thread = new Thread(this::gameLoop); + thread.start(); +} + +public void stop() { + LOGGER.info("Stop game."); + isRunning = false; +} +``` + +The `addEntity` method is used to add new entities to the game world. + +```java +public void addEntity(Entity entity) { + entities.add(entity); +} +``` + +In the `App` class, we can see how the `World` class and its methods are used to create a game world, add entities to it, and start the game loop. + +```java +@Slf4j +public class App { + + private static final int GAME_RUNNING_TIME = 2000; + + public static void main(String[] args) { + try { + var world = new World(); + var skeleton1 = new Skeleton(1, 10); + var skeleton2 = new Skeleton(2, 70); + var statue = new Statue(3, 20); + world.addEntity(skeleton1); + world.addEntity(skeleton2); + world.addEntity(statue); + world.run(); + Thread.sleep(GAME_RUNNING_TIME); + world.stop(); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } + } +} +``` + +Console output: + +``` +14:46:33.181 [main] INFO com.iluwatar.updatemethod.World -- Start game. +14:46:33.280 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 11. +14:46:33.281 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 71. +14:46:33.452 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 12. +14:46:33.452 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 72. +14:46:33.621 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 13. +14:46:33.621 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 73. +14:46:33.793 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 14. +14:46:33.793 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 74. +14:46:33.885 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 15. +14:46:33.885 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 75. +14:46:34.113 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 16. +14:46:34.113 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 76. +14:46:34.324 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 17. +14:46:34.324 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 77. +14:46:34.574 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 18. +14:46:34.575 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 78. +14:46:34.730 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 19. +14:46:34.731 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 79. +14:46:34.803 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 20. +14:46:34.803 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 80. +14:46:34.979 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 21. +14:46:34.979 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 81. +14:46:35.045 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 22. +14:46:35.046 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 82. +14:46:35.187 [main] INFO com.iluwatar.updatemethod.World -- Stop game. +14:46:35.288 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 1 is on position 23. +14:46:35.289 [Thread-0] INFO com.iluwatar.updatemethod.Skeleton -- Skeleton 2 is on position 83. +``` + +This is a basic implementation of the Update Method pattern. In a real-world application, the `Entity` class would likely have additional methods and properties, and the `update` method would contain more complex logic to simulate the entity's behavior. + +## When to Use the Update Method Pattern in Java + +Update Method works well when: + +* Typically applied in scenarios where multiple objects need synchronous updates without the overhead of manual synchronization, making it a go-to for advanced Java developers. +* The application has a number of objects or systems that need to run simultaneously. +* Each object’s behavior is mostly independent of the others. +* The objects need to be simulated over time. + +## Real-World Applications of Update Method Pattern in Java + +* Real-time games and data processing applications where world objects need to be updated once per frame. + +## Benefits and Trade-offs of Update Method Pattern + +Benefits: + +* Each entity encapsulates its own behavior +* Makes it easy to add and remove entities +* Keeps the main loop uncluttered + +Trade-offs: + +* Increases complexity due to yielding control every frame +* The state needs to be stored to enable resuming updates after each frame +* Entities are simulated each frame, but they are not truly concurrent + +## Related Java Design Patterns + +* [Component](https://java-design-patterns.com/patterns/component/): Often used in game development to allow entities to be composed of various components, each potentially having its own update method. +* [Game Loop](https://java-design-patterns.com/patterns/game-loop/): Continuously updates game state and renders the game, which may include the Update Method for various game objects. + +## References and Credits + +* [Game Programming Patterns](https://amzn.to/3wLTbvr) * [Game Programming Patterns - Update Method](http://gameprogrammingpatterns.com/update-method.html) diff --git a/update-method/etc/update-method.urm.puml b/update-method/etc/update-method.urm.puml index 53d2a6eb6a2e..7be0440f74bc 100644 --- a/update-method/etc/update-method.urm.puml +++ b/update-method/etc/update-method.urm.puml @@ -20,7 +20,7 @@ package com.iluwatar.updatemethod { - PATROLLING_RIGHT_BOUNDING : int {static} # patrollingLeft : boolean + Skeleton(id : int) - + Skeleton(id : int, postition : int) + + Skeleton(id : int, position : int) + update() } class Statue { @@ -48,4 +48,4 @@ package com.iluwatar.updatemethod { World --> "-entities" Entity Skeleton --|> Entity Statue --|> Entity -@enduml \ No newline at end of file +@enduml diff --git a/update-method/pom.xml b/update-method/pom.xml index 84e0c7664dfa..c48003975f3c 100644 --- a/update-method/pom.xml +++ b/update-method/pom.xml @@ -34,6 +34,14 @@ 4.0.0 update-method + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/App.java b/update-method/src/main/java/com/iluwatar/updatemethod/App.java index 9d9456d31959..f51538842610 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/App.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/App.java @@ -27,10 +27,10 @@ import lombok.extern.slf4j.Slf4j; /** - * This pattern simulate a collection of independent objects by telling each to - * process one frame of behavior at a time. The game world maintains a collection - * of objects. Each object implements an update method that simulates one frame of - * the object’s behavior. Each frame, the game updates every object in the collection. + * This pattern simulate a collection of independent objects by telling each to process one frame of + * behavior at a time. The game world maintains a collection of objects. Each object implements an + * update method that simulates one frame of the object’s behavior. Each frame, the game updates + * every object in the collection. */ @Slf4j public class App { @@ -39,6 +39,7 @@ public class App { /** * Program entry point. + * * @param args runtime arguments */ public static void main(String[] args) { diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java b/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java index f7a8a0ff36d9..02de2638e6a7 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java @@ -29,18 +29,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Abstract class for all the entity types. - */ +/** Abstract class for all the entity types. */ public abstract class Entity { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected int id; - @Getter - @Setter - protected int position; + @Getter @Setter protected int position; public Entity(int id) { this.id = id; @@ -48,5 +44,4 @@ public Entity(int id) { } public abstract void update(); - } diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java b/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java index f6e6f25d44e7..26d861d784fc 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java @@ -25,10 +25,9 @@ package com.iluwatar.updatemethod; /** - * Skeletons are always patrolling on the game map. Initially all the skeletons - * patrolling to the right, and after them reach the bounding, it will start - * patrolling to the left. For each frame, one skeleton will move 1 position - * step. + * Skeletons are always patrolling on the game map. Initially all the skeletons patrolling to the + * right, and after them reach the bounding, it will start patrolling to the left. For each frame, + * one skeleton will move 1 position step. */ public class Skeleton extends Entity { @@ -76,4 +75,3 @@ public void update() { logger.info("Skeleton {} is on position {}.", id, position); } } - diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java b/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java index 7212a7a1b6d3..d67baf2d0711 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java @@ -24,9 +24,7 @@ */ package com.iluwatar.updatemethod; -/** - * Statues shoot lightning at regular intervals. - */ +/** Statues shoot lightning at regular intervals. */ public class Statue extends Entity { protected int frames; diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/World.java b/update-method/src/main/java/com/iluwatar/updatemethod/World.java index 5335b23f12f0..ef93c82f8c3d 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/World.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/World.java @@ -27,12 +27,9 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; -import java.util.Random; import lombok.extern.slf4j.Slf4j; -/** - * The game world class. Maintain all the objects existed in the game frames. - */ +/** The game world class. Maintain all the objects existed in the game frames. */ @Slf4j public class World { @@ -46,9 +43,9 @@ public World() { } /** - * Main game loop. This loop will always run until the game is over. For - * each loop it will process user input, update internal status, and render - * the next frames. For more detail please refer to the game-loop pattern. + * Main game loop. This loop will always run until the game is over. For each loop it will process + * user input, update internal status, and render the next frames. For more detail please refer to + * the game-loop pattern. */ private void gameLoop() { while (isRunning) { @@ -59,9 +56,8 @@ private void gameLoop() { } /** - * Handle any user input that has happened since the last call. In order to - * simulate the situation in real-life game, here we add a random time lag. - * The time lag ranges from 50 ms to 250 ms. + * Handle any user input that has happened since the last call. In order to simulate the situation + * in real-life game, here we add a random time lag. The time lag ranges from 50 ms to 250 ms. */ private void processInput() { try { @@ -74,8 +70,8 @@ private void processInput() { } /** - * Update internal status. The update method pattern invoke update method for - * each entity in the game. + * Update internal status. The update method pattern invoke update method for each entity in the + * game. */ private void update() { for (var entity : entities) { @@ -83,17 +79,12 @@ private void update() { } } - /** - * Render the next frame. Here we do nothing since it is not related to the - * pattern. - */ + /** Render the next frame. Here we do nothing since it is not related to the pattern. */ private void render() { // Does Nothing } - /** - * Run game loop. - */ + /** Run game loop. */ public void run() { LOGGER.info("Start game."); isRunning = true; @@ -101,9 +92,7 @@ public void run() { thread.start(); } - /** - * Stop game loop. - */ + /** Stop game loop. */ public void stop() { LOGGER.info("Stop game."); isRunning = false; @@ -112,5 +101,4 @@ public void stop() { public void addEntity(Entity entity) { entities.add(entity); } - } diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java index e684cfef0134..53a9d2334d4d 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java @@ -32,6 +32,6 @@ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java index c5672ae95a63..6f602245acd4 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.updatemethod; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + class SkeletonTest { private static Skeleton skeleton; diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java index 7df1b9755fba..45b062d5389d 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java @@ -24,12 +24,12 @@ */ package com.iluwatar.updatemethod; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - class StatueTest { private static Statue statue; diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java index d4284d80109e..3c94d5dd54ea 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.updatemethod; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + class WorldTest { private static World world; diff --git a/value-object/README.md b/value-object/README.md index 43b08f328598..88478b9314bf 100644 --- a/value-object/README.md +++ b/value-object/README.md @@ -1,44 +1,58 @@ --- -title: Value Object -category: Creational +title: "Value Object Pattern in Java: Enhancing Performance with Immutable Data Types" +shortTitle: Value Object +description: "Explore the Value Object pattern in Java with our in-depth guide. Learn how immutable objects enhance performance and memory efficiency in software design. Ideal for developers looking to optimize Java applications." +category: Structural language: en tag: - - Instantiation + - Data access + - Data binding + - Domain + - Encapsulation + - Enterprise patterns + - Immutable + - Optimization + - Performance + - Persistence --- -## Intent +## Also known as -Provide objects which follow value semantics rather than reference semantics. -This means value objects' equality is not based on identity. Two value objects are -equal when they have the same value, not necessarily being the same object. +* Embedded Value +* Immutable Object +* Inline Value +* Integrated Value -## Explanation +## Intent of Value Object Design Pattern + +The Value Object pattern in Java creates immutable objects that represent a descriptive aspect of the domain with no conceptual identity. It aims to enhance performance and reduce memory overhead by storing frequently accessed immutable data directly within the object that uses it, rather than separately. + +## Detailed Explanation of Value Object Pattern with Real-World Examples Real-world example -> There is a class for hero statistics in a role-playing game. The statistics contain attributes -> such as strength, intelligence, and luck. The statistics of different heroes should be equal -> when all the attributes are equal. +> Consider the case of a business card. In our example, a BusinessCard class is implemented as a Value Object to demonstrate immutable data handling and efficiency in Java applications. In the real world, a business card contains information such as the person's name, job title, phone number, and email address. This information represents a specific and complete set of attributes describing the contact details of an individual but doesn't have an identity itself beyond this information. +> +> In a software system, you can create a `BusinessCard` class as a Value Object. This class would be immutable, meaning once a `BusinessCard` object is created with a person's details, those details cannot change. If you need a different business card, you create a new instance rather than modifying the existing one. The equality of two `BusinessCard` objects would be based on their contained data rather than their memory addresses, ensuring that two business cards with the same details are considered equal. This mirrors how business cards in real life are used and compared based on their content, not on the physical card itself. In plain words -> Value objects are equal when their attributes have the same value +> Value objects are equal when their attributes have the same value. Wikipedia says -> In computer science, a value object is a small object that represents a simple entity whose -> equality is not based on identity: i.e. two value objects are equal when they have the same -> value, not necessarily being the same object. +> In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object. -**Programmatic Example** +## Programmatic Example of Value Object Pattern in Java -Here is the `HeroStat` class that is the value object. Notice the use of -[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation. +There is a class for hero statistics in a role-playing game. The statistics contain attributes such as strength, intelligence, and luck. The statistics of different heroes should be equal when all the attributes are equal. + +Here is the `HeroStat` class that is the value object. Notice the use of [Lombok's `@Value`](https://projectlombok.org/features/Value) annotation. ```java @Value(staticConstructor = "valueOf") +@ToString class HeroStat { - int strength; int intelligence; int luck; @@ -48,16 +62,18 @@ class HeroStat { The example creates three different `HeroStat`s and compares their equality. ```java -var statA = HeroStat.valueOf(10, 5, 0); -var statB = HeroStat.valueOf(10, 5, 0); -var statC = HeroStat.valueOf(5, 1, 8); +public static void main(String[] args) { + var statA = HeroStat.valueOf(10, 5, 0); + var statB = HeroStat.valueOf(10, 5, 0); + var statC = HeroStat.valueOf(5, 1, 8); -LOGGER.info(statA.toString()); -LOGGER.info(statB.toString()); -LOGGER.info(statC.toString()); + LOGGER.info("statA: {}", statA); + LOGGER.info("statB: {}", statB); + LOGGER.info("statC: {}", statC); -LOGGER.info("Is statA and statB equal : {}", statA.equals(statB)); -LOGGER.info("Is statA and statC equal : {}", statA.equals(statC)); + LOGGER.info("Are statA and statB equal? {}", statA.equals(statB)); + LOGGER.info("Are statA and statC equal? {}", statA.equals(statC)); +} ``` Here's the console output. @@ -70,26 +86,59 @@ Here's the console output. 20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false ``` -## Class diagram +## When to Use the Value Object Pattern in Java -![alt text](./etc/value-object.png "Value Object") +Use the Value Object when -## Applicability +* Apply the Value Object pattern when you need high-performance Java applications with reduced memory overhead, especially in systems requiring efficient data management. +* When representing a set of attributes that together describe an entity but without an identity. +* When the equality of the objects is based on the value of the properties, not the identity. +* When you need to ensure that objects cannot be altered once created. +* An application requires high performance and the data involved is immutable. +* Memory footprint reduction is critical, especially in environments with limited resources. +* Objects frequently access a particular piece of immutable data. -Use the Value Object when +## Value Object Pattern Java Tutorials -* The object's equality needs to be based on the object's value +* [VALJOs - Value Java Objects (Stephen Colebourne)](http://blog.joda.org/2014/03/valjos-value-java-objects.html) -## Known uses +## Real-World Applications of Value Object Pattern in Java +* Implementing complex data types like monetary values, measurements, and other domain-specific values. * [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) * [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html) +* [java.awt.Color](https://docs.oracle.com/javase/8/docs/api/java/awt/Color.html) * [joda-time, money, beans](http://www.joda.org/) -## Credits +## Benefits and Trade-offs of Value Object Pattern + +Benefits: + +* Simplifies code by making objects immutable. +* Thread-safe as the object's state cannot change after creation. +* Easier to reason about and maintain. +* Reduces the memory overhead by avoiding separate allocations for immutable data. +* Improves performance by minimizing memory accesses and reducing cache misses. + +Trade-offs: + +* Creating a new object for every change can be less efficient for complex objects. +* Increased memory usage due to the creation of multiple objects representing different states. +* Increases complexity in object design and can lead to tightly coupled systems. +* Modifying the embedded value necessitates changes across all objects that embed this value, which can complicate maintenance. + +## Related Java Design Patterns + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Often used to create instances of value objects. +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): Shares objects to support large quantities using a minimal amount of memory, somewhat similar in intent but different in implementation. +* [Builder](https://java-design-patterns.com/patterns/builder/): Can be used to construct complex value objects step by step. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Can be used to clone existing value objects, though cloning is less common with immutable objects. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): Ensures a class has only one instance and provides a global point of access to it, can be used to manage a shared embedded value. + +## References and Credits -* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html) -* [ValueObject](https://martinfowler.com/bliki/ValueObject.html) -* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html) -* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object) -* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [ValueObject (Martin Fowler)](https://martinfowler.com/bliki/ValueObject.html) diff --git a/value-object/pom.xml b/value-object/pom.xml index 2ffba3b64820..b8b2d3433fdc 100644 --- a/value-object/pom.xml +++ b/value-object/pom.xml @@ -34,6 +34,14 @@ value-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/value-object/src/main/java/com/iluwatar/value/object/App.java b/value-object/src/main/java/com/iluwatar/value/object/App.java index 6e5c906724d4..0082fae44f56 100644 --- a/value-object/src/main/java/com/iluwatar/value/object/App.java +++ b/value-object/src/main/java/com/iluwatar/value/object/App.java @@ -29,7 +29,7 @@ /** * A Value Object are objects which follow value semantics rather than reference semantics. This * means value objects' equality are not based on identity. Two value objects are equal when they - * have the same value, not necessarily being the same object.. + * have the same value, not necessarily being the same object. * *

      Value Objects must override equals(), hashCode() to check the equality with values. Value * Objects should be immutable so declare members final. Obtain instances by static factory methods. @@ -43,19 +43,17 @@ @Slf4j public class App { - /** - * This example creates three HeroStats (value objects) and checks equality between those. - */ + /** This example creates three HeroStats (value objects) and checks equality between those. */ public static void main(String[] args) { var statA = HeroStat.valueOf(10, 5, 0); var statB = HeroStat.valueOf(10, 5, 0); var statC = HeroStat.valueOf(5, 1, 8); - LOGGER.info(statA.toString()); - LOGGER.info(statB.toString()); - LOGGER.info(statC.toString()); + LOGGER.info("statA: {}", statA); + LOGGER.info("statB: {}", statB); + LOGGER.info("statC: {}", statC); - LOGGER.info("Is statA and statB equal : {}", statA.equals(statB)); - LOGGER.info("Is statA and statC equal : {}", statA.equals(statC)); + LOGGER.info("Are statA and statB equal? {}", statA.equals(statB)); + LOGGER.info("Are statA and statC equal? {}", statA.equals(statC)); } } diff --git a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java index 4226743a00a9..fbeeadd75a86 100644 --- a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java +++ b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java @@ -24,16 +24,17 @@ */ package com.iluwatar.value.object; +import lombok.ToString; import lombok.Value; /** * HeroStat is a value object. * * @see - * http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html - * + * http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html */ @Value(staticConstructor = "valueOf") +@ToString class HeroStat { int strength; diff --git a/value-object/src/test/java/com/iluwatar/value/object/AppTest.java b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java index a0a76eac230b..8e957df0cdda 100644 --- a/value-object/src/test/java/com/iluwatar/value/object/AppTest.java +++ b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.value.object; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java b/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java index 82c66322d910..fe3238b3f076 100644 --- a/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java +++ b/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java @@ -29,17 +29,16 @@ import org.junit.jupiter.api.Test; -/** - * Unit test for HeroStat. - */ +/** Unit test for HeroStat. */ class HeroStatTest { /** * Tester for equals() and hashCode() methods of a class. Using guava's EqualsTester. * - * @see - * http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/EqualsTester.html - * + * @see + * http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/EqualsTester.html + * */ @Test void testEquals() { @@ -60,5 +59,4 @@ void testToString() { assertEquals(heroStatA.toString(), heroStatB.toString()); assertNotEquals(heroStatA.toString(), heroStatC.toString()); } - } diff --git a/version-number/README.md b/version-number/README.md index 7b3f0f1a3d7b..6178f2317a89 100644 --- a/version-number/README.md +++ b/version-number/README.md @@ -1,63 +1,62 @@ --- -title: Version Number -category: Concurrency +title: "Version Number Pattern in Java: Implementing Robust Version Management in Java Applications" +shortTitle: Version Number +description: "Explore the Version Number pattern in Java to manage concurrent data access and maintain data integrity. Learn how to implement it effectively with examples and best practices." +category: Data access language: en tag: - - Data access - - Microservices + - Compatibility + - Data access + - Persistence + - State tracking + - Versioning --- -## Name / classification - -Version Number. - ## Also known as -Entity Versioning, Optimistic Locking. +* Entity Versioning +* Versioning -## Intent +## Intent of Version Number Design Pattern -Resolve concurrency conflicts when multiple clients are trying to update same entity simultaneously. +Ensure data consistency and integrity in Java applications by tracking changes with version numbers—a crucial component of concurrent data management. -## Explanation +## Detailed Explanation of Version Number Pattern with Real-World Examples -Real world example +Real-world example -> Alice and Bob are working on the book, which stored in the database. Our heroes are making -> changes simultaneously, and we need some mechanism to prevent them from overwriting each other. +> Consider a library system where multiple librarians can update the details of books simultaneously. Each book entry in the library's database has a version number. When a librarian wants to update a book's details, the system checks the version number of the entry. If the version number matches the current version in the database, the update proceeds, and the version number is incremented. If the version number has changed, it means another librarian has already updated the book details, prompting the system to notify the librarian of the conflict and suggesting a review of the latest changes. This ensures that updates do not overwrite each other unintentionally, maintaining data integrity and consistency. In plain words -> Version Number pattern grants protection against concurrent updates to same entity. +> The Version Number pattern in Java provides robust protection against concurrent updates, ensuring reliable data versioning in distributed systems. Wikipedia says -> Optimistic concurrency control assumes that multiple transactions can frequently complete -> without interfering with each other. While running, transactions use data resources without -> acquiring locks on those resources. Before committing, each transaction verifies that no other -> transaction has modified the data it has read. If the check reveals conflicting modifications, -> the committing transaction rolls back and can be restarted. +> The Version Number pattern is a technique used to manage concurrent access to data in databases and other data stores. It involves associating a version number with each record, which is incremented every time the record is updated. This pattern helps ensure that when multiple users or processes attempt to update the same data simultaneously, conflicts can be detected and resolved. -**Programmatic Example** +## Programmatic Example of Version Number Pattern in Java + +Alice and Bob are working on the book, which stored in the database. Our heroes are making changes simultaneously, and we need some mechanism to prevent them from overwriting each other. We have a `Book` entity, which is versioned, and has a copy-constructor: ```java +@Getter +@Setter public class Book { - private long id; - private String title = ""; - private String author = ""; - - private long version = 0; // version number - - public Book(Book book) { - this.id = book.id; - this.title = book.title; - this.author = book.author; - this.version = book.version; - } - - // getters and setters are omitted here + + private long id; + private String title = ""; + private String author = ""; + private long version = 0; // version number + + public Book(Book book) { + this.id = book.id; + this.title = book.title; + this.author = book.author; + this.version = book.version; + } } ``` @@ -65,6 +64,7 @@ We also have `BookRepository`, which implements concurrency control: ```java public class BookRepository { + private final Map collection = new HashMap<>(); public void update(Book book) throws BookNotFoundException, VersionMismatchException { @@ -98,66 +98,99 @@ public class BookRepository { } ``` -Here's the concurrency control in action: +Here's the version number pattern in action: ```java -var bookId = 1; -// Alice and Bob took the book concurrently -final var aliceBook = bookRepository.get(bookId); -final var bobBook = bookRepository.get(bookId); - -aliceBook.setTitle("Kama Sutra"); // Alice has updated book title -bookRepository.update(aliceBook); // and successfully saved book in database -LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion()); - -// now Bob has the stale version of the book with empty title and version = 0 -// while actual book in database has filled title and version = 1 -bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author -try { - LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion()); - bookRepository.update(bobBook); // Bob tries to save his book to database -} catch (VersionMismatchException e) { - // Bob update fails, and book in repository remained untouchable - LOGGER.info("Exception: {}", e.getMessage()); - // Now Bob should reread actual book from repository, do his changes again and save again +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) throws + BookDuplicateException, + BookNotFoundException, + VersionMismatchException { + var bookId = 1; + + var bookRepository = new BookRepository(); + var book = new Book(); + book.setId(bookId); + bookRepository.add(book); // adding a book with empty title and author + LOGGER.info("An empty book with version {} was added to repository", book.getVersion()); + + // Alice and Bob took the book concurrently + final var aliceBook = bookRepository.get(bookId); + final var bobBook = bookRepository.get(bookId); + + aliceBook.setTitle("Kama Sutra"); // Alice has updated book title + bookRepository.update(aliceBook); // and successfully saved book in database + LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion()); + + // now Bob has the stale version of the book with empty title and version = 0 + // while actual book in database has filled title and version = 1 + bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author + try { + LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion()); + bookRepository.update(bobBook); // Bob tries to save his book to database + } catch (VersionMismatchException e) { + // Bob update fails, and book in repository remained untouchable + LOGGER.info("Exception: {}", e.getMessage()); + // Now Bob should reread actual book from repository, do his changes again and save again + } + } } ``` Program output: -```java -Alice updates the book with new version 1 -Bob tries to update the book with his version 0 -Exception: Tried to update stale version 0 while actual version is 1 ``` +14:51:04.119 [main] INFO com.iluwatar.versionnumber.App -- An empty book with version 0 was added to repository +14:51:04.122 [main] INFO com.iluwatar.versionnumber.App -- Alice updates the book with new version 1 +14:51:04.122 [main] INFO com.iluwatar.versionnumber.App -- Bob tries to update the book with his version 0 +14:51:04.123 [main] INFO com.iluwatar.versionnumber.App -- Exception: Tried to update stale version 0 while actual version is 1 +``` + +## When to Use the Version Number Pattern in Java + +* Use when you need to handle concurrent data modifications in a distributed system. +* Suitable for systems where data consistency and integrity are crucial. +* Ideal for applications using databases that support versioning or row versioning features. + +## Version Number Pattern Java Tutorials + +* [JPA entity versioning (byteslounge.com)](https://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking) +* [Optimistic Locking in JPA (Baeldung)](https://www.baeldung.com/jpa-optimistic-locking) +* [Versioning Entity (java2s.com)](http://www.java2s.com/Tutorial/Java/0355__JPA/VersioningEntity.htm) + +## Real-World Applications of Version Number Pattern in Java -## Class diagram +* Hibernate (Java Persistence API) uses version numbers to implement optimistic locking. +* Microsoft SQL Server and Oracle databases support version-based concurrency control. +* Apache CouchDB and other NoSQL databases implement versioning for conflict resolution. +* [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning) +* [Apache Solr](https://lucene.apache.org/solr/guide/6_6/updating-parts-of-documents.html) -![alt text](./etc/version-number.urm.png "Version Number pattern class diagram") +## Benefits and Trade-offs of Version Number Pattern -## Applicability +Benefits: -Use Version Number for: +* Improves data consistency and integrity. +* Reduces the likelihood of lost updates in concurrent environments. +* Provides a mechanism for conflict detection and resolution. -* resolving concurrent write-access to the data -* strong data consistency +Trade-offs: -## Tutorials -* [Version Number Pattern Tutorial](http://www.java2s.com/Tutorial/Java/0355__JPA/VersioningEntity.htm) +* Requires additional logic for version checking and handling conflicts. +* Can lead to increased complexity in database schema and application logic. +* Potential performance overhead due to version checks and conflict resolution. -## Known uses - * [Hibernate](https://vladmihalcea.com/jpa-entity-version-property-hibernate/) - * [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning) - * [Apache Solr](https://lucene.apache.org/solr/guide/6_6/updating-parts-of-documents.html) +## Related Java Design Patterns -## Consequences -Version Number pattern allows to implement a concurrency control, which is usually done -via Optimistic Offline Lock pattern. +* [Optimistic Offline Lock](https://java-design-patterns.com/patterns/optimistic-offline-lock/): Uses version numbers to detect conflicts rather than preventing them from occurring. +* Pessimistic Offline Lock: An alternative approach to concurrency control where data is locked for updates to prevent conflicts. -## Related patterns -* [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) +## References and Credits -## Credits -* [Optimistic Locking in JPA](https://www.baeldung.com/jpa-optimistic-locking) -* [JPA entity versioning](https://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking) -* [J2EE Design Patterns](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3y6yv1z) +* [J2EE Design Patterns](https://amzn.to/4dpzgmx) +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/version-number/pom.xml b/version-number/pom.xml index 54128114ebf2..d3fab29bd77e 100644 --- a/version-number/pom.xml +++ b/version-number/pom.xml @@ -34,6 +34,14 @@ version-number + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/App.java b/version-number/src/main/java/com/iluwatar/versionnumber/App.java index 8b1818e296c9..98e12228c822 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/App.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/App.java @@ -28,16 +28,15 @@ import org.slf4j.LoggerFactory; /** - * The Version Number pattern helps to resolve concurrency conflicts in applications. - * Usually these conflicts arise in database operations, when multiple clients are trying - * to update the same record simultaneously. - * Resolving such conflicts requires determining whether an object has changed. - * For this reason we need a version number that is incremented with each change - * to the underlying data, e.g. database. The version number can be used by repositories - * to check for external changes and to report concurrency issues to the users. + * The Version Number pattern helps to resolve concurrency conflicts in applications. Usually these + * conflicts arise in database operations, when multiple clients are trying to update the same + * record simultaneously. Resolving such conflicts requires determining whether an object has + * changed. For this reason we need a version number that is incremented with each change to the + * underlying data, e.g. database. The version number can be used by repositories to check for + * external changes and to report concurrency issues to the users. * - *

      In this example we show how Alice and Bob will try to update the {@link Book} - * and save it simultaneously to {@link BookRepository}, which represents a typical database. + *

      In this example we show how Alice and Bob will try to update the {@link Book} and save it + * simultaneously to {@link BookRepository}, which represents a typical database. * *

      As in real databases, each client operates with copy of the data instead of original data * passed by reference, that's why we are using {@link Book} copy-constructor here. @@ -50,10 +49,8 @@ public class App { * * @param args command line args */ - public static void main(String[] args) throws - BookDuplicateException, - BookNotFoundException, - VersionMismatchException { + public static void main(String[] args) + throws BookDuplicateException, BookNotFoundException, VersionMismatchException { var bookId = 1; var bookRepository = new BookRepository(); diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/Book.java b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java index f01d4be36086..067e08291546 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/Book.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java @@ -24,59 +24,25 @@ */ package com.iluwatar.versionnumber; -/** - * Model class for Book entity. - */ +import lombok.Getter; +import lombok.Setter; + +/** Model class for Book entity. */ +@Getter +@Setter public class Book { private long id; private String title = ""; private String author = ""; - private long version = 0; // version number - public Book() { + public Book() {} - } - - /** - * We need this copy constructor to copy book representation in {@link BookRepository}. - */ + /** We need this copy constructor to copy book representation in {@link BookRepository}. */ public Book(Book book) { this.id = book.id; this.title = book.title; this.author = book.author; this.version = book.version; } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public long getVersion() { - return version; - } - - public void setVersion(long version) { - this.version = version; - } } diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java index 9763e1fa5362..7651a7bb573e 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.versionnumber; -/** - * When someone has tried to add a book which repository already have. - */ +/** When someone has tried to add a book which repository already have. */ public class BookDuplicateException extends Exception { public BookDuplicateException(String message) { super(message); diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java index 9f45ad0cb5d0..deceebdd106c 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.versionnumber; -/** - * Client has tried to make an operation with book which repository does not have. - */ +/** Client has tried to make an operation with book which repository does not have. */ public class BookNotFoundException extends Exception { public BookNotFoundException(String message) { super(message); diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java index fa062022248c..087b28934f75 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java @@ -24,21 +24,20 @@ */ package com.iluwatar.versionnumber; -import java.util.HashMap; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** - * This repository represents simplified database. - * As a typical database do, repository operates with copies of object. - * So client and repo has different copies of book, which can lead to concurrency conflicts - * as much as in real databases. + * This repository represents simplified database. As a typical database do, repository operates + * with copies of object. So client and repo has different copies of book, which can lead to + * concurrency conflicts as much as in real databases. */ public class BookRepository { - private final Map collection = new HashMap<>(); + private final ConcurrentHashMap collection = new ConcurrentHashMap<>(); + private final Object lock = new Object(); /** - * Adds book to collection. - * Actually we are putting copy of book (saving a book by value, not by reference); + * Adds book to collection. Actually we are putting copy of book (saving a book by value, not by + * reference); */ public void add(Book book) throws BookDuplicateException { if (collection.containsKey(book.getId())) { @@ -49,32 +48,34 @@ public void add(Book book) throws BookDuplicateException { collection.put(book.getId(), new Book(book)); } - /** - * Updates book in collection only if client has modified the latest version of the book. - */ + /** Updates book in collection only if client has modified the latest version of the book. */ public void update(Book book) throws BookNotFoundException, VersionMismatchException { if (!collection.containsKey(book.getId())) { throw new BookNotFoundException("Not found book with id: " + book.getId()); } - var latestBook = collection.get(book.getId()); - if (book.getVersion() != latestBook.getVersion()) { - throw new VersionMismatchException( - "Tried to update stale version " + book.getVersion() - + " while actual version is " + latestBook.getVersion() - ); - } + // used synchronized block to ensure only one thread compares and update the version + synchronized (lock) { + var latestBook = collection.get(book.getId()); + if (book.getVersion() != latestBook.getVersion()) { + throw new VersionMismatchException( + "Tried to update stale version " + + book.getVersion() + + " while actual version is " + + latestBook.getVersion()); + } - // update version, including client representation - modify by reference here - book.setVersion(book.getVersion() + 1); + // update version, including client representation - modify by reference here + book.setVersion(book.getVersion() + 1); - // save book copy to repository - collection.put(book.getId(), new Book(book)); + // save book copy to repository + collection.put(book.getId(), new Book(book)); + } } /** - * Returns book representation to the client. - * Representation means we are returning copy of the book. + * Returns book representation to the client. Representation means we are returning copy of the + * book. */ public Book get(long bookId) throws BookNotFoundException { if (!collection.containsKey(bookId)) { diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java index da4d2916258b..68d878333c07 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.versionnumber; -/** - * Client has tried to update a stale version of the book. - */ +/** Client has tried to update a stale version of the book. */ public class VersionMismatchException extends Exception { public VersionMismatchException(String message) { super(message); diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java index a47da0e8366e..640fd95330fa 100644 --- a/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java +++ b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.versionnumber; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ class AppTest { /** * Issue: Add at least one assertion to this test case. * - * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} - * throws an exception. + *

      Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java index b480cfd41f61..f97e67150c2b 100644 --- a/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java +++ b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java @@ -24,14 +24,12 @@ */ package com.iluwatar.versionnumber; +import static org.junit.jupiter.api.Assertions.*; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for {@link BookRepository} - */ +/** Tests for {@link BookRepository} */ class BookRepositoryTest { private final long bookId = 1; private final BookRepository bookRepository = new BookRepository(); @@ -50,7 +48,8 @@ void testDefaultVersionRemainsZeroAfterAdd() throws BookNotFoundException { } @Test - void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() throws BookNotFoundException, VersionMismatchException { + void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() + throws BookNotFoundException, VersionMismatchException { final var aliceBook = bookRepository.get(bookId); final var bobBook = bookRepository.get(bookId); @@ -66,7 +65,8 @@ void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() throws BookNotFoundE } @Test - void testShouldThrowVersionMismatchExceptionOnStaleUpdate() throws BookNotFoundException, VersionMismatchException { + void testShouldThrowVersionMismatchExceptionOnStaleUpdate() + throws BookNotFoundException, VersionMismatchException { final var aliceBook = bookRepository.get(bookId); final var bobBook = bookRepository.get(bookId); diff --git a/virtual-proxy/.gitignore b/virtual-proxy/.gitignore new file mode 100644 index 000000000000..0d952be47a0d --- /dev/null +++ b/virtual-proxy/.gitignore @@ -0,0 +1,59 @@ +################## Eclipse ###################### +target +.metadata +.settings +.classpath +.project +*.class +tmp/ +*.tmp +*.bak +*~.nib +local.properties +.loadpath +.recommenders +.DS_Store + +####### Java annotation processor (APT) ######## +.factorypath + +################ Package Files ################## +*.jar +*.war +*.ear +*.swp +datanucleus.log +/bin/ +*.log +event-sourcing/Journal.json + +################## Checkstyle ################### +.checkstyle + +##################### STS ####################### +.apt_generated +.springBeans +.sts4-cache + +################# IntelliJ IDEA ################# +.idea +*.iws +*.iml +*.ipr + +################### NetBeans #################### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +#################### VS Code #################### +.vscode/ + +#################### Java Design Patterns ####### +etc/Java Design Patterns.urm.puml +serialized-entity/output.txt \ No newline at end of file diff --git a/virtual-proxy/README.md b/virtual-proxy/README.md new file mode 100644 index 000000000000..e5b32fd096a4 --- /dev/null +++ b/virtual-proxy/README.md @@ -0,0 +1,160 @@ +--- +title: "Virtual Proxy Pattern in Java: Enhancing Performance with Lazy Loading Techniques" +shortTitle: Virtual Proxy +description: "Explore the Virtual Proxy Design Pattern in Java to improve your applications. Learn how this pattern optimizes performance and manages resources efficiently by controlling the creation and access of resource-intensive objects. Ideal for developers looking to enhance system responsiveness." +category: Structural +language: en +tag: + - Caching + - Decoupling + - Lazy initialization +--- + +## Also known as + +* Lazy Initialization Proxy +* Virtual Surrogate + +## Intent of Virtual Proxy Design Pattern + +The Virtual Proxy Design Pattern is a crucial component in Java design patterns, enabling efficient resource management and performance optimization through controlled object creation. It provides a surrogate or placeholder for another object to control its creation and access, particularly when dealing with resource-intensive operations. + +## Detailed Explanation of Virtual Proxy Pattern with Real-World Examples + +Real-world example + +> Just as a high-end art gallery uses photographs to save resources and reduce risks, the Virtual Proxy Pattern in Java manages resource-intensive operations by displaying only necessary objects, significantly enhancing system efficiency. To protect the actual artwork and reduce the risk of damage or theft, the gallery initially displays high-quality photographs of the artworks. When a serious buyer expresses genuine interest, the gallery then brings out the original artwork from a secure storage area for viewing. +> +> In this analogy, the high-quality photograph serves as the virtual proxy for the actual artwork. The real artwork is only fetched and displayed when truly necessary, thus saving resources and reducing risk, similar to how the Virtual Proxy pattern defers object creation until it is needed. + +In plain words + +> The virtual proxy pattern allows a representative class to stand in for another class to control access to it, particularly for resource-intensive operations. + +Wikipedia says + +> A proxy that controls access to a resource that is expensive to create. + +## Programmatic Example of Virtual Proxy Pattern in Java + +The Virtual Proxy design pattern in Java can optimize resource utilization and system performance. + +Consider an online video streaming platform where video objects are resource-intensive due to their large data size and required processing power. To efficiently manage resources, the system uses a virtual proxy to handle video objects. The virtual proxy defers the creation of actual video objects until they are explicitly required for playback, thus saving system resources and improving response times for users. + +Given our example of a video streaming service, here is how it might be implemented: + +First, we define an `ExpensiveObject` interface, which outlines the method for processing video. + +```java +public interface ExpensiveObject { + void process(); +} +``` + +Here’s the implementation of a `RealVideoObject` that represents an expensive-to-create video object. + +```java +@Slf4j +@Getter +public class RealVideoObject implements ExpensiveObject { + + public RealVideoObject() { + heavyInitialConfiguration(); + } + + private void heavyInitialConfiguration() { + LOGGER.info("Loading initial video configurations..."); + } + + @Override + public void process() { + LOGGER.info("Processing and playing video content..."); + } +} +``` + +The `VideoObjectProxy` serves as a stand-in for `RealExpensiveObject`. + +```java +@Getter +public class VideoObjectProxy implements ExpensiveObject { + + private RealVideoObject realVideoObject; + + public void setRealVideoObject(RealVideoObject realVideoObject) { + this.realVideoObject = realVideoObject; + } + + @Override + public void process() { + if (realVideoObject == null) { + realVideoObject = new RealVideoObject(); + } + realVideoObject.process(); + } +} +``` + +And here’s how the proxy is used in the system. + +```java +public static void main(String[] args) { + ExpensiveObject videoObject = new VideoObjectProxy(); + videoObject.process(); // The first call creates and plays the video + videoObject.process(); // Subsequent call uses the already created object +} +``` + +Program output: + +``` +14:54:30.602 [main] INFO com.iluwatar.virtual.proxy.RealVideoObject -- Loading initial video configurations... +14:54:30.604 [main] INFO com.iluwatar.virtual.proxy.RealVideoObject -- Processing and playing video content... +14:54:30.604 [main] INFO com.iluwatar.virtual.proxy.RealVideoObject -- Processing and playing video content... +``` + +## When to Use the Virtual Proxy Pattern in Java + +Use the Virtual Proxy pattern when: + +* Object creation is resource-intensive, and not all instances are utilized immediately or ever. +* The performance of a system can be significantly improved by deferring the creation of objects until they are needed. +* There is a need for control over resource usage in systems dealing with large quantities of high-overhead objects. + +## Virtual Proxy Pattern Java Tutorials + +* [The Proxy Pattern in Java (Baeldung)](https://www.baeldung.com/java-proxy-pattern) + +## Real-World Applications of Virtual Proxy Pattern in Java + +* Lazy Initialization: Create objects only when they are actually needed. +* Resource Management: Efficiently manage resources by creating heavy objects only on demand. +* Access Control: Include logic to check conditions before delegating calls to the actual object. +* Performance Optimization: Incorporate caching of results or states that are expensive to obtain or compute. +* Data Binding and Integration: Delay integration and binding processes until the data is actually needed. +* In Java, the `java.awt.Image` class uses virtual proxies to load images on demand. +* Hibernate ORM framework uses proxies to implement lazy loading of entities. + +## Benefits and Trade-offs of Virtual Proxy Pattern + +Benefits: + +* Reduces memory usage by deferring object creation. +* Can improve performance by delaying heavy operations until needed. + +Trade-offs: + +* Introduces complexity in the codebase. +* Can lead to unexpected behaviors if not handled properly, especially in multithreaded environments. + +## Related Java Design Patterns + +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Virtual Proxy is a specific type of the Proxy pattern focused on lazy initialization. +* [Lazy Loading](https://java-design-patterns.com/patterns/lazy-loading/): Directly related as the core idea of Virtual Proxy is to defer object creation. +* [Decorator](https://java-design-patterns.com/patterns/decorator/): Similar in structure, but Decorators add behavior to the objects while proxies control access. + +## References and Credits + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/virtual-proxy/etc/virtual-proxy.urm.puml b/virtual-proxy/etc/virtual-proxy.urm.puml new file mode 100644 index 000000000000..e30149e3809e --- /dev/null +++ b/virtual-proxy/etc/virtual-proxy.urm.puml @@ -0,0 +1,26 @@ +@startuml +package com.iluwatar.virtual.proxy { + class App { + + App() + + main(args : String[]) {static} + } + interface ExpensiveObject { + + process() {abstract} + } + class RealVideoObject { + - LOGGER : Logger {static} + + RealVideoObject() + - heavyInitialConfiguration() + + process() + } + class VideoObjectProxy { + - realVideoObject : RealVideoObject + + VideoObjectProxy() + + getRealVideoObject() : RealVideoObject + + process() + } +} +VideoObjectProxy --> "-realVideoObject" RealVideoObject +RealVideoObject ..|> ExpensiveObject +VideoObjectProxy ..|> ExpensiveObject +@enduml \ No newline at end of file diff --git a/virtual-proxy/etc/virtual.proxy.urm.png b/virtual-proxy/etc/virtual.proxy.urm.png new file mode 100644 index 000000000000..1baf530bd2d8 Binary files /dev/null and b/virtual-proxy/etc/virtual.proxy.urm.png differ diff --git a/virtual-proxy/etc/virtual.proxy.urm.puml b/virtual-proxy/etc/virtual.proxy.urm.puml new file mode 100644 index 000000000000..9d3cc5d5d415 --- /dev/null +++ b/virtual-proxy/etc/virtual.proxy.urm.puml @@ -0,0 +1,29 @@ +@startuml +class Client { + + main(args : String[]) {static} +} + +class RealVideoObject { + - videoData : String + + RealVideoObject() + + process() + + getVideoData() : String + - heavyInitialConfiguration() : void +} + +interface ExpensiveObject { + + process() {abstract} +} + +class VideoObjectProxy { + - realVideoObject : RealVideoObject + + VideoObjectProxy() + + process() + + setRealVideoObject(realVideoObject : RealVideoObject) : void + + getRealVideoObject() : RealVideoObject +} + +VideoObjectProxy --> "-realVideoObject" RealVideoObject +RealVideoObject ..|> ExpensiveObject +VideoObjectProxy ..|> ExpensiveObject +@enduml diff --git a/virtual-proxy/pom.xml b/virtual-proxy/pom.xml new file mode 100644 index 000000000000..d9cf174f9a4e --- /dev/null +++ b/virtual-proxy/pom.xml @@ -0,0 +1,82 @@ + + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + virtual-proxy + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + org.hamcrest + hamcrest + 3.0 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + App + + + + + + + + + \ No newline at end of file diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/App.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/App.java new file mode 100644 index 000000000000..3f78bfe8178d --- /dev/null +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/App.java @@ -0,0 +1,40 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.virtual.proxy; + +/** The main application class that sets up and runs the Virtual Proxy pattern demo. */ +public class App { + /** + * The entry point of the application. + * + * @param args the command line arguments + */ + public static void main(String[] args) { + ExpensiveObject videoObject = new VideoObjectProxy(); + videoObject.process(); // The first call creates and plays the video + videoObject.process(); // Subsequent call uses the already created object + } +} diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/ExpensiveObject.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/ExpensiveObject.java new file mode 100644 index 000000000000..12e183b4e2fa --- /dev/null +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/ExpensiveObject.java @@ -0,0 +1,30 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.virtual.proxy; + +/** Interface for expensive object and proxy object. */ +public interface ExpensiveObject { + void process(); +} diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/RealVideoObject.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/RealVideoObject.java new file mode 100644 index 000000000000..afadf1df6549 --- /dev/null +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/RealVideoObject.java @@ -0,0 +1,48 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.virtual.proxy; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** Represents a real video object that is expensive to create and manage. */ +@Slf4j +@Getter +public class RealVideoObject implements ExpensiveObject { + + public RealVideoObject() { + heavyInitialConfiguration(); + } + + private void heavyInitialConfiguration() { + LOGGER.info("Loading initial video configurations..."); + } + + @Override + public void process() { + LOGGER.info("Processing and playing video content..."); + } +} diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/VideoObjectProxy.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/VideoObjectProxy.java new file mode 100644 index 000000000000..9138fc95b883 --- /dev/null +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/VideoObjectProxy.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.virtual.proxy; + +import lombok.Getter; + +/** + * A proxy class for the real video object, providing a layer of control over the object + * instantiation. + */ +@Getter +public class VideoObjectProxy implements ExpensiveObject { + private RealVideoObject realVideoObject; + + @Override + public void process() { + if (realVideoObject == null) { + realVideoObject = new RealVideoObject(); + } + realVideoObject.process(); + } +} diff --git a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/AppTest.java b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/AppTest.java new file mode 100644 index 000000000000..b4b424e94359 --- /dev/null +++ b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/AppTest.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.virtual.proxy; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +/** Application test */ +class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/RealVideoObjectTest.java b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/RealVideoObjectTest.java new file mode 100644 index 000000000000..d3d471ad0987 --- /dev/null +++ b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/RealVideoObjectTest.java @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.virtual.proxy; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +/** Tests for RealVideoObject. */ +class RealVideoObjectTest { + + @Test + void testVideoObject() { + var videoObject = new RealVideoObject(); + assertThat(videoObject, instanceOf(ExpensiveObject.class)); + } + + @Test + public void constructorDoesNotThrowException() { + assertDoesNotThrow(RealVideoObject::new, "Constructor should not throw any exception"); + } + + @Test + public void processDoesNotThrowException() { + RealVideoObject realVideoObject = new RealVideoObject(); + assertDoesNotThrow(realVideoObject::process, "Process method should not throw any exception"); + } +} diff --git a/layers/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/VideoObjectProxyTest.java similarity index 65% rename from layers/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java rename to virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/VideoObjectProxyTest.java index 7ab244edf78f..7d91b7c39fed 100644 --- a/layers/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java +++ b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/VideoObjectProxyTest.java @@ -22,36 +22,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.layers.exception; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +package com.iluwatar.virtual.proxy; -import exception.CakeBakingException; -import org.junit.jupiter.api.Test; - -/** - * Date: 12/15/15 - 7:57 PM - * - * @author Jeroen Meulemeester - */ - -class CakeBakingExceptionTest { +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.jupiter.api.Assertions.*; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; +/** Tests for VideoObjectProxy. */ +public class VideoObjectProxyTest { @Test - void testConstructor() { - final var exception = new CakeBakingException(); - assertNull(exception.getMessage()); - assertNull(exception.getCause()); + void shouldBeInstanceOfExpensiveObject() { + MatcherAssert.assertThat(new VideoObjectProxy(), instanceOf(ExpensiveObject.class)); } @Test - void testConstructorWithMessage() { - final var expectedMessage = "message"; - final var exception = new CakeBakingException(expectedMessage); - assertEquals(expectedMessage, exception.getMessage()); - assertNull(exception.getCause()); + void constructorDoesNotThrowException() { + assertDoesNotThrow(VideoObjectProxy::new, "Constructor should not throw any exception"); } + @Test + void processDoesNotThrowException() { + assertDoesNotThrow( + () -> new VideoObjectProxy().process(), "Process method should not throw any exception"); + } } diff --git a/visitor/README.md b/visitor/README.md index ce184859ee3e..674062606a31 100644 --- a/visitor/README.md +++ b/visitor/README.md @@ -1,38 +1,42 @@ --- -title: Visitor +title: "Visitor Pattern in Java: Implementing Robust Operations Across Diverse Object Structures" +shortTitle: Visitor +description: "Explore the Visitor design pattern in Java with detailed examples and class diagrams. Learn how to implement operations without altering object structures for clean and maintainable code." category: Behavioral language: en tag: - - Gang of Four + - Decoupling + - Extensibility + - Gang of Four + - Object composition + - Polymorphism --- -## Intent +## Intent of Visitor Design Pattern -Represent an operation to be performed on the elements of an object structure. Visitor lets you -define a new operation without changing the classes of the elements on which it operates. +To represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. -## Explanation +## Detailed Explanation of Visitor Pattern with Real-World Examples Real-world example -> Consider a tree structure with army units. Commander has two sergeants under it and each sergeant -> has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can -> easily create new objects that interact with the commander, sergeants, soldiers, or all of them. +> An analogous real-world example of the Visitor design pattern can be seen in a museum tour guide system. Imagine a museum where visitors can take guided tours to learn about different types of exhibits, such as paintings, sculptures, and historical artifacts. Each exhibit type requires a different explanation, which is provided by specialized tour guides. +> +> In this scenario, the exhibits are like the elements in the Visitor pattern, and the tour guides are like the visitors. The museum structure remains unchanged, but new guides with new types of tours (operations) can be added without modifying the exhibits themselves. Each guide (visitor) implements a specific way to interact with the exhibits, providing detailed explanations according to their specialization, thereby separating the operations from the objects they operate on. In plain words -> Visitor pattern defines operations that can be performed on the nodes of the data structure. +> The Java Visitor pattern defines operations that can be performed on nodes of various data structures, enhancing Java application extensibility. Wikipedia says -> In object-oriented programming and software engineering, the visitor design pattern is a way of -> separating an algorithm from an object structure on which it operates. A practical result of this -> separation is the ability to add new operations to existing object structures without modifying -> the structures. +> In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. -**Programmatic Example** +## Programmatic Example of Visitor Pattern in Java -Given the army unit example from above, we first have the Unit and UnitVisitor base types. +Consider a tree structure with army units. Commander has two sergeants under it and each sergeant has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can easily create new objects that interact with the commander, sergeants, soldiers, or all of them. + +Given the army unit example from above, we first have the `Unit` and `UnitVisitor` base types. ```java public abstract class Unit { @@ -48,6 +52,9 @@ public abstract class Unit { } } +``` + +```java public interface UnitVisitor { void visit(Soldier soldier); @@ -58,7 +65,7 @@ public interface UnitVisitor { } ``` -Then we have the concrete units. +Then we have the concrete units `Commander`, `Sergeant`, and `Soldier`. ```java public class Commander extends Unit { @@ -78,7 +85,9 @@ public class Commander extends Unit { return "commander"; } } +``` +```java public class Sergeant extends Unit { public Sergeant(Unit... children) { @@ -96,7 +105,9 @@ public class Sergeant extends Unit { return "sergeant"; } } +``` +```java public class Soldier extends Unit { public Soldier(Unit... children) { @@ -116,7 +127,7 @@ public class Soldier extends Unit { } ``` -Here are then some concrete visitors. +Here are the concrete visitors `CommanderVisitor`, `SergeantVisitor`, and `SoldierVisitor`. ```java @Slf4j @@ -137,7 +148,9 @@ public class CommanderVisitor implements UnitVisitor { LOGGER.info("Good to see you {}", commander); } } +``` +```java @Slf4j public class SergeantVisitor implements UnitVisitor { @@ -156,7 +169,9 @@ public class SergeantVisitor implements UnitVisitor { // Do nothing } } +``` +```java @Slf4j public class SoldierVisitor implements UnitVisitor { @@ -180,52 +195,83 @@ public class SoldierVisitor implements UnitVisitor { Finally, we can show the power of visitors in action. ```java -commander.accept(new SoldierVisitor()); -commander.accept(new SergeantVisitor()); -commander.accept(new CommanderVisitor()); +public static void main(String[] args) { + + var commander = new Commander( + new Sergeant(new Soldier(), new Soldier(), new Soldier()), + new Sergeant(new Soldier(), new Soldier(), new Soldier()) + ); + commander.accept(new SoldierVisitor()); + commander.accept(new SergeantVisitor()); + commander.accept(new CommanderVisitor()); +} ``` Program output: ``` -Greetings soldier -Greetings soldier -Greetings soldier -Greetings soldier -Greetings soldier -Greetings soldier -Hello sergeant -Hello sergeant -Good to see you commander +14:58:06.115 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier +14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier +14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier +14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier +14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier +14:58:06.118 [main] INFO com.iluwatar.visitor.SoldierVisitor -- Greetings soldier +14:58:06.118 [main] INFO com.iluwatar.visitor.SergeantVisitor -- Hello sergeant +14:58:06.118 [main] INFO com.iluwatar.visitor.SergeantVisitor -- Hello sergeant +14:58:06.118 [main] INFO com.iluwatar.visitor.CommanderVisitor -- Good to see you commander ``` -## Class diagram +## Detailed Explanation of Visitor Pattern with Real-World Examples -![alt text](./etc/visitor_1.png "Visitor") +![Visitor](./etc/visitor_1.png "Visitor") -## Applicability +## When to Use the Visitor Pattern in Java Use the Visitor pattern when -* An object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes. -* Many distinct and unrelated operations need to be performed on objects in an object structure, and you want to avoid "polluting" their classes with these operations. Visitor lets you keep related operations together by defining them in one class. When the object structure is shared by many applications, use Visitor to put operations in just those applications that need them. -* The classes defining the object structure rarely change, but you often want to define new operations over the structure. Changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it's probably better to define the operations in those classes. +* Implement the Visitor design pattern in Java when you need to efficiently perform operations across groups of similar objects without modifying their classes, and you want to avoid polluting their classes with this operation. +* Use it when a class structure is stable, but you need to perform new operations on the structure without changing it. +* It's beneficial when the set of classes are fixed and only the operations need to be extended. -## Tutorials +## Visitor Pattern Java Tutorials -* [Refactoring Guru](https://refactoring.guru/design-patterns/visitor) -* [Dzone](https://dzone.com/articles/design-patterns-visitor) -* [Sourcemaking](https://sourcemaking.com/design_patterns/visitor) +* [Visitor (Refactoring Guru)](https://refactoring.guru/design-patterns/visitor) +* [Visitor Pattern Tutorial with Java Examples (DZone)](https://dzone.com/articles/design-patterns-visitor) +* [Visitor Design Pattern (Sourcemaking)](https://sourcemaking.com/design_patterns/visitor) -## Known uses +## Real-World Applications of Visitor Pattern in Java +* Compiler design, where the Visitor pattern can be used for operations like pretty printing, semantic checks, etc. +* Abstract Syntax Tree (AST) processing. +* Document structure processing (e.g., HTML, XML). * [Apache Wicket](https://github.com/apache/wicket) component tree, see [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) * [javax.lang.model.element.AnnotationValue](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValue.html) and [AnnotationValueVisitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValueVisitor.html) * [javax.lang.model.element.Element](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Element.html) and [Element Visitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/ElementVisitor.html) * [java.nio.file.FileVisitor](http://docs.oracle.com/javase/8/docs/api/java/nio/file/FileVisitor.html) -## Credits +## Benefits and Trade-offs of Visitor Pattern + +Benefits: + +* Simplifies adding new operations: Adding a new operation is straightforward because you can add a new visitor without changing existing code. +* Single Responsibility Principle: The Visitor pattern allows you to move related behavior into one class. +* Open/Closed Principle: Elements can stay closed to modification while visitors are open to extension. + +Trade-offs: + +* Adding new element classes: If you need to add new types of elements, you'll need to change both the visitor interface and all of its concrete visitors. +* Circular dependencies: In complex systems, this pattern can introduce circular dependencies between visitor and element classes. +* Breaking encapsulation: Visitor pattern requires that the element classes expose enough details to allow the visitor to do its job, potentially breaking encapsulation. + +## Related Java Design Patterns + +* [Composite](https://java-design-patterns.com/patterns/composite/): The Visitor pattern is often used in conjunction with the Composite pattern, where the visitor can perform operations over a composite structure. +* [Interpreter](https://java-design-patterns.com/patterns/interpreter/): Visitors can be used to implement the non-terminal expressions in the Interpreter pattern. +* [Strategy](https://java-design-patterns.com/patterns/strategy/): Visitor can be considered a way of making strategies work on objects that they were not designed to operate on. + +## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) -* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) -* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/visitor/pom.xml b/visitor/pom.xml index 5825b7dbdf55..480fafd0350a 100644 --- a/visitor/pom.xml +++ b/visitor/pom.xml @@ -34,6 +34,14 @@ visitor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/visitor/src/main/java/com/iluwatar/visitor/App.java b/visitor/src/main/java/com/iluwatar/visitor/App.java index b6246ecff36f..61c91fbb39fb 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/App.java +++ b/visitor/src/main/java/com/iluwatar/visitor/App.java @@ -25,12 +25,12 @@ package com.iluwatar.visitor; /** - *

      Visitor pattern defines a mechanism to apply operations on nodes in a hierarchy. New - * operations can be added without altering the node interface.

      + * Visitor pattern defines a mechanism to apply operations on nodes in a hierarchy. New operations + * can be added without altering the node interface. * *

      In this example there is a unit hierarchy beginning from {@link Commander}. This hierarchy is * traversed by visitors. {@link SoldierVisitor} applies its operation on {@link Soldier}s, {@link - * SergeantVisitor} on {@link Sergeant}s and so on.

      + * SergeantVisitor} on {@link Sergeant}s and so on. */ public class App { @@ -41,13 +41,12 @@ public class App { */ public static void main(String[] args) { - var commander = new Commander( - new Sergeant(new Soldier(), new Soldier(), new Soldier()), - new Sergeant(new Soldier(), new Soldier(), new Soldier()) - ); + var commander = + new Commander( + new Sergeant(new Soldier(), new Soldier(), new Soldier()), + new Sergeant(new Soldier(), new Soldier(), new Soldier())); commander.accept(new SoldierVisitor()); commander.accept(new SergeantVisitor()); commander.accept(new CommanderVisitor()); - } } diff --git a/visitor/src/main/java/com/iluwatar/visitor/Commander.java b/visitor/src/main/java/com/iluwatar/visitor/Commander.java index 01c8a43ba8c8..b6d2af80d64d 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Commander.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Commander.java @@ -1,50 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Commander. - */ -public class Commander extends Unit { - - public Commander(Unit... children) { - super(children); - } - - /** - * Accept a Visitor. - * @param visitor UnitVisitor to be accepted - */ - @Override - public void accept(UnitVisitor visitor) { - visitor.visit(this); - super.accept(visitor); - } - - @Override - public String toString() { - return "commander"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Commander. */ +public class Commander extends Unit { + + public Commander(Unit... children) { + super(children); + } + + /** + * Accept a Visitor. + * + * @param visitor UnitVisitor to be accepted + */ + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "commander"; + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java index a0286163a618..326d28764030 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java @@ -1,61 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -import lombok.extern.slf4j.Slf4j; - -/** - * CommanderVisitor. - */ -@Slf4j -public class CommanderVisitor implements UnitVisitor { - - /** - * Soldier Visitor method. - * @param soldier Soldier to be visited - */ - @Override - public void visit(Soldier soldier) { - // Do nothing - } - - /** - * Sergeant Visitor method. - * @param sergeant Sergeant to be visited - */ - @Override - public void visit(Sergeant sergeant) { - // Do nothing - } - - /** - * Commander Visitor method. - * @param commander Commander to be visited - */ - @Override - public void visit(Commander commander) { - LOGGER.info("Good to see you {}", commander); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +import lombok.extern.slf4j.Slf4j; + +/** CommanderVisitor. */ +@Slf4j +public class CommanderVisitor implements UnitVisitor { + + /** + * Soldier Visitor method. + * + * @param soldier Soldier to be visited + */ + @Override + public void visit(Soldier soldier) { + // Do nothing + } + + /** + * Sergeant Visitor method. + * + * @param sergeant Sergeant to be visited + */ + @Override + public void visit(Sergeant sergeant) { + // Do nothing + } + + /** + * Commander Visitor method. + * + * @param commander Commander to be visited + */ + @Override + public void visit(Commander commander) { + LOGGER.info("Good to see you {}", commander); + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java b/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java index ed9fc98b5333..11ef218b22bd 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java @@ -1,50 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Sergeant. - */ -public class Sergeant extends Unit { - - public Sergeant(Unit... children) { - super(children); - } - - /** - * Accept a Visitor. - * @param visitor UnitVisitor to be accepted - */ - @Override - public void accept(UnitVisitor visitor) { - visitor.visit(this); - super.accept(visitor); - } - - @Override - public String toString() { - return "sergeant"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Sergeant. */ +public class Sergeant extends Unit { + + public Sergeant(Unit... children) { + super(children); + } + + /** + * Accept a Visitor. + * + * @param visitor UnitVisitor to be accepted + */ + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "sergeant"; + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java index 3377806b94a6..09f4fd4bb1e1 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java @@ -1,61 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -import lombok.extern.slf4j.Slf4j; - -/** - * SergeantVisitor. - */ -@Slf4j -public class SergeantVisitor implements UnitVisitor { - - /** - * Soldier Visitor method. - * @param soldier Soldier to be visited - */ - @Override - public void visit(Soldier soldier) { - // Do nothing - } - - /** - * Sergeant Visitor method. - * @param sergeant Sergeant to be visited - */ - @Override - public void visit(Sergeant sergeant) { - LOGGER.info("Hello {}", sergeant); - } - - /** - * Commander Visitor method. - * @param commander Commander to be visited - */ - @Override - public void visit(Commander commander) { - // Do nothing - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +import lombok.extern.slf4j.Slf4j; + +/** SergeantVisitor. */ +@Slf4j +public class SergeantVisitor implements UnitVisitor { + + /** + * Soldier Visitor method. + * + * @param soldier Soldier to be visited + */ + @Override + public void visit(Soldier soldier) { + // Do nothing + } + + /** + * Sergeant Visitor method. + * + * @param sergeant Sergeant to be visited + */ + @Override + public void visit(Sergeant sergeant) { + LOGGER.info("Hello {}", sergeant); + } + + /** + * Commander Visitor method. + * + * @param commander Commander to be visited + */ + @Override + public void visit(Commander commander) { + // Do nothing + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/Soldier.java b/visitor/src/main/java/com/iluwatar/visitor/Soldier.java index 30398374e3c1..565bcbdef535 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Soldier.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Soldier.java @@ -1,50 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Soldier. - */ -public class Soldier extends Unit { - - public Soldier(Unit... children) { - super(children); - } - - /** - * Accept a Visitor. - * @param visitor UnitVisitor to be accepted - */ - @Override - public void accept(UnitVisitor visitor) { - visitor.visit(this); - super.accept(visitor); - } - - @Override - public String toString() { - return "soldier"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Soldier. */ +public class Soldier extends Unit { + + public Soldier(Unit... children) { + super(children); + } + + /** + * Accept a Visitor. + * + * @param visitor UnitVisitor to be accepted + */ + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "soldier"; + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java index 6827c82d5268..eb722ec861c2 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java @@ -1,61 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -import lombok.extern.slf4j.Slf4j; - -/** - * SoldierVisitor. - */ -@Slf4j -public class SoldierVisitor implements UnitVisitor { - - /** - * Soldier Visitor method. - * @param soldier Soldier to be visited - */ - @Override - public void visit(Soldier soldier) { - LOGGER.info("Greetings {}", soldier); - } - - /** - * Sergeant Visitor method. - * @param sergeant Sergeant to be visited - */ - @Override - public void visit(Sergeant sergeant) { - // Do nothing - } - - /** - * Commander Visitor method. - * @param commander Commander to be visited - */ - @Override - public void visit(Commander commander) { - // Do nothing - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +import lombok.extern.slf4j.Slf4j; + +/** SoldierVisitor. */ +@Slf4j +public class SoldierVisitor implements UnitVisitor { + + /** + * Soldier Visitor method. + * + * @param soldier Soldier to be visited + */ + @Override + public void visit(Soldier soldier) { + LOGGER.info("Greetings {}", soldier); + } + + /** + * Sergeant Visitor method. + * + * @param sergeant Sergeant to be visited + */ + @Override + public void visit(Sergeant sergeant) { + // Do nothing + } + + /** + * Commander Visitor method. + * + * @param commander Commander to be visited + */ + @Override + public void visit(Commander commander) { + // Do nothing + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/Unit.java b/visitor/src/main/java/com/iluwatar/visitor/Unit.java index cd5fc0b35e3e..9bf3938da391 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Unit.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Unit.java @@ -26,9 +26,7 @@ import java.util.Arrays; -/** - * Interface for the nodes in hierarchy. - */ +/** Interface for the nodes in hierarchy. */ public abstract class Unit { private final Unit[] children; @@ -37,9 +35,7 @@ public Unit(Unit... children) { this.children = children; } - /** - * Accept visitor. - */ + /** Accept visitor. */ public void accept(UnitVisitor visitor) { Arrays.stream(children).forEach(child -> child.accept(visitor)); } diff --git a/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java index af60902289fc..c2f93d839b78 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java @@ -1,38 +1,35 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Visitor interface. - */ -public interface UnitVisitor { - - void visit(Soldier soldier); - - void visit(Sergeant sergeant); - - void visit(Commander commander); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Visitor interface. */ +public interface UnitVisitor { + + void visit(Soldier soldier); + + void visit(Sergeant sergeant); + + void visit(Commander commander); +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java index 9fecce713a43..642b6b48972c 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.visitor; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test. - */ +import org.junit.jupiter.api.Test; + +/** Application test. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java index 070667e2bc9a..2961fc54e688 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java @@ -27,16 +27,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -/** - * Date: 12/30/15 - 19:45 PM. - * - * @author Jeroen Meulemeester - */ +/** CommanderTest */ class CommanderTest extends UnitTest { - /** - * Create a new test instance for the given {@link Commander}. - */ + /** Create a new test instance for the given {@link Commander}. */ public CommanderTest() { super(Commander::new); } @@ -45,5 +39,4 @@ public CommanderTest() { void verifyVisit(Commander unit, UnitVisitor mockedVisitor) { verify(mockedVisitor).visit(eq(unit)); } - -} \ No newline at end of file +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java index 4bd3fbb09d1f..96a1cd49d2a5 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java @@ -24,25 +24,11 @@ */ package com.iluwatar.visitor; -import java.util.Optional; - -/** - * Date: 12/30/15 - 18:43 PM. - * - * @author Jeroen Meulemeester - */ +/** CommanderVisitorTest */ class CommanderVisitorTest extends VisitorTest { - /** - * Create a new test instance for the given visitor. - */ + /** Create a new test instance for the given visitor. */ public CommanderVisitorTest() { - super( - new CommanderVisitor(), - Optional.of("Good to see you commander"), - Optional.empty(), - Optional.empty() - ); + super(new CommanderVisitor(), ("Good to see you commander"), null, null); } - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java index 882faa60767d..cf3af93bb1c9 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java @@ -27,16 +27,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -/** - * Date: 12/30/15 - 19:45 PM. - * - * @author Jeroen Meulemeester - */ +/** SergeantTest */ class SergeantTest extends UnitTest { - /** - * Create a new test instance for the given {@link Sergeant}. - */ + /** Create a new test instance for the given {@link Sergeant}. */ public SergeantTest() { super(Sergeant::new); } @@ -45,5 +39,4 @@ public SergeantTest() { void verifyVisit(Sergeant unit, UnitVisitor mockedVisitor) { verify(mockedVisitor).visit(eq(unit)); } - -} \ No newline at end of file +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java index a543ed3ef2c3..a0468f6cbceb 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java @@ -24,25 +24,11 @@ */ package com.iluwatar.visitor; -import java.util.Optional; - -/** - * Date: 12/30/15 - 18:36 PM. - * - * @author Jeroen Meulemeester - */ +/** SergeantVisitorTest */ class SergeantVisitorTest extends VisitorTest { - /** - * Create a new test instance for the given visitor. - */ + /** Create a new test instance for the given visitor. */ public SergeantVisitorTest() { - super( - new SergeantVisitor(), - Optional.empty(), - Optional.of("Hello sergeant"), - Optional.empty() - ); + super(new SergeantVisitor(), null, ("Hello sergeant"), null); } - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java index 5868efdc36ee..71226a394a73 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java @@ -27,16 +27,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -/** - * Date: 12/30/15 - 19:45 PM. - * - * @author Jeroen Meulemeester - */ +/** SoldierTest */ class SoldierTest extends UnitTest { - /** - * Create a new test instance for the given {@link Soldier}. - */ + /** Create a new test instance for the given {@link Soldier}. */ public SoldierTest() { super(Soldier::new); } @@ -45,5 +39,4 @@ public SoldierTest() { void verifyVisit(Soldier unit, UnitVisitor mockedVisitor) { verify(mockedVisitor).visit(eq(unit)); } - -} \ No newline at end of file +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java index 108a6dc4a63b..6844257dcda9 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java @@ -24,25 +24,11 @@ */ package com.iluwatar.visitor; -import java.util.Optional; - -/** - * Date: 12/30/15 - 18:59 PM. - * - * @author Jeroen Meulemeester - */ +/** SoldierVisitorTest */ class SoldierVisitorTest extends VisitorTest { - /** - * Create a new test instance for the given visitor. - */ + /** Create a new test instance for the given visitor. */ public SoldierVisitorTest() { - super( - new SoldierVisitor(), - Optional.empty(), - Optional.empty(), - Optional.of("Greetings soldier") - ); + super(new SoldierVisitor(), null, null, ("Greetings soldier")); } - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java index e8c84ce523ea..57bfa4350131 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java @@ -34,16 +34,13 @@ import org.junit.jupiter.api.Test; /** - * Date: 12/30/15 - 18:59 PM. Test related to Units + * Test related to Units * * @param Type of Unit - * @author Jeroen Meulemeester */ public abstract class UnitTest { - /** - * Factory to create new instances of the tested unit. - */ + /** Factory to create new instances of the tested unit. */ private final Function factory; /** @@ -74,9 +71,8 @@ void testAccept() { /** * Verify if the correct visit method is called on the mock, depending on the tested instance. * - * @param unit The tested unit instance + * @param unit The tested unit instance * @param mockedVisitor The mocked {@link UnitVisitor} who should have gotten a visit by the unit */ abstract void verifyVisit(final U unit, final UnitVisitor mockedVisitor); - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java index cfec06a96ffe..374f1fef6246 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java @@ -31,17 +31,15 @@ import ch.qos.logback.core.AppenderBase; import java.util.LinkedList; import java.util.List; -import java.util.Optional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; /** - * Date: 12/30/15 - 18:59 PM. Test case for Visitor Pattern + * Test case for Visitor Pattern * * @param Type of UnitVisitor - * @author Jeroen Meulemeester */ public abstract class VisitorTest { @@ -57,39 +55,30 @@ void tearDown() { appender.stop(); } - /** - * The tested visitor instance. - */ + /** The tested visitor instance. */ private final V visitor; - /** - * The optional expected response when being visited by a commander. - */ - private final Optional commanderResponse; + /** The expected response when being visited by a commander. */ + private final String commanderResponse; - /** - * The optional expected response when being visited by a sergeant. - */ - private final Optional sergeantResponse; + /** The expected response when being visited by a sergeant. */ + private final String sergeantResponse; - /** - * The optional expected response when being visited by a soldier. - */ - private final Optional soldierResponse; + /** The expected response when being visited by a soldier. */ + private final String soldierResponse; /** * Create a new test instance for the given visitor. * - * @param commanderResponse The optional expected response when being visited by a commander - * @param sergeantResponse The optional expected response when being visited by a sergeant - * @param soldierResponse The optional expected response when being visited by a soldier + * @param commanderResponse The expected response when being visited by a commander + * @param sergeantResponse The expected response when being visited by a sergeant + * @param soldierResponse The expected response when being visited by a soldier */ public VisitorTest( final V visitor, - final Optional commanderResponse, - final Optional sergeantResponse, - final Optional soldierResponse - ) { + final String commanderResponse, + final String sergeantResponse, + final String soldierResponse) { this.visitor = visitor; this.commanderResponse = commanderResponse; this.sergeantResponse = sergeantResponse; @@ -99,8 +88,8 @@ public VisitorTest( @Test void testVisitCommander() { this.visitor.visit(new Commander()); - if (this.commanderResponse.isPresent()) { - assertEquals(this.commanderResponse.get(), appender.getLastMessage()); + if (this.commanderResponse != null) { + assertEquals(this.commanderResponse, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } } @@ -108,8 +97,8 @@ void testVisitCommander() { @Test void testVisitSergeant() { this.visitor.visit(new Sergeant()); - if (this.sergeantResponse.isPresent()) { - assertEquals(this.sergeantResponse.get(), appender.getLastMessage()); + if (this.sergeantResponse != null) { + assertEquals(this.sergeantResponse, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } } @@ -117,13 +106,13 @@ void testVisitSergeant() { @Test void testVisitSoldier() { this.visitor.visit(new Soldier()); - if (this.soldierResponse.isPresent()) { - assertEquals(this.soldierResponse.get(), appender.getLastMessage()); + if (this.soldierResponse != null) { + assertEquals(this.soldierResponse, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } } - private class InMemoryAppender extends AppenderBase { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender() {