diff --git a/patterns/.DS_Store b/patterns/.DS_Store new file mode 100644 index 000000000..31078eb5b Binary files /dev/null and b/patterns/.DS_Store differ diff --git a/patterns/creational/.DS_Store b/patterns/creational/.DS_Store new file mode 100644 index 000000000..036d56d71 Binary files /dev/null and b/patterns/creational/.DS_Store differ diff --git a/patterns/creational/abstract_factory.py b/patterns/creational/abstract_factory.py index 0ec49bbfe..bcb683af1 100644 --- a/patterns/creational/abstract_factory.py +++ b/patterns/creational/abstract_factory.py @@ -90,7 +90,7 @@ def main() -> None: if __name__ == "__main__": - shop = PetShop(random_animal) + shop = PetShop(random.choice([Dog,Cat])) import doctest doctest.testmod() diff --git a/patterns/creational/borg.py b/patterns/creational/borg.py index edd0589d9..cf481d860 100644 --- a/patterns/creational/borg.py +++ b/patterns/creational/borg.py @@ -107,5 +107,8 @@ def main(): if __name__ == "__main__": import doctest - + + rm0 = Borg() + rm1 = YourBorg() + doctest.testmod() diff --git a/patterns/creational/builder.py b/patterns/creational/builder.py index 223839235..d8014434f 100644 --- a/patterns/creational/builder.py +++ b/patterns/creational/builder.py @@ -109,6 +109,20 @@ def main(): if __name__ == "__main__": + + # import os + # import pytest + # if os.getenv('_PYTEST_RAISE', "0") != "0": + + # @pytest.hookimpl(tryfirst=True) + # def pytest_exception_interact(call): + # raise call.excinfo.value + + # @pytest.hookimpl(tryfirst=True) + # def pytest_internalerror(excinfo): + # raise excinfo.value + + import doctest doctest.testmod() diff --git a/patterns/refactoring.guru.zip b/patterns/refactoring.guru.zip new file mode 100644 index 000000000..a01c2a8d5 Binary files /dev/null and b/patterns/refactoring.guru.zip differ diff --git a/patterns/refactoring.guru/.travis.yml b/patterns/refactoring.guru/.travis.yml new file mode 100755 index 000000000..14c8f6ad7 --- /dev/null +++ b/patterns/refactoring.guru/.travis.yml @@ -0,0 +1,12 @@ +language: python +sudo: false +dist: xenial +cache: pip +python: + - '3.7' + # for the future... + # - '3.7-dev' + # - '3.8-dev' +install: pip install pycodestyle +script: + - make codestyle-check diff --git a/patterns/refactoring.guru/LICENSE.txt b/patterns/refactoring.guru/LICENSE.txt new file mode 100755 index 000000000..4bdd165d4 --- /dev/null +++ b/patterns/refactoring.guru/LICENSE.txt @@ -0,0 +1,414 @@ +Refactoring.Guru: Design Patterns in Python +© Alexander Shvets + +This work is licensed under a +Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + +You should have received a copy of the license along with this +work. If not, see . + +======================================================================= + +Attribution-NonCommercial-NoDerivatives 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 +International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-NoDerivatives 4.0 International Public +License ("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + c. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + d. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + e. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + f. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + g. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + h. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce and reproduce, but not Share, Adapted Material + for NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material, You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + For the avoidance of doubt, You do not have permission under + this Public License to Share Adapted Material. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only and provided You do not Share Adapted Material; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/patterns/refactoring.guru/Makefile b/patterns/refactoring.guru/Makefile new file mode 100755 index 000000000..c5cf1eb98 --- /dev/null +++ b/patterns/refactoring.guru/Makefile @@ -0,0 +1,8 @@ +codestyle-check: + find . -name \*.py -exec pycodestyle --config=setup.cfg {} + + +codestyle-fix: + autopep8 -r . --global-config setup.cfg + +init: + sudo pip install -r requirements.txt \ No newline at end of file diff --git a/patterns/refactoring.guru/README.md b/patterns/refactoring.guru/README.md new file mode 100755 index 000000000..df5665600 --- /dev/null +++ b/patterns/refactoring.guru/README.md @@ -0,0 +1,77 @@ +# Design Patterns in Python + +This repository is part of the [Refactoring.Guru](https://refactoring.guru/design-patterns) project. + +It contains Python examples for all classic GoF design patterns. + +Each pattern includes two examples: + +- [x] **Conceptual** examples show the internal structure of patterns, including detailed comments. + +- [ ] **RealWorld** examples show how patterns can be used in real-world Python applications. + + +## Requirements + +These examples require Python 3.7 and newer. + +All examples can be launched via the command line, using the Python executable as follows: + +```sh +python src/Path-to-example/main.py +``` + +For the best experience, I recommend working with examples with these IDEs: + +- [PyCharm](https://www.jetbrains.com/pycharm/) +- [Visual Studio Code](https://code.visualstudio.com/) with the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) + +## FAQ + +#### 1. What is the _Client Code_? + +_Client_ means _client of classes, defined as part of a pattern_, which is merely a caller of the given methods or a user of the given classes. In other words, it's the part of your application's code that uses the pattern's classes. + +#### 2. I don't understand the roles you're referring to in RealWorld examples. + +Take a look at the conceptual example first. There you'll find detailed descriptions of each class in a pattern, its role, and connection to other classes. + + +## Contributor's Guide + +I appreciate any help, whether it's a simple fix of a typo or a whole new example. Just [make a fork](https://help.github.com/articles/fork-a-repo/), make your change and submit a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). + +Here's a style guide which might help you to keep your changes consistent with the rest of the project's code: + +1. All code should match the [PEP 8 coding style guide](https://www.python.org/dev/peps/pep-0008/) + +2. Try to hard-wrap the code at 80th's character. It helps to list the code on the website without scrollbars. + +3. Aim to put all code within one file. Yes, I realize that it's not how it supposed to be done in production. However, it helps people to understand examples better, since all code fits into one screen. + +4. Comments may or may not have language tags in them, such as this: + + ```python + """ + EN: All products families have the same varieties (MacOS/Windows). + + This is a MacOS variant of a button. + + RU: Все семейства продуктов имеют одни и те же вариации (MacOS/Windows). + + Это вариант кнопки под MacOS. + """ + ``` + + This notation helps to keep the code in one place while allowing the website to generates separate versions of examples for all listed languages. Don't be scared and ignore the non-English part of such comments. If you want to change something in a comment like this, just do it. Even if you do it wrong, we'll tell you how to fix it during the Pull Request. + + +## License + +This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + +Creative Commons License + +## Credits + +Authors: Alexey Pyltsyn ([@lex111](https://github.com/lex111)) and Alexander Shvets ([@neochief](https://github.com/neochief)) diff --git a/patterns/refactoring.guru/requirements.txt b/patterns/refactoring.guru/requirements.txt new file mode 100755 index 000000000..1665608a1 --- /dev/null +++ b/patterns/refactoring.guru/requirements.txt @@ -0,0 +1,2 @@ +pycodestyle +pep8 \ No newline at end of file diff --git a/patterns/refactoring.guru/setup.cfg b/patterns/refactoring.guru/setup.cfg new file mode 100755 index 000000000..6df8f27fc --- /dev/null +++ b/patterns/refactoring.guru/setup.cfg @@ -0,0 +1,7 @@ +[pycodestyle] +count = True +ignore = W293,W605 +max-line-length = 200 + +[pep8] +in-place = true \ No newline at end of file diff --git a/patterns/refactoring.guru/src/AbstractFactory/Conceptual/Output.txt b/patterns/refactoring.guru/src/AbstractFactory/Conceptual/Output.txt new file mode 100755 index 000000000..a21fa5fe5 --- /dev/null +++ b/patterns/refactoring.guru/src/AbstractFactory/Conceptual/Output.txt @@ -0,0 +1,7 @@ +Client: Testing client code with the first factory type: +The result of the product B1. +The result of the B1 collaborating with the (The result of the product A1.) + +Client: Testing the same client code with the second factory type: +The result of the product B2. +The result of the B2 collaborating with the (The result of the product A2.) \ No newline at end of file diff --git a/patterns/refactoring.guru/src/AbstractFactory/Conceptual/main.py b/patterns/refactoring.guru/src/AbstractFactory/Conceptual/main.py new file mode 100755 index 000000000..e25a02c49 --- /dev/null +++ b/patterns/refactoring.guru/src/AbstractFactory/Conceptual/main.py @@ -0,0 +1,216 @@ +""" +EN: Abstract Factory Design Pattern + +Intent: Lets you produce families of related objects without specifying their +concrete classes. + +RU: Паттерн Абстрактная Фабрика + +Назначение: Предоставляет интерфейс для создания семейств связанных или +зависимых объектов без привязки к их конкретным классам. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod + + +class AbstractFactory(ABC): + """ + EN: The Abstract Factory interface declares a set of methods that return + different abstract products. These products are called a family and are + related by a high-level theme or concept. Products of one family are usually + able to collaborate among themselves. A family of products may have several + variants, but the products of one variant are incompatible with products of + another. + + RU: Интерфейс Абстрактной Фабрики объявляет набор методов, которые + возвращают различные абстрактные продукты. Эти продукты называются + семейством и связаны темой или концепцией высокого уровня. Продукты одного + семейства обычно могут взаимодействовать между собой. Семейство продуктов + может иметь несколько вариаций, но продукты одной вариации несовместимы с + продуктами другой. + """ + @abstractmethod + def create_product_a(self) -> AbstractProductA: + pass + + @abstractmethod + def create_product_b(self) -> AbstractProductB: + pass + + +class ConcreteFactory1(AbstractFactory): + """ + EN: Concrete Factories produce a family of products that belong to a single + variant. The factory guarantees that resulting products are compatible. Note + that signatures of the Concrete Factory's methods return an abstract + product, while inside the method a concrete product is instantiated. + + RU: Конкретная Фабрика производит семейство продуктов одной вариации. + Фабрика гарантирует совместимость полученных продуктов. Обратите внимание, + что сигнатуры методов Конкретной Фабрики возвращают абстрактный продукт, в + то время как внутри метода создается экземпляр конкретного продукта. + """ + + def create_product_a(self) -> AbstractProductA: + return ConcreteProductA1() + + def create_product_b(self) -> AbstractProductB: + return ConcreteProductB1() + + +class ConcreteFactory2(AbstractFactory): + """ + EN: Each Concrete Factory has a corresponding product variant. + + RU: Каждая Конкретная Фабрика имеет соответствующую вариацию продукта. + """ + + def create_product_a(self) -> AbstractProductA: + return ConcreteProductA2() + + def create_product_b(self) -> AbstractProductB: + return ConcreteProductB2() + + +class AbstractProductA(ABC): + """ + EN: Each distinct product of a product family should have a base interface. + All variants of the product must implement this interface. + + RU: Каждый отдельный продукт семейства продуктов должен иметь базовый + интерфейс. Все вариации продукта должны реализовывать этот интерфейс. + """ + + @abstractmethod + def useful_function_a(self) -> str: + pass + + +""" +EN: Concrete Products are created by corresponding Concrete Factories. + +RU: Конкретные продукты создаются соответствующими Конкретными Фабриками. +""" + + +class ConcreteProductA1(AbstractProductA): + def useful_function_a(self) -> str: + return "The result of the product A1." + + +class ConcreteProductA2(AbstractProductA): + def useful_function_a(self) -> str: + return "The result of the product A2." + + +class AbstractProductB(ABC): + """ + EN: Here's the the base interface of another product. All products can + interact with each other, but proper interaction is possible only between + products of the same concrete variant. + + RU: Базовый интерфейс другого продукта. Все продукты могут взаимодействовать + друг с другом, но правильное взаимодействие возможно только между продуктами + одной и той же конкретной вариации. + """ + @abstractmethod + def useful_function_b(self) -> None: + """ + EN: Product B is able to do its own thing... + + RU: Продукт B способен работать самостоятельно... + """ + pass + + @abstractmethod + def another_useful_function_b(self, collaborator: AbstractProductA) -> None: + """ + EN: ...but it also can collaborate with the ProductA. + + The Abstract Factory makes sure that all products it creates are of the + same variant and thus, compatible. + + RU: ...а также взаимодействовать с Продуктами A той же вариации. + + Абстрактная Фабрика гарантирует, что все продукты, которые она создает, + имеют одинаковую вариацию и, следовательно, совместимы. + """ + pass + + +""" +EN: Concrete Products are created by corresponding Concrete Factories. + +RU: Конкретные Продукты создаются соответствующими Конкретными Фабриками. +""" + + +class ConcreteProductB1(AbstractProductB): + def useful_function_b(self) -> str: + return "The result of the product B1." + + """ + EN: The variant, Product B1, is only able to work correctly with the + variant, Product A1. Nevertheless, it accepts any instance of + AbstractProductA as an argument. + + RU: Продукт B1 может корректно работать только с Продуктом A1. Тем не менее, + он принимает любой экземпляр Абстрактного Продукта А в качестве аргумента. + """ + + def another_useful_function_b(self, collaborator: AbstractProductA) -> str: + result = collaborator.useful_function_a() + return f"The result of the B1 collaborating with the ({result})" + + +class ConcreteProductB2(AbstractProductB): + def useful_function_b(self) -> str: + return "The result of the product B2." + + def another_useful_function_b(self, collaborator: AbstractProductA): + """ + EN: The variant, Product B2, is only able to work correctly with the + variant, Product A2. Nevertheless, it accepts any instance of + AbstractProductA as an argument. + + RU: Продукт B2 может корректно работать только с Продуктом A2. Тем не + менее, он принимает любой экземпляр Абстрактного Продукта А в качестве + аргумента. + """ + result = collaborator.useful_function_a() + return f"The result of the B2 collaborating with the ({result})" + + +def client_code(factory: AbstractFactory) -> None: + """ + EN: The client code works with factories and products only through abstract + types: AbstractFactory and AbstractProduct. This lets you pass any factory + or product subclass to the client code without breaking it. + + RU: Клиентский код работает с фабриками и продуктами только через + абстрактные типы: Абстрактная Фабрика и Абстрактный Продукт. Это позволяет + передавать любой подкласс фабрики или продукта клиентскому коду, не нарушая + его. + """ + product_a = factory.create_product_a() + product_b = factory.create_product_b() + + print(f"{product_b.useful_function_b()}") + print(f"{product_b.another_useful_function_b(product_a)}", end="") + + +if __name__ == "__main__": + """ + EN: The client code can work with any concrete factory class. + + RU: Клиентский код может работать с любым конкретным классом фабрики. + """ + print("Client: Testing client code with the first factory type:") + client_code(ConcreteFactory1()) + + print("\n") + + print("Client: Testing the same client code with the second factory type:") + client_code(ConcreteFactory2()) diff --git a/patterns/refactoring.guru/src/Adapter/Conceptual/class/Output.txt b/patterns/refactoring.guru/src/Adapter/Conceptual/class/Output.txt new file mode 100755 index 000000000..3f345f78e --- /dev/null +++ b/patterns/refactoring.guru/src/Adapter/Conceptual/class/Output.txt @@ -0,0 +1,8 @@ +Client: I can work just fine with the Target objects: +Target: The default target's behavior. + +Client: The Adaptee class has a weird interface. See, I don't understand it: +Adaptee: .eetpadA eht fo roivaheb laicepS + +Client: But I can work with it via the Adapter: +Adapter: (TRANSLATED) Special behavior of the Adaptee. \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Adapter/Conceptual/class/main.py b/patterns/refactoring.guru/src/Adapter/Conceptual/class/main.py new file mode 100755 index 000000000..7907ae53a --- /dev/null +++ b/patterns/refactoring.guru/src/Adapter/Conceptual/class/main.py @@ -0,0 +1,78 @@ +""" +EN: Adapter Design Pattern + +Intent: Provides a unified interface that allows objects with incompatible +interfaces to collaborate. + +RU: Паттерн Адаптер + +Назначение: Позволяет объектам с несовместимыми интерфейсами работать вместе. +""" + + +class Target: + """ + EN: The Target defines the domain-specific interface used by the client + code. + + RU: Целевой класс объявляет интерфейс, с которым может работать клиентский + код. + """ + + def request(self) -> str: + return "Target: The default target's behavior." + + +class Adaptee: + """ + EN: The Adaptee contains some useful behavior, but its interface is + incompatible with the existing client code. The Adaptee needs some + adaptation before the client code can use it. + + RU: Адаптируемый класс содержит некоторое полезное поведение, но его + интерфейс несовместим с существующим клиентским кодом. Адаптируемый класс + нуждается в некоторой доработке, прежде чем клиентский код сможет его + использовать. + """ + + def specific_request(self) -> str: + return ".eetpadA eht fo roivaheb laicepS" + + +class Adapter(Target, Adaptee): + """ + EN: The Adapter makes the Adaptee's interface compatible with the Target's + interface via multiple inheritance. + + RU: Адаптер делает интерфейс Адаптируемого класса совместимым с целевым + интерфейсом благодаря множественному наследованию. + """ + + def request(self) -> str: + return f"Adapter: (TRANSLATED) {self.specific_request()[::-1]}" + + +def client_code(target: "Target") -> None: + """ + EN: The client code supports all classes that follow the Target interface. + + RU: Клиентский код поддерживает все классы, использующие интерфейс Target. + """ + + print(target.request(), end="") + + +if __name__ == "__main__": + print("Client: I can work just fine with the Target objects:") + target = Target() + client_code(target) + print("\n") + + adaptee = Adaptee() + print("Client: The Adaptee class has a weird interface. " + "See, I don't understand it:") + print(f"Adaptee: {adaptee.specific_request()}", end="\n\n") + + print("Client: But I can work with it via the Adapter:") + adapter = Adapter() + client_code(adapter) diff --git a/patterns/refactoring.guru/src/Adapter/Conceptual/object/Output.txt b/patterns/refactoring.guru/src/Adapter/Conceptual/object/Output.txt new file mode 100755 index 000000000..3f345f78e --- /dev/null +++ b/patterns/refactoring.guru/src/Adapter/Conceptual/object/Output.txt @@ -0,0 +1,8 @@ +Client: I can work just fine with the Target objects: +Target: The default target's behavior. + +Client: The Adaptee class has a weird interface. See, I don't understand it: +Adaptee: .eetpadA eht fo roivaheb laicepS + +Client: But I can work with it via the Adapter: +Adapter: (TRANSLATED) Special behavior of the Adaptee. \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Adapter/Conceptual/object/main.py b/patterns/refactoring.guru/src/Adapter/Conceptual/object/main.py new file mode 100755 index 000000000..4ce5175d6 --- /dev/null +++ b/patterns/refactoring.guru/src/Adapter/Conceptual/object/main.py @@ -0,0 +1,81 @@ +""" +EN: Adapter Design Pattern + +Intent: Provides a unified interface that allows objects with incompatible +interfaces to collaborate. + +RU: Паттерн Адаптер + +Назначение: Позволяет объектам с несовместимыми интерфейсами работать вместе. +""" + + +class Target: + """ + EN: The Target defines the domain-specific interface used by the client + code. + + RU: Целевой класс объявляет интерфейс, с которым может работать клиентский + код. + """ + + def request(self) -> str: + return "Target: The default target's behavior." + + +class Adaptee: + """ + EN: The Adaptee contains some useful behavior, but its interface is + incompatible with the existing client code. The Adaptee needs some + adaptation before the client code can use it. + + RU: Адаптируемый класс содержит некоторое полезное поведение, но его + интерфейс несовместим с существующим клиентским кодом. Адаптируемый класс + нуждается в некоторой доработке, прежде чем клиентский код сможет его + использовать. + """ + + def specific_request(self) -> str: + return ".eetpadA eht fo roivaheb laicepS" + + +class Adapter(Target): + """ + EN: The Adapter makes the Adaptee's interface compatible with the Target's + interface via composition. + + RU: Адаптер делает интерфейс Адаптируемого класса совместимым с целевым + интерфейсом благодаря агрегации. + """ + + def __init__(self, adaptee: Adaptee) -> None: + self.adaptee = adaptee + + def request(self) -> str: + return f"Adapter: (TRANSLATED) {self.adaptee.specific_request()[::-1]}" + + +def client_code(target: Target) -> None: + """ + EN: The client code supports all classes that follow the Target interface. + + RU: Клиентский код поддерживает все классы, использующие интерфейс Target. + """ + + print(target.request(), end="") + + +if __name__ == "__main__": + print("Client: I can work just fine with the Target objects:") + target = Target() + client_code(target) + print("\n") + + adaptee = Adaptee() + print("Client: The Adaptee class has a weird interface. " + "See, I don't understand it:") + print(f"Adaptee: {adaptee.specific_request()}", end="\n\n") + + print("Client: But I can work with it via the Adapter:") + adapter = Adapter(adaptee) + client_code(adapter) diff --git a/patterns/refactoring.guru/src/Bridge/Conceptual/Output.txt b/patterns/refactoring.guru/src/Bridge/Conceptual/Output.txt new file mode 100755 index 000000000..1ebad72b6 --- /dev/null +++ b/patterns/refactoring.guru/src/Bridge/Conceptual/Output.txt @@ -0,0 +1,5 @@ +Abstraction: Base operation with: +ConcreteImplementationA: Here's the result on the platform A. + +ExtendedAbstraction: Extended operation with: +ConcreteImplementationB: Here's the result on the platform B. \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Bridge/Conceptual/main.py b/patterns/refactoring.guru/src/Bridge/Conceptual/main.py new file mode 100755 index 000000000..be2bf8685 --- /dev/null +++ b/patterns/refactoring.guru/src/Bridge/Conceptual/main.py @@ -0,0 +1,139 @@ +""" +EN: Bridge Design Pattern + +Intent: Lets you split a large class or a set of closely related classes into +two separate hierarchies—abstraction and implementation—which can be developed +independently of each other. + + A + / \ A N + Aa Ab ===> / \ / \ + / \ / \ Aa(N) Ab(N) 1 2 + Aa1 Aa2 Ab1 Ab2 + +RU: Паттерн Мост + +Назначение: Разделяет один или несколько классов на две отдельные иерархии — +абстракцию и реализацию, позволяя изменять их независимо друг от друга. + + A + / \ A N + Aa Ab ===> / \ / \ + / \ / \ Aa(N) Ab(N) 1 2 + Aa1 Aa2 Ab1 Ab2 +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod + + +class Abstraction: + """ + EN: The Abstraction defines the interface for the "control" part of the two + class hierarchies. It maintains a reference to an object of the + Implementation hierarchy and delegates all of the real work to this object. + + RU: Абстракция устанавливает интерфейс для «управляющей» части двух иерархий + классов. Она содержит ссылку на объект из иерархии Реализации и делегирует + ему всю настоящую работу. + """ + + def __init__(self, implementation: Implementation) -> None: + self.implementation = implementation + + def operation(self) -> str: + return (f"Abstraction: Base operation with:\n" + f"{self.implementation.operation_implementation()}") + + +class ExtendedAbstraction(Abstraction): + """ + EN: You can extend the Abstraction without changing the Implementation + classes. + + RU: Можно расширить Абстракцию без изменения классов Реализации. + """ + + def operation(self) -> str: + return (f"ExtendedAbstraction: Extended operation with:\n" + f"{self.implementation.operation_implementation()}") + + +class Implementation(ABC): + """ + EN: The Implementation defines the interface for all implementation classes. + It doesn't have to match the Abstraction's interface. In fact, the two + interfaces can be entirely different. Typically the Implementation interface + provides only primitive operations, while the Abstraction defines higher- + level operations based on those primitives. + + RU: Реализация устанавливает интерфейс для всех классов реализации. Он не + должен соответствовать интерфейсу Абстракции. На практике оба интерфейса + могут быть совершенно разными. Как правило, интерфейс Реализации + предоставляет только примитивные операции, в то время как Абстракция + определяет операции более высокого уровня, основанные на этих примитивах. + """ + + @abstractmethod + def operation_implementation(self) -> str: + pass + + +""" +EN: Each Concrete Implementation corresponds to a specific platform and +implements the Implementation interface using that platform's API. + +RU: Каждая Конкретная Реализация соответствует определённой платформе и +реализует интерфейс Реализации с использованием API этой платформы. +""" + + +class ConcreteImplementationA(Implementation): + def operation_implementation(self) -> str: + return "ConcreteImplementationA: Here's the result on the platform A." + + +class ConcreteImplementationB(Implementation): + def operation_implementation(self) -> str: + return "ConcreteImplementationB: Here's the result on the platform B." + + +def client_code(abstraction: Abstraction) -> None: + """ + EN: Except for the initialization phase, where an Abstraction object gets + linked with a specific Implementation object, the client code should only + depend on the Abstraction class. This way the client code can support any + abstraction-implementation combination. + + RU: За исключением этапа инициализации, когда объект Абстракции связывается + с определённым объектом Реализации, клиентский код должен зависеть только от + класса Абстракции. Таким образом, клиентский код может поддерживать любую + комбинацию абстракции и реализации. + """ + + # ... + + print(abstraction.operation(), end="") + + # ... + + +if __name__ == "__main__": + """ + EN: The client code should be able to work with any pre-configured + abstraction-implementation combination. + + RU: Клиентский код должен работать с любой предварительно сконфигурированной + комбинацией абстракции и реализации. + """ + + implementation = ConcreteImplementationA() + abstraction = Abstraction(implementation) + client_code(abstraction) + + print("\n") + + implementation = ConcreteImplementationB() + abstraction = ExtendedAbstraction(implementation) + client_code(abstraction) diff --git a/patterns/refactoring.guru/src/Builder/Conceptual/Output.txt b/patterns/refactoring.guru/src/Builder/Conceptual/Output.txt new file mode 100755 index 000000000..6a564fe42 --- /dev/null +++ b/patterns/refactoring.guru/src/Builder/Conceptual/Output.txt @@ -0,0 +1,8 @@ +Standard basic product: +Product parts: PartA1 + +Standard full featured product: +Product parts: PartA1, PartB1, PartC1 + +Custom product: +Product parts: PartA1, PartB1 \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Builder/Conceptual/classes_builder.png b/patterns/refactoring.guru/src/Builder/Conceptual/classes_builder.png new file mode 100644 index 000000000..db8af1bce Binary files /dev/null and b/patterns/refactoring.guru/src/Builder/Conceptual/classes_builder.png differ diff --git a/patterns/refactoring.guru/src/Builder/Conceptual/main.py b/patterns/refactoring.guru/src/Builder/Conceptual/main.py new file mode 100755 index 000000000..ad395ffb6 --- /dev/null +++ b/patterns/refactoring.guru/src/Builder/Conceptual/main.py @@ -0,0 +1,226 @@ +""" +EN: Builder Design Pattern + +Intent: Lets you construct complex objects step by step. The pattern allows you +to produce different types and representations of an object using the same +construction code. + +RU: Паттерн Строитель + +Назначение: Позволяет создавать сложные объекты пошагово. Строитель даёт +возможность использовать один и тот же код строительства для получения разных +представлений объектов. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import Any + + +class Builder(ABC): + """ + EN: The Builder interface specifies methods for creating the different parts + of the Product objects. + + RU: Интерфейс Строителя объявляет создающие методы для различных частей + объектов Продуктов. + """ + + @property + @abstractmethod + def product(self) -> None: + pass + + @abstractmethod + def produce_part_a(self) -> None: + pass + + @abstractmethod + def produce_part_b(self) -> None: + pass + + @abstractmethod + def produce_part_c(self) -> None: + pass + + +class ConcreteBuilder1(Builder): + """ + EN: The Concrete Builder classes follow the Builder interface and provide + specific implementations of the building steps. Your program may have + several variations of Builders, implemented differently. + + RU: Классы Конкретного Строителя следуют интерфейсу Строителя и + предоставляют конкретные реализации шагов построения. Ваша программа может + иметь несколько вариантов Строителей, реализованных по-разному. + """ + + def __init__(self) -> None: + """ + EN: A fresh builder instance should contain a blank product object, + which is used in further assembly. + + RU: Новый экземпляр строителя должен содержать пустой объект продукта, + который используется в дальнейшей сборке. + """ + self.reset() + + def reset(self) -> None: + self._product = Product1() + + @property + def product(self) -> Product1: + """ + EN: Concrete Builders are supposed to provide their own methods for + retrieving results. That's because various types of builders may create + entirely different products that don't follow the same interface. + Therefore, such methods cannot be declared in the base Builder interface + (at least in a statically typed programming language). + + Usually, after returning the end result to the client, a builder + instance is expected to be ready to start producing another product. + That's why it's a usual practice to call the reset method at the end of + the `getProduct` method body. However, this behavior is not mandatory, + and you can make your builders wait for an explicit reset call from the + client code before disposing of the previous result. + + RU: Конкретные Строители должны предоставить свои собственные методы + получения результатов. Это связано с тем, что различные типы строителей + могут создавать совершенно разные продукты с разными интерфейсами. + Поэтому такие методы не могут быть объявлены в базовом интерфейсе + Строителя (по крайней мере, в статически типизированном языке + программирования). + + Как правило, после возвращения конечного результата клиенту, экземпляр + строителя должен быть готов к началу производства следующего продукта. + Поэтому обычной практикой является вызов метода сброса в конце тела + метода getProduct. Однако такое поведение не является обязательным, вы + можете заставить своих строителей ждать явного запроса на сброс из кода + клиента, прежде чем избавиться от предыдущего результата. + """ + product = self._product + self.reset() + return product + + def produce_part_a(self) -> None: + self._product.add("PartA1") + + def produce_part_b(self) -> None: + self._product.add("PartB1") + + def produce_part_c(self) -> None: + self._product.add("PartC1") + + +class Product1(): + """ + EN: It makes sense to use the Builder pattern only when your products are + quite complex and require extensive configuration. + + Unlike in other creational patterns, different concrete builders can produce + unrelated products. In other words, results of various builders may not + always follow the same interface. + + RU: Имеет смысл использовать паттерн Строитель только тогда, когда ваши + продукты достаточно сложны и требуют обширной конфигурации. + + В отличие от других порождающих паттернов, различные конкретные строители + могут производить несвязанные продукты. Другими словами, результаты + различных строителей могут не всегда следовать одному и тому же интерфейсу. + """ + + def __init__(self) -> None: + self.parts = [] + + def add(self, part: Any) -> None: + self.parts.append(part) + + def list_parts(self) -> None: + print(f"Product parts: {', '.join(self.parts)}", end="") + + +class Director: + """ + EN: The Director is only responsible for executing the building steps in a + particular sequence. It is helpful when producing products according to a + specific order or configuration. Strictly speaking, the Director class is + optional, since the client can control builders directly. + + RU: Директор отвечает только за выполнение шагов построения в определённой + последовательности. Это полезно при производстве продуктов в определённом + порядке или особой конфигурации. Строго говоря, класс Директор необязателен, + так как клиент может напрямую управлять строителями. + """ + + def __init__(self) -> None: + self._builder = None + + @property + def builder(self) -> Builder: + return self._builder + + @builder.setter + def builder(self, builder: Builder) -> None: + """ + EN: The Director works with any builder instance that the client code + passes to it. This way, the client code may alter the final type of the + newly assembled product. + + RU: Директор работает с любым экземпляром строителя, который передаётся + ему клиентским кодом. Таким образом, клиентский код может изменить + конечный тип вновь собираемого продукта. + """ + self._builder = builder + + """ + EN: The Director can construct several product variations using the same + building steps. + + RU: Директор может строить несколько вариаций продукта, используя одинаковые + шаги построения. + """ + + def build_minimal_viable_product(self) -> None: + self.builder.produce_part_a() + + def build_full_featured_product(self) -> None: + self.builder.produce_part_a() + self.builder.produce_part_b() + self.builder.produce_part_c() + + +if __name__ == "__main__": + """ + EN: The client code creates a builder object, passes it to the director and + then initiates the construction process. The end result is retrieved from + the builder object. + + RU: Клиентский код создаёт объект-строитель, передаёт его директору, а затем + инициирует процесс построения. Конечный результат извлекается из + объекта-строителя. + """ + + director = Director() + builder = ConcreteBuilder1() + director.builder = builder + + print("Standard basic product: ") + director.build_minimal_viable_product() + builder.product.list_parts() + + print("\n") + + print("Standard full featured product: ") + director.build_full_featured_product() + builder.product.list_parts() + + print("\n") + + # EN: Remember, the Builder pattern can be used without a Director class. + # + # RU: Помните, что паттерн Строитель можно использовать без класса Директор. + print("Custom product: ") + builder.produce_part_a() + builder.produce_part_b() + builder.product.list_parts() diff --git a/patterns/refactoring.guru/src/ChainOfResponsibility/Conceptual/Output.txt b/patterns/refactoring.guru/src/ChainOfResponsibility/Conceptual/Output.txt new file mode 100755 index 000000000..ff7e5d826 --- /dev/null +++ b/patterns/refactoring.guru/src/ChainOfResponsibility/Conceptual/Output.txt @@ -0,0 +1,17 @@ +Chain: Monkey > Squirrel > Dog + +Client: Who wants a Nut? + Squirrel: I'll eat the Nut +Client: Who wants a Banana? + Monkey: I'll eat the Banana +Client: Who wants a Cup of coffee? + Cup of coffee was left untouched. + +Subchain: Squirrel > Dog + +Client: Who wants a Nut? + Squirrel: I'll eat the Nut +Client: Who wants a Banana? + Banana was left untouched. +Client: Who wants a Cup of coffee? + Cup of coffee was left untouched. \ No newline at end of file diff --git a/patterns/refactoring.guru/src/ChainOfResponsibility/Conceptual/main.py b/patterns/refactoring.guru/src/ChainOfResponsibility/Conceptual/main.py new file mode 100755 index 000000000..97cb4d3bd --- /dev/null +++ b/patterns/refactoring.guru/src/ChainOfResponsibility/Conceptual/main.py @@ -0,0 +1,137 @@ +""" +EN: Chain of Responsibility Design Pattern + +Intent: Lets you pass requests along a chain of handlers. Upon receiving a +request, each handler decides either to process the request or to pass it to the +next handler in the chain. + +RU: Паттерн Цепочка обязанностей + +Назначение: Позволяет передавать запросы последовательно по цепочке +обработчиков. Каждый последующий обработчик решает, может ли он обработать +запрос сам и стоит ли передавать запрос дальше по цепи. +""" + +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import Any, Optional + + +class Handler(ABC): + """ + EN: The Handler interface declares a method for building the chain of + handlers. It also declares a method for executing a request. + + RU: Интерфейс Обработчика объявляет метод построения цепочки обработчиков. + Он также объявляет метод для выполнения запроса. + """ + + @abstractmethod + def set_next(self, handler: Handler) -> Handler: + pass + + @abstractmethod + def handle(self, request) -> Optional[str]: + pass + + +class AbstractHandler(Handler): + """ + EN: The default chaining behavior can be implemented inside a base handler + class. + + RU: Поведение цепочки по умолчанию может быть реализовано внутри базового + класса обработчика. + """ + + _next_handler: Handler = None + + def set_next(self, handler: Handler) -> Handler: + self._next_handler = handler + # EN: Returning a handler from here will let us link handlers in a + # convenient way like this: + # monkey.set_next(squirrel).set_next(dog) + # + # RU: Возврат обработчика отсюда позволит связать обработчики простым + # способом, вот так: + # monkey.set_next(squirrel).set_next(dog) + return handler + + @abstractmethod + def handle(self, request: Any) -> str: + if self._next_handler: + return self._next_handler.handle(request) + + return None + + +""" +EN: All Concrete Handlers either handle a request or pass it to the next handler +in the chain. + +RU: Все Конкретные Обработчики либо обрабатывают запрос, либо передают его +следующему обработчику в цепочке. +""" + + +class MonkeyHandler(AbstractHandler): + def handle(self, request: Any) -> str: + if request == "Banana": + return f"Monkey: I'll eat the {request}" + else: + return super().handle(request) + + +class SquirrelHandler(AbstractHandler): + def handle(self, request: Any) -> str: + if request == "Nut": + return f"Squirrel: I'll eat the {request}" + else: + return super().handle(request) + + +class DogHandler(AbstractHandler): + def handle(self, request: Any) -> str: + if request == "MeatBall": + return f"Dog: I'll eat the {request}" + else: + return super().handle(request) + + +def client_code(handler: Handler) -> None: + """ + EN: The client code is usually suited to work with a single handler. In most + cases, it is not even aware that the handler is part of a chain. + + RU: Обычно клиентский код приспособлен для работы с единственным + обработчиком. В большинстве случаев клиенту даже неизвестно, что этот + обработчик является частью цепочки. + """ + + for food in ["Nut", "Banana", "Cup of coffee"]: + print(f"\nClient: Who wants a {food}?") + result = handler.handle(food) + if result: + print(f" {result}", end="") + else: + print(f" {food} was left untouched.", end="") + + +if __name__ == "__main__": + monkey = MonkeyHandler() + squirrel = SquirrelHandler() + dog = DogHandler() + + monkey.set_next(squirrel).set_next(dog) + + # EN: The client should be able to send a request to any handler, not just + # the first one in the chain. + # + # RU: Клиент должен иметь возможность отправлять запрос любому обработчику, + # а не только первому в цепочке. + print("Chain: Monkey > Squirrel > Dog") + client_code(monkey) + print("\n") + + print("Subchain: Squirrel > Dog") + client_code(squirrel) diff --git a/patterns/refactoring.guru/src/Command/Conceptual/Output.txt b/patterns/refactoring.guru/src/Command/Conceptual/Output.txt new file mode 100755 index 000000000..dc0c7284f --- /dev/null +++ b/patterns/refactoring.guru/src/Command/Conceptual/Output.txt @@ -0,0 +1,7 @@ +Invoker: Does anybody want something done before I begin? +SimpleCommand: See, I can do simple things like printing (Say Hi!) +Invoker: ...doing something really important... +Invoker: Does anybody want something done after I finish? +ComplexCommand: Complex stuff should be done by a receiver object +Receiver: Working on (Send email.) +Receiver: Also working on (Save report.) \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Command/Conceptual/main.py b/patterns/refactoring.guru/src/Command/Conceptual/main.py new file mode 100755 index 000000000..3eb53c340 --- /dev/null +++ b/patterns/refactoring.guru/src/Command/Conceptual/main.py @@ -0,0 +1,159 @@ +""" +EN: Command Design Pattern + +Intent: Turns a request into a stand-alone object that contains all information +about the request. This transformation lets you parameterize methods with +different requests, delay or queue a request's execution, and support undoable +operations. + +RU: Паттерн Команда + +Назначение: Превращает запросы в объекты, позволяя передавать их как аргументы +при вызове методов, ставить запросы в очередь, логировать их, а также +поддерживать отмену операций. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod + + +class Command(ABC): + """ + EN: The Command interface declares a method for executing a command. + + RU: Интерфейс Команды объявляет метод для выполнения команд. + """ + + @abstractmethod + def execute(self) -> None: + pass + + +class SimpleCommand(Command): + """ + EN: Some commands can implement simple operations on their own. + + RU: Некоторые команды способны выполнять простые операции самостоятельно. + """ + + def __init__(self, payload: str) -> None: + self._payload = payload + + def execute(self) -> None: + print(f"SimpleCommand: See, I can do simple things like printing" + f"({self._payload})") + + +class ComplexCommand(Command): + """ + EN: However, some commands can delegate more complex operations to other + objects, called "receivers." + + RU: Но есть и команды, которые делегируют более сложные операции другим + объектам, называемым «получателями». + """ + + def __init__(self, receiver: Receiver, a: str, b: str) -> None: + """ + EN: Complex commands can accept one or several receiver objects along + with any context data via the constructor. + + RU: Сложные команды могут принимать один или несколько + объектов-получателей вместе с любыми данными о контексте через + конструктор. + """ + + self._receiver = receiver + self._a = a + self._b = b + + def execute(self) -> None: + """ + EN: Commands can delegate to any methods of a receiver. + + RU: Команды могут делегировать выполнение любым методам получателя. + """ + + print("ComplexCommand: Complex stuff should be done by a receiver object", end="") + self._receiver.do_something(self._a) + self._receiver.do_something_else(self._b) + + +class Receiver: + """ + EN: The Receiver classes contain some important business logic. They know + how to perform all kinds of operations, associated with carrying out a + request. In fact, any class may serve as a Receiver. + + RU: Классы Получателей содержат некую важную бизнес-логику. Они умеют + выполнять все виды операций, связанных с выполнением запроса. Фактически, + любой класс может выступать Получателем. + """ + + def do_something(self, a: str) -> None: + print(f"\nReceiver: Working on ({a}.)", end="") + + def do_something_else(self, b: str) -> None: + print(f"\nReceiver: Also working on ({b}.)", end="") + + +class Invoker: + """ + EN: The Invoker is associated with one or several commands. It sends a + request to the command. + + RU: Отправитель связан с одной или несколькими командами. Он отправляет + запрос команде. + """ + + _on_start = None + _on_finish = None + + """ + EN: Initialize commands. + + RU: Инициализация команд. + """ + + def set_on_start(self, command: Command): + self._on_start = command + + def set_on_finish(self, command: Command): + self._on_finish = command + + def do_something_important(self) -> None: + """ + EN: The Invoker does not depend on concrete command or receiver classes. + The Invoker passes a request to a receiver indirectly, by executing a + command. + + RU: Отправитель не зависит от классов конкретных команд и получателей. + Отправитель передаёт запрос получателю косвенно, выполняя команду. + """ + + print("Invoker: Does anybody want something done before I begin?") + if isinstance(self._on_start, Command): + self._on_start.execute() + + print("Invoker: ...doing something really important...") + + print("Invoker: Does anybody want something done after I finish?") + if isinstance(self._on_finish, Command): + self._on_finish.execute() + + +if __name__ == "__main__": + """ + EN: The client code can parameterize an invoker with any commands. + + RU: Клиентский код может параметризовать отправителя любыми командами. + """ + + invoker = Invoker() + invoker.set_on_start(SimpleCommand("Say Hi!")) + receiver = Receiver() + invoker.set_on_finish(ComplexCommand( + receiver, "Send email", "Save report")) + + invoker.do_something_important() diff --git a/patterns/refactoring.guru/src/Composite/Conceptual/Output.txt b/patterns/refactoring.guru/src/Composite/Conceptual/Output.txt new file mode 100755 index 000000000..d61c1747d --- /dev/null +++ b/patterns/refactoring.guru/src/Composite/Conceptual/Output.txt @@ -0,0 +1,8 @@ +Client: I've got a simple component: +RESULT: Leaf + +Client: Now I've got a composite tree: +RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)) + +Client: I don't need to check the components classes even when managing the tree: +RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf) \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Composite/Conceptual/main.py b/patterns/refactoring.guru/src/Composite/Conceptual/main.py new file mode 100755 index 000000000..333801df6 --- /dev/null +++ b/patterns/refactoring.guru/src/Composite/Conceptual/main.py @@ -0,0 +1,221 @@ +""" +EN: Composite Design Pattern + +Intent: Lets you compose objects into tree structures and then work with these +structures as if they were individual objects. + +RU: Паттерн Компоновщик + +Назначение: Позволяет сгруппировать объекты в древовидную структуру, а затем +работать с ними так, как будто это единичный объект. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import List + + +class Component(ABC): + """ + EN: The base Component class declares common operations for both simple and + complex objects of a composition. + + RU: Базовый класс Компонент объявляет общие операции как для простых, так и + для сложных объектов структуры. + """ + + @property + def parent(self) -> Component: + return self._parent + + @parent.setter + def parent(self, parent: Component): + """ + EN: Optionally, the base Component can declare an interface for setting + and accessing a parent of the component in a tree structure. It can also + provide some default implementation for these methods. + + RU: При необходимости базовый Компонент может объявить интерфейс для + установки и получения родителя компонента в древовидной структуре. Он + также может предоставить некоторую реализацию по умолчанию для этих + методов. + """ + + self._parent = parent + + """ + EN: In some cases, it would be beneficial to define the child-management + operations right in the base Component class. This way, you won't need to + expose any concrete component classes to the client code, even during the + object tree assembly. The downside is that these methods will be empty for + the leaf-level components. + + RU: В некоторых случаях целесообразно определить операции управления + потомками прямо в базовом классе Компонент. Таким образом, вам не нужно + будет предоставлять конкретные классы компонентов клиентскому коду, даже во + время сборки дерева объектов. Недостаток такого подхода в том, что эти + методы будут пустыми для компонентов уровня листа. + """ + + def add(self, component: Component) -> None: + pass + + def remove(self, component: Component) -> None: + pass + + def is_composite(self) -> bool: + """ + EN: You can provide a method that lets the client code figure out + whether a component can bear children. + + RU: Вы можете предоставить метод, который позволит клиентскому коду + понять, может ли компонент иметь вложенные объекты. + """ + + return False + + @abstractmethod + def operation(self) -> str: + """ + EN: The base Component may implement some default behavior or leave it + to concrete classes (by declaring the method containing the behavior as + "abstract"). + + RU: Базовый Компонент может сам реализовать некоторое поведение по + умолчанию или поручить это конкретным классам, объявив метод, содержащий + поведение абстрактным. + """ + + pass + + +class Leaf(Component): + """ + EN: The Leaf class represents the end objects of a composition. A leaf can't + have any children. + + Usually, it's the Leaf objects that do the actual work, whereas Composite + objects only delegate to their sub-components. + + RU: Класс Лист представляет собой конечные объекты структуры. Лист не может + иметь вложенных компонентов. + + Обычно объекты Листьев выполняют фактическую работу, тогда как объекты + Контейнера лишь делегируют работу своим подкомпонентам. + """ + + def operation(self) -> str: + return "Leaf" + + +class Composite(Component): + """ + EN: The Composite class represents the complex components that may have + children. Usually, the Composite objects delegate the actual work to their + children and then "sum-up" the result. + + RU: Класс Контейнер содержит сложные компоненты, которые могут иметь + вложенные компоненты. Обычно объекты Контейнеры делегируют фактическую + работу своим детям, а затем «суммируют» результат. + """ + + def __init__(self) -> None: + self._children: List[Component] = [] + + """ + EN: A composite object can add or remove other components (both simple or + complex) to or from its child list. + + RU: Объект контейнера может как добавлять компоненты в свой список вложенных + компонентов, так и удалять их, как простые, так и сложные. + """ + + def add(self, component: Component) -> None: + self._children.append(component) + component.parent = self + + def remove(self, component: Component) -> None: + self._children.remove(component) + component.parent = None + + def is_composite(self) -> bool: + return True + + def operation(self) -> str: + """ + EN: The Composite executes its primary logic in a particular way. It + traverses recursively through all its children, collecting and summing + their results. Since the composite's children pass these calls to their + children and so forth, the whole object tree is traversed as a result. + + RU: Контейнер выполняет свою основную логику особым образом. Он проходит + рекурсивно через всех своих детей, собирая и суммируя их результаты. + Поскольку потомки контейнера передают эти вызовы своим потомкам и так + далее, в результате обходится всё дерево объектов. + """ + + results = [] + for child in self._children: + results.append(child.operation()) + return f"Branch({'+'.join(results)})" + + +def client_code(component: Component) -> None: + """ + EN: The client code works with all of the components via the base interface. + + RU: Клиентский код работает со всеми компонентами через базовый интерфейс. + """ + + print(f"RESULT: {component.operation()}", end="") + + +def client_code2(component1: Component, component2: Component) -> None: + """ + EN: Thanks to the fact that the child-management operations are declared in + the base Component class, the client code can work with any component, + simple or complex, without depending on their concrete classes. + + RU: Благодаря тому, что операции управления потомками объявлены в базовом + классе Компонента, клиентский код может работать как с простыми, так и со + сложными компонентами, вне зависимости от их конкретных классов. + """ + + if component1.is_composite(): + component1.add(component2) + + print(f"RESULT: {component1.operation()}", end="") + + +if __name__ == "__main__": + # EN: This way the client code can support the simple leaf components... + # + # RU: Таким образом, клиентский код может поддерживать простые + # компоненты-листья... + simple = Leaf() + print("Client: I've got a simple component:") + client_code(simple) + print("\n") + + # EN: ...as well as the complex composites. + # + # RU: ...а также сложные контейнеры. + tree = Composite() + + branch1 = Composite() + branch1.add(Leaf()) + branch1.add(Leaf()) + + branch2 = Composite() + branch2.add(Leaf()) + + tree.add(branch1) + tree.add(branch2) + + print("Client: Now I've got a composite tree:") + client_code(tree) + print("\n") + + print("Client: I don't need to check the components classes even when managing the tree:") + client_code2(tree, simple) diff --git a/patterns/refactoring.guru/src/Decorator/Conceptual/Output.txt b/patterns/refactoring.guru/src/Decorator/Conceptual/Output.txt new file mode 100755 index 000000000..01cbc5d85 --- /dev/null +++ b/patterns/refactoring.guru/src/Decorator/Conceptual/Output.txt @@ -0,0 +1,5 @@ +Client: I've got a simple component: +RESULT: ConcreteComponent + +Client: Now I've got a decorated component: +RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent)) \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Decorator/Conceptual/main.py b/patterns/refactoring.guru/src/Decorator/Conceptual/main.py new file mode 100755 index 000000000..08fb28bf4 --- /dev/null +++ b/patterns/refactoring.guru/src/Decorator/Conceptual/main.py @@ -0,0 +1,149 @@ +""" +EN: Decorator Design Pattern + +Intent: Lets you attach new behaviors to objects by placing these objects inside +special wrapper objects that contain the behaviors. + +RU: Паттерн Декоратор + +Назначение: Позволяет динамически добавлять объектам новую функциональность, +оборачивая их в полезные «обёртки». +""" + + +class Component(): + """ + EN: The base Component interface defines operations that can be altered by + decorators. + + RU: Базовый интерфейс Компонента определяет поведение, которое изменяется + декораторами. + """ + + def operation(self) -> str: + pass + + +class ConcreteComponent(Component): + """ + EN: Concrete Components provide default implementations of the operations. + There might be several variations of these classes. + + RU: Конкретные Компоненты предоставляют реализации поведения по умолчанию. + Может быть несколько вариаций этих классов. + """ + + def operation(self) -> str: + return "ConcreteComponent" + + +class Decorator(Component): + """ + EN: The base Decorator class follows the same interface as the other + components. The primary purpose of this class is to define the wrapping + interface for all concrete decorators. The default implementation of the + wrapping code might include a field for storing a wrapped component and the + means to initialize it. + + RU: Базовый класс Декоратора следует тому же интерфейсу, что и другие + компоненты. Основная цель этого класса - определить интерфейс обёртки для + всех конкретных декораторов. Реализация кода обёртки по умолчанию может + включать в себя поле для хранения завёрнутого компонента и средства его + инициализации. + """ + + _component: Component = None + + def __init__(self, component: Component) -> None: + self._component = component + + @property + def component(self) -> Component: + """ + EN: The Decorator delegates all work to the wrapped component. + + RU: Декоратор делегирует всю работу обёрнутому компоненту. + """ + + return self._component + + def operation(self) -> str: + return self._component.operation() + + +class ConcreteDecoratorA(Decorator): + """ + EN: Concrete Decorators call the wrapped object and alter its result in some + way. + + RU: Конкретные Декораторы вызывают обёрнутый объект и изменяют его результат + некоторым образом. + """ + + def operation(self) -> str: + """ + EN: Decorators may call parent implementation of the operation, instead + of calling the wrapped object directly. This approach simplifies + extension of decorator classes. + + RU: Декораторы могут вызывать родительскую реализацию операции, вместо + того, чтобы вызвать обёрнутый объект напрямую. Такой подход упрощает + расширение классов декораторов. + """ + return f"ConcreteDecoratorA({self.component.operation()})" + + +class ConcreteDecoratorB(Decorator): + """ + EN: Decorators can execute their behavior either before or after the call to + a wrapped object. + + RU: Декораторы могут выполнять своё поведение до или после вызова обёрнутого + объекта. + """ + + def operation(self) -> str: + return f"ConcreteDecoratorB({self.component.operation()})" + + +def client_code(component: Component) -> None: + """ + EN: The client code works with all objects using the Component interface. + This way it can stay independent of the concrete classes of components it + works with. + + RU: Клиентский код работает со всеми объектами, используя интерфейс + Компонента. Таким образом, он остаётся независимым от конкретных классов + компонентов, с которыми работает. + """ + + # ... + + print(f"RESULT: {component.operation()}", end="") + + # ... + + +if __name__ == "__main__": + # EN: This way the client code can support both simple components... + # + # RU: Таким образом, клиентский код может поддерживать как простые + # компоненты... + simple = ConcreteComponent() + print("Client: I've got a simple component:") + client_code(simple) + print("\n") + + # EN: ...as well as decorated ones. + # + # Note how decorators can wrap not only simple components but the other + # decorators as well. + # + # RU: ...так и декорированные. + # + # Обратите внимание, что декораторы могут обёртывать не только простые + # компоненты, но и другие декораторы. + decorator1 = ConcreteDecoratorA(simple) + decorator2 = ConcreteDecoratorB(decorator1) + print("Client: Now I've got a decorated component:") + client_code(decorator2) diff --git a/patterns/refactoring.guru/src/Facade/Conceptual/Output.txt b/patterns/refactoring.guru/src/Facade/Conceptual/Output.txt new file mode 100755 index 000000000..4e76f7e74 --- /dev/null +++ b/patterns/refactoring.guru/src/Facade/Conceptual/Output.txt @@ -0,0 +1,6 @@ +Facade initializes subsystems: +Subsystem1: Ready! +Subsystem2: Get ready! +Facade orders subsystems to perform the action: +Subsystem1: Go! +Subsystem2: Fire! \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Facade/Conceptual/main.py b/patterns/refactoring.guru/src/Facade/Conceptual/main.py new file mode 100755 index 000000000..85a834bf0 --- /dev/null +++ b/patterns/refactoring.guru/src/Facade/Conceptual/main.py @@ -0,0 +1,129 @@ +""" +EN: Facade Design Pattern + +Intent: Provides a simplified interface to a library, a framework, or any other +complex set of classes. + +RU: Паттерн Фасад + +Назначение: Предоставляет простой интерфейс к сложной системе классов, +библиотеке или фреймворку. +""" + + +from __future__ import annotations + + +class Facade: + """ + EN: The Facade class provides a simple interface to the complex logic of one + or several subsystems. The Facade delegates the client requests to the + appropriate objects within the subsystem. The Facade is also responsible for + managing their lifecycle. All of this shields the client from the undesired + complexity of the subsystem. + + RU: Класс Фасада предоставляет простой интерфейс для сложной логики одной + или нескольких подсистем. Фасад делегирует запросы клиентов соответствующим + объектам внутри подсистемы. Фасад также отвечает за управление их жизненным + циклом. Все это защищает клиента от нежелательной сложности подсистемы. + """ + + def __init__(self, subsystem1: Subsystem1, subsystem2: Subsystem2) -> None: + """ + EN: Depending on your application's needs, you can provide the Facade + with existing subsystem objects or force the Facade to create them on + its own. + + RU: В зависимости от потребностей вашего приложения вы можете + предоставить Фасаду существующие объекты подсистемы или заставить Фасад + создать их самостоятельно. + """ + + self._subsystem1 = subsystem1 or Subsystem1() + self._subsystem2 = subsystem2 or Subsystem2() + + def operation(self) -> str: + """ + EN: The Facade's methods are convenient shortcuts to the sophisticated + functionality of the subsystems. However, clients get only to a fraction + of a subsystem's capabilities. + + RU: Методы Фасада удобны для быстрого доступа к сложной функциональности + подсистем. Однако клиенты получают только часть возможностей подсистемы. + """ + + results = [] + results.append("Facade initializes subsystems:") + results.append(self._subsystem1.operation1()) + results.append(self._subsystem2.operation1()) + results.append("Facade orders subsystems to perform the action:") + results.append(self._subsystem1.operation_n()) + results.append(self._subsystem2.operation_z()) + return "\n".join(results) + + +class Subsystem1: + """ + EN: The Subsystem can accept requests either from the facade or client + directly. In any case, to the Subsystem, the Facade is yet another client, + and it's not a part of the Subsystem. + + RU: Подсистема может принимать запросы либо от фасада, либо от клиента + напрямую. В любом случае, для Подсистемы Фасад – это ещё один клиент, и он + не является частью Подсистемы. + """ + + def operation1(self) -> str: + return "Subsystem1: Ready!" + + # ... + + def operation_n(self) -> str: + return "Subsystem1: Go!" + + +class Subsystem2: + """ + EN: Some facades can work with multiple subsystems at the same time. + + RU: Некоторые фасады могут работать с разными подсистемами одновременно. + """ + + def operation1(self) -> str: + return "Subsystem2: Get ready!" + + # ... + + def operation_z(self) -> str: + return "Subsystem2: Fire!" + + +def client_code(facade: Facade) -> None: + """ + EN: The client code works with complex subsystems through a simple interface + provided by the Facade. When a facade manages the lifecycle of the + subsystem, the client might not even know about the existence of the + subsystem. This approach lets you keep the complexity under control. + + RU: Клиентский код работает со сложными подсистемами через простой + интерфейс, предоставляемый Фасадом. Когда фасад управляет жизненным циклом + подсистемы, клиент может даже не знать о существовании подсистемы. Такой + подход позволяет держать сложность под контролем. + """ + + print(facade.operation(), end="") + + +if __name__ == "__main__": + # EN: The client code may have some of the subsystem's objects already + # created. In this case, it might be worthwhile to initialize the Facade + # with these objects instead of letting the Facade create new instances. + # + # RU: В клиентском коде могут быть уже созданы некоторые объекты подсистемы. + # В этом случае может оказаться целесообразным инициализировать Фасад с + # этими объектами вместо того, чтобы позволить Фасаду создавать новые + # экземпляры. + subsystem1 = Subsystem1() + subsystem2 = Subsystem2() + facade = Facade(subsystem1, subsystem2) + client_code(facade) diff --git a/patterns/refactoring.guru/src/FactoryMethod/Conceptual/Output.txt b/patterns/refactoring.guru/src/FactoryMethod/Conceptual/Output.txt new file mode 100755 index 000000000..53a22b9d0 --- /dev/null +++ b/patterns/refactoring.guru/src/FactoryMethod/Conceptual/Output.txt @@ -0,0 +1,7 @@ +App: Launched with the ConcreteCreator1. +Client: I'm not aware of the creator's class, but it still works. +Creator: The same creator's code has just worked with {Result of the ConcreteProduct1} + +App: Launched with the ConcreteCreator2. +Client: I'm not aware of the creator's class, but it still works. +Creator: The same creator's code has just worked with {Result of the ConcreteProduct2} \ No newline at end of file diff --git a/patterns/refactoring.guru/src/FactoryMethod/Conceptual/main.py b/patterns/refactoring.guru/src/FactoryMethod/Conceptual/main.py new file mode 100755 index 000000000..14d6fb71d --- /dev/null +++ b/patterns/refactoring.guru/src/FactoryMethod/Conceptual/main.py @@ -0,0 +1,152 @@ +""" +EN: Factory Method Design Pattern + +Intent: Provides an interface for creating objects in a superclass, but allows +subclasses to alter the type of objects that will be created. + +RU: Паттерн Фабричный Метод + +Назначение: Определяет общий интерфейс для создания объектов в суперклассе, +позволяя подклассам изменять тип создаваемых объектов. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod + + +class Creator(ABC): + """ + EN: The Creator class declares the factory method that is supposed to return + an object of a Product class. The Creator's subclasses usually provide the + implementation of this method. + + RU: Класс Создатель объявляет фабричный метод, который должен возвращать + объект класса Продукт. Подклассы Создателя обычно предоставляют реализацию + этого метода. + """ + + @abstractmethod + def factory_method(self): + """ + EN: Note that the Creator may also provide some default implementation + of the factory method. + + RU: Обратите внимание, что Создатель может также обеспечить реализацию + фабричного метода по умолчанию. + """ + pass + + def some_operation(self) -> str: + """ + EN: Also note that, despite its name, the Creator's primary + responsibility is not creating products. Usually, it contains some core + business logic that relies on Product objects, returned by the factory + method. Subclasses can indirectly change that business logic by + overriding the factory method and returning a different type of product + from it. + + RU: Также заметьте, что, несмотря на название, основная обязанность + Создателя не заключается в создании продуктов. Обычно он содержит + некоторую базовую бизнес-логику, которая основана на объектах Продуктов, + возвращаемых фабричным методом. Подклассы могут косвенно изменять эту + бизнес-логику, переопределяя фабричный метод и возвращая из него другой + тип продукта. + """ + + # EN: Call the factory method to create a Product object. + # + # RU: Вызываем фабричный метод, чтобы получить объект-продукт. + product = self.factory_method() + + # EN: Now, use the product. + # + # RU: Далее, работаем с этим продуктом. + result = f"Creator: The same creator's code has just worked with {product.operation()}" + + return result + + +""" +EN: Concrete Creators override the factory method in order to change the +resulting product's type. + +RU: Конкретные Создатели переопределяют фабричный метод для того, чтобы изменить +тип результирующего продукта. +""" + + +class ConcreteCreator1(Creator): + """ + EN: Note that the signature of the method still uses the abstract product + type, even though the concrete product is actually returned from the method. + This way the Creator can stay independent of concrete product classes. + + RU: Обратите внимание, что сигнатура метода по-прежнему использует тип + абстрактного продукта, хотя фактически из метода возвращается конкретный + продукт. Таким образом, Создатель может оставаться независимым от конкретных + классов продуктов. + """ + + def factory_method(self) -> Product: + return ConcreteProduct1() + + +class ConcreteCreator2(Creator): + def factory_method(self) -> Product: + return ConcreteProduct2() + + +class Product(ABC): + """ + EN: The Product interface declares the operations that all concrete products + must implement. + + RU: Интерфейс Продукта объявляет операции, которые должны выполнять все + конкретные продукты. + """ + + @abstractmethod + def operation(self) -> str: + pass + + +""" +EN: Concrete Products provide various implementations of the Product interface. + +RU: Конкретные Продукты предоставляют различные реализации интерфейса Продукта. +""" + + +class ConcreteProduct1(Product): + def operation(self) -> str: + return "{Result of the ConcreteProduct1}" + + +class ConcreteProduct2(Product): + def operation(self) -> str: + return "{Result of the ConcreteProduct2}" + + +def client_code(creator: Creator) -> None: + """ + EN: The client code works with an instance of a concrete creator, albeit + through its base interface. As long as the client keeps working with the + creator via the base interface, you can pass it any creator's subclass. + + RU: Клиентский код работает с экземпляром конкретного создателя, хотя и + через его базовый интерфейс. Пока клиент продолжает работать с создателем + через базовый интерфейс, вы можете передать ему любой подкласс создателя. + """ + + print(f"Client: I'm not aware of the creator's class, but it still works.\n" + f"{creator.some_operation()}", end="") + + +if __name__ == "__main__": + print("App: Launched with the ConcreteCreator1.") + client_code(ConcreteCreator1()) + print("\n") + + print("App: Launched with the ConcreteCreator2.") + client_code(ConcreteCreator2()) diff --git a/patterns/refactoring.guru/src/Flyweight/Conceptual/Output.txt b/patterns/refactoring.guru/src/Flyweight/Conceptual/Output.txt new file mode 100755 index 000000000..6fca45f31 --- /dev/null +++ b/patterns/refactoring.guru/src/Flyweight/Conceptual/Output.txt @@ -0,0 +1,22 @@ +FlyweightFactory: I have 5 flyweights: +Camaro2018_Chevrolet_pink +C300_Mercedes Benz_black +C500_Mercedes Benz_red +BMW_M5_red +BMW_X6_white + +Client: Adding a car to database. +FlyweightFactory: Reusing existing flyweight. +Flyweight: Displaying shared (["BMW", "M5", "red"]) and unique (["CL234IR", "James Doe"]) state. + +Client: Adding a car to database. +FlyweightFactory: Can't find a flyweight, creating new one. +Flyweight: Displaying shared (["BMW", "X1", "red"]) and unique (["CL234IR", "James Doe"]) state. + +FlyweightFactory: I have 6 flyweights: +Camaro2018_Chevrolet_pink +C300_Mercedes Benz_black +C500_Mercedes Benz_red +BMW_M5_red +BMW_X6_white +BMW_X1_red \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Flyweight/Conceptual/main.py b/patterns/refactoring.guru/src/Flyweight/Conceptual/main.py new file mode 100755 index 000000000..c8e73fe98 --- /dev/null +++ b/patterns/refactoring.guru/src/Flyweight/Conceptual/main.py @@ -0,0 +1,136 @@ +""" +EN: Flyweight Design Pattern + +Intent: Lets you fit more objects into the available amount of RAM by sharing +common parts of state between multiple objects, instead of keeping all of the +data in each object. + +RU: Паттерн Легковес + +Назначение: Позволяет вместить бóльшее количество объектов в отведённую +оперативную память. Легковес экономит память, разделяя общее состояние объектов +между собой, вместо хранения одинаковых данных в каждом объекте. +""" + + +import json +from typing import Dict + + +class Flyweight(): + """ + EN: The Flyweight stores a common portion of the state (also called + intrinsic state) that belongs to multiple real business entities. The + Flyweight accepts the rest of the state (extrinsic state, unique for each + entity) via its method parameters. + + RU: Легковес хранит общую часть состояния (также называемую внутренним + состоянием), которая принадлежит нескольким реальным бизнес-объектам. + Легковес принимает оставшуюся часть состояния (внешнее состояние, уникальное + для каждого объекта) через его параметры метода. + """ + + def __init__(self, shared_state: str) -> None: + self._shared_state = shared_state + + def operation(self, unique_state: str) -> None: + s = json.dumps(self._shared_state) + u = json.dumps(unique_state) + print(f"Flyweight: Displaying shared ({s}) and unique ({u}) state.", end="") + + +class FlyweightFactory(): + """ + EN: The Flyweight Factory creates and manages the Flyweight objects. It + ensures that flyweights are shared correctly. When the client requests a + flyweight, the factory either returns an existing instance or creates a new + one, if it doesn't exist yet. + + RU: Фабрика Легковесов создает объекты-Легковесы и управляет ими. Она + обеспечивает правильное разделение легковесов. Когда клиент запрашивает + легковес, фабрика либо возвращает существующий экземпляр, либо создает + новый, если он ещё не существует. + """ + + _flyweights: Dict[str, Flyweight] = {} + + def __init__(self, initial_flyweights: Dict) -> None: + for state in initial_flyweights: + self._flyweights[self.get_key(state)] = Flyweight(state) + + def get_key(self, state: Dict) -> str: + """ + EN: Returns a Flyweight's string hash for a given state. + + RU: Возвращает хеш строки Легковеса для данного состояния. + """ + + return "_".join(sorted(state)) + + def get_flyweight(self, shared_state: Dict) -> Flyweight: + """ + EN: Returns an existing Flyweight with a given state or creates a new + one. + + RU: Возвращает существующий Легковес с заданным состоянием или создает + новый. + """ + + key = self.get_key(shared_state) + + if not self._flyweights.get(key): + print("FlyweightFactory: Can't find a flyweight, creating new one.") + self._flyweights[key] = Flyweight(shared_state) + else: + print("FlyweightFactory: Reusing existing flyweight.") + + return self._flyweights[key] + + def list_flyweights(self) -> None: + count = len(self._flyweights) + print(f"FlyweightFactory: I have {count} flyweights:") + print("\n".join(map(str, self._flyweights.keys())), end="") + + +def add_car_to_police_database( + factory: FlyweightFactory, plates: str, owner: str, + brand: str, model: str, color: str +) -> None: + print("\n\nClient: Adding a car to database.") + flyweight = factory.get_flyweight([brand, model, color]) + # EN: The client code either stores or calculates extrinsic state and passes + # it to the flyweight's methods. + # + # RU: Клиентский код либо сохраняет, либо вычисляет внешнее состояние и + # передает его методам легковеса. + flyweight.operation([plates, owner]) + + +if __name__ == "__main__": + """ + EN: The client code usually creates a bunch of pre-populated flyweights in + the initialization stage of the application. + + RU: Клиентский код обычно создает кучу предварительно заполненных легковесов + на этапе инициализации приложения. + """ + + factory = FlyweightFactory([ + ["Chevrolet", "Camaro2018", "pink"], + ["Mercedes Benz", "C300", "black"], + ["Mercedes Benz", "C500", "red"], + ["BMW", "M5", "red"], + ["BMW", "X6", "white"], + ]) + + factory.list_flyweights() + + add_car_to_police_database( + factory, "CL234IR", "James Doe", "BMW", "M5", "red") + + add_car_to_police_database( + factory, "CL234IR", "James Doe", "BMW", "X1", "red") + + print("\n") + + factory.list_flyweights() diff --git a/patterns/refactoring.guru/src/Iterator/Conceptual/Output.txt b/patterns/refactoring.guru/src/Iterator/Conceptual/Output.txt new file mode 100755 index 000000000..48107cd61 --- /dev/null +++ b/patterns/refactoring.guru/src/Iterator/Conceptual/Output.txt @@ -0,0 +1,9 @@ +Straight traversal: +First +Second +Third + +Reverse traversal: +Third +Second +First \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Iterator/Conceptual/main.py b/patterns/refactoring.guru/src/Iterator/Conceptual/main.py new file mode 100755 index 000000000..e2f60ce79 --- /dev/null +++ b/patterns/refactoring.guru/src/Iterator/Conceptual/main.py @@ -0,0 +1,132 @@ +""" +EN: Iterator Design Pattern + +Intent: Lets you traverse elements of a collection without exposing its +underlying representation (list, stack, tree, etc.). + +RU: Паттерн Итератор + +Назначение: Даёт возможность последовательно обходить элементы составных +объектов, не раскрывая их внутреннего представления. +""" + + +from __future__ import annotations +from collections.abc import Iterable, Iterator +from typing import Any + + +""" +EN: To create an iterator in Python, there are two abstract classes from the +built-in `collections` module - Iterable,Iterator. We need to implement the +`__iter__()` method in the iterated object (collection), and the `__next__ ()` +method in theiterator. + +RU: Для создания итератора в Python есть два абстрактных класса из встроенного +модуля collections - Iterable, Iterator. Нужно реализовать метод __iter__() в +итерируемом объекте (списке), а метод __next__() в итераторе. +""" + + +class AlphabeticalOrderIterator(Iterator): + """ + EN: Concrete Iterators implement various traversal algorithms. These classes + store the current traversal position at all times. + + RU: Конкретные Итераторы реализуют различные алгоритмы обхода. Эти классы + постоянно хранят текущее положение обхода. + """ + + """ + EN: `_position` attribute stores the current traversal position. An iterator + may have a lot of other fields for storing iteration state, especially when + it is supposed to work with a particular kind of collection. + + RU: Атрибут _position хранит текущее положение обхода. У итератора может + быть множество других полей для хранения состояния итерации, особенно когда + он должен работать с определённым типом коллекции. + """ + _position: int = None + + """ + EN: This attribute indicates the traversal direction. + + RU: Этот атрибут указывает направление обхода. + """ + _reverse: bool = False + + def __init__(self, collection: WordsCollection, reverse: bool = False) -> None: + self._collection = collection + self._reverse = reverse + self._position = -1 if reverse else 0 + + def __next__(self) -> Any: + """ + EN: The __next__() method must return the next item in the sequence. On + reaching the end, and in subsequent calls, it must raise StopIteration. + + RU: Метод __next __() должен вернуть следующий элемент в + последовательности. При достижении конца коллекции и в последующих + вызовах должно вызываться исключение StopIteration. + """ + try: + value = self._collection[self._position] + self._position += -1 if self._reverse else 1 + except IndexError: + raise StopIteration() + + return value + + +class WordsCollection(Iterable): + """ + EN: Concrete Collections provide one or several methods for retrieving fresh + iterator instances, compatible with the collection class. + + RU: Конкретные Коллекции предоставляют один или несколько методов для + получения новых экземпляров итератора, совместимых с классом коллекции. + """ + + def __init__(self, collection: list[Any] | None = None) -> None: + self._collection = collection or [] + + + def __getitem__(self, index: int) -> Any: + return self._collection[index] + + def __iter__(self) -> AlphabeticalOrderIterator: + """ + EN: The __iter__() method returns the iterator object itself, by default + we return the iterator in ascending order. + + RU: Метод __iter__() возвращает объект итератора, по умолчанию мы + возвращаем итератор с сортировкой по возрастанию. + """ + return AlphabeticalOrderIterator(self) + + def get_reverse_iterator(self) -> AlphabeticalOrderIterator: + return AlphabeticalOrderIterator(self, True) + + def add_item(self, item: Any) -> None: + self._collection.append(item) + + +if __name__ == "__main__": + # EN: The client code may or may not know about the Concrete Iterator or + # Collection classes, depending on the level of indirection you want to keep + # in your program. + # + # RU: Клиентский код может знать или не знать о Конкретном Итераторе или + # классах Коллекций, в зависимости от уровня косвенности, который вы хотите + # сохранить в своей программе. + collection = WordsCollection() + collection.add_item("First") + collection.add_item("Second") + collection.add_item("Third") + + print("Straight traversal:") + print("\n".join(collection)) + print("") + + print("Reverse traversal:") + print("\n".join(collection.get_reverse_iterator()), end="") diff --git a/patterns/refactoring.guru/src/Mediator/Conceptual/Output.txt b/patterns/refactoring.guru/src/Mediator/Conceptual/Output.txt new file mode 100755 index 000000000..744e08113 --- /dev/null +++ b/patterns/refactoring.guru/src/Mediator/Conceptual/Output.txt @@ -0,0 +1,11 @@ +Client triggers operation A. +Component 1 does A. +Mediator reacts on A and triggers following operations: +Component 2 does C. + + +Client triggers operation D. +Component 2 does D. +Mediator reacts on D and triggers following operations: +Component 1 does B. +Component 2 does C. diff --git a/patterns/refactoring.guru/src/Mediator/Conceptual/main.py b/patterns/refactoring.guru/src/Mediator/Conceptual/main.py new file mode 100755 index 000000000..e2c95d88d --- /dev/null +++ b/patterns/refactoring.guru/src/Mediator/Conceptual/main.py @@ -0,0 +1,116 @@ +""" +EN: Mediator Design Pattern + +Intent: Lets you reduce chaotic dependencies between objects. The pattern +restricts direct communications between the objects and forces them to +collaborate only via a mediator object. + +RU: Паттерн Посредник + +Назначение: Позволяет уменьшить связанность множества классов между собой, +благодаря перемещению этих связей в один класс-посредник. +""" + + +from __future__ import annotations +from abc import ABC + + +class Mediator(ABC): + """ + EN: The Mediator interface declares a method used by components to notify + the mediator about various events. The Mediator may react to these events + and pass the execution to other components. + + RU: Интерфейс Посредника предоставляет метод, используемый компонентами для + уведомления посредника о различных событиях. Посредник может реагировать на + эти события и передавать исполнение другим компонентам. + """ + + def notify(self, sender: object, event: str) -> None: + pass + + +class ConcreteMediator(Mediator): + def __init__(self, component1: Component1, component2: Component2) -> None: + self._component1 = component1 + self._component1.mediator = self + self._component2 = component2 + self._component2.mediator = self + + def notify(self, sender: object, event: str) -> None: + if event == "A": + print("Mediator reacts on A and triggers following operations:") + self._component2.do_c() + elif event == "D": + print("Mediator reacts on D and triggers following operations:") + self._component1.do_b() + self._component2.do_c() + + +class BaseComponent: + """ + EN: The Base Component provides the basic functionality of storing a + mediator's instance inside component objects. + + RU: Базовый Компонент обеспечивает базовую функциональность хранения + экземпляра посредника внутри объектов компонентов. + """ + + def __init__(self, mediator: Mediator = None) -> None: + self._mediator = mediator + + @property + def mediator(self) -> Mediator: + return self._mediator + + @mediator.setter + def mediator(self, mediator: Mediator) -> None: + self._mediator = mediator + + +""" +EN: Concrete Components implement various functionality. They don't depend on +other components. They also don't depend on any concrete mediator classes. + +RU: Конкретные Компоненты реализуют различную функциональность. Они не зависят +от других компонентов. Они также не зависят от каких-либо конкретных классов +посредников. +""" + + +class Component1(BaseComponent): + def do_a(self) -> None: + print("Component 1 does A.") + self.mediator.notify(self, "A") + + def do_b(self) -> None: + print("Component 1 does B.") + self.mediator.notify(self, "B") + + +class Component2(BaseComponent): + def do_c(self) -> None: + print("Component 2 does C.") + self.mediator.notify(self, "C") + + def do_d(self) -> None: + print("Component 2 does D.") + self.mediator.notify(self, "D") + + +if __name__ == "__main__": + # EN: The client code. + # + # RU: Клиентский код. + c1 = Component1() + c2 = Component2() + mediator = ConcreteMediator(c1, c2) + + print("Client triggers operation A.") + c1.do_a() + + print("\n", end="") + + print("Client triggers operation D.") + c2.do_d() diff --git a/patterns/refactoring.guru/src/Memento/Conceptual/Output.txt b/patterns/refactoring.guru/src/Memento/Conceptual/Output.txt new file mode 100755 index 000000000..4ef09e6c7 --- /dev/null +++ b/patterns/refactoring.guru/src/Memento/Conceptual/Output.txt @@ -0,0 +1,28 @@ +Originator: My initial state is: Super-duper-super-puper-super. + +Caretaker: Saving Originator's state... +Originator: I'm doing something important. +Originator: and my state has changed to: wQAehHYOqVSlpEXjyIcgobrxsZUnat + +Caretaker: Saving Originator's state... +Originator: I'm doing something important. +Originator: and my state has changed to: lHxNORKcsgMWYnJqoXjVCbQLEIeiSp + +Caretaker: Saving Originator's state... +Originator: I'm doing something important. +Originator: and my state has changed to: cvIYsRilNOtwynaKdEZpDCQkFAXVMf + +Caretaker: Here's the list of mementos: +2019-01-26 21:11:24 / (Super-dup...) +2019-01-26 21:11:24 / (wQAehHYOq...) +2019-01-26 21:11:24 / (lHxNORKcs...) + +Client: Now, let's rollback! + +Caretaker: Restoring state to: 2019-01-26 21:11:24 / (lHxNORKcs...) +Originator: My state has changed to: lHxNORKcsgMWYnJqoXjVCbQLEIeiSp + +Client: Once more! + +Caretaker: Restoring state to: 2019-01-26 21:11:24 / (wQAehHYOq...) +Originator: My state has changed to: wQAehHYOqVSlpEXjyIcgobrxsZUnat diff --git a/patterns/refactoring.guru/src/Memento/Conceptual/main.py b/patterns/refactoring.guru/src/Memento/Conceptual/main.py new file mode 100755 index 000000000..dfd2ae65e --- /dev/null +++ b/patterns/refactoring.guru/src/Memento/Conceptual/main.py @@ -0,0 +1,188 @@ +""" +EN: Memento Design Pattern + +Intent: Lets you save and restore the previous state of an object without +revealing the details of its implementation. + +RU: Паттерн Снимок + +Назначение: Фиксирует и восстанавливает внутреннее состояние объекта таким +образом, чтобы в дальнейшем объект можно было восстановить в этом состоянии без +нарушения инкапсуляции. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod +from datetime import datetime +from random import sample +from string import ascii_letters + + +class Originator: + """ + EN: The Originator holds some important state that may change over time. It + also defines a method for saving the state inside a memento and another + method for restoring the state from it. + + RU: Создатель содержит некоторое важное состояние, которое может со временем + меняться. Он также объявляет метод сохранения состояния внутри снимка и + метод восстановления состояния из него. + """ + + _state = None + """ + EN: For the sake of simplicity, the originator's state is stored inside a + single variable. + + RU: Для удобства состояние создателя хранится внутри одной переменной. + """ + + def __init__(self, state: str) -> None: + self._state = state + print(f"Originator: My initial state is: {self._state}") + + def do_something(self) -> None: + """ + EN: The Originator's business logic may affect its internal state. + Therefore, the client should backup the state before launching methods + of the business logic via the save() method. + + RU: Бизнес-логика Создателя может повлиять на его внутреннее состояние. + Поэтому клиент должен выполнить резервное копирование состояния с + помощью метода save перед запуском методов бизнес-логики. + """ + + print("Originator: I'm doing something important.") + self._state = self._generate_random_string(30) + print(f"Originator: and my state has changed to: {self._state}") + + @staticmethod + def _generate_random_string(length: int = 10) -> str: + return "".join(sample(ascii_letters, length)) + + def save(self) -> Memento: + """ + EN: Saves the current state inside a memento. + + RU: Сохраняет текущее состояние внутри снимка. + """ + + return ConcreteMemento(self._state) + + def restore(self, memento: Memento) -> None: + """ + EN: Restores the Originator's state from a memento object. + + RU: Восстанавливает состояние Создателя из объекта снимка. + """ + + self._state = memento.get_state() + print(f"Originator: My state has changed to: {self._state}") + + +class Memento(ABC): + """ + EN: The Memento interface provides a way to retrieve the memento's metadata, + such as creation date or name. However, it doesn't expose the Originator's + state. + + RU: Интерфейс Снимка предоставляет способ извлечения метаданных снимка, + таких как дата создания или название. Однако он не раскрывает состояние + Создателя. + """ + + @abstractmethod + def get_name(self) -> str: + pass + + @abstractmethod + def get_date(self) -> str: + pass + + +class ConcreteMemento(Memento): + def __init__(self, state: str) -> None: + self._state = state + self._date = str(datetime.now())[:19] + + def get_state(self) -> str: + """ + EN: The Originator uses this method when restoring its state. + + RU: Создатель использует этот метод, когда восстанавливает своё + состояние. + """ + return self._state + + def get_name(self) -> str: + """ + EN: The rest of the methods are used by the Caretaker to display + metadata. + + RU: Остальные методы используются Опекуном для отображения метаданных. + """ + + return f"{self._date} / ({self._state[0:9]}...)" + + def get_date(self) -> str: + return self._date + + +class Caretaker: + """ + EN: The Caretaker doesn't depend on the Concrete Memento class. Therefore, + it doesn't have access to the originator's state, stored inside the memento. + It works with all mementos via the base Memento interface. + + RU: Опекун не зависит от класса Конкретного Снимка. Таким образом, он не + имеет доступа к состоянию создателя, хранящемуся внутри снимка. Он работает + со всеми снимками через базовый интерфейс Снимка. + """ + + def __init__(self, originator: Originator) -> None: + self._mementos = [] + self._originator = originator + + def backup(self) -> None: + print("\nCaretaker: Saving Originator's state...") + self._mementos.append(self._originator.save()) + + def undo(self) -> None: + if not len(self._mementos): + return + + memento = self._mementos.pop() + print(f"Caretaker: Restoring state to: {memento.get_name()}") + try: + self._originator.restore(memento) + except Exception: + self.undo() + + def show_history(self) -> None: + print("Caretaker: Here's the list of mementos:") + for memento in self._mementos: + print(memento.get_name()) + + +if __name__ == "__main__": + originator = Originator("Super-duper-super-puper-super.") + caretaker = Caretaker(originator) + + caretaker.backup() + originator.do_something() + + caretaker.backup() + originator.do_something() + + caretaker.backup() + originator.do_something() + + print() + caretaker.show_history() + + print("\nClient: Now, let's rollback!\n") + caretaker.undo() + + print("\nClient: Once more!\n") + caretaker.undo() diff --git a/patterns/refactoring.guru/src/Observer/Conceptual/Output.txt b/patterns/refactoring.guru/src/Observer/Conceptual/Output.txt new file mode 100755 index 000000000..704d01002 --- /dev/null +++ b/patterns/refactoring.guru/src/Observer/Conceptual/Output.txt @@ -0,0 +1,18 @@ +Subject: Attached an observer. +Subject: Attached an observer. + +Subject: I'm doing something important. +Subject: My state has just changed to: 0 +Subject: Notifying observers... +ConcreteObserverA: Reacted to the event +ConcreteObserverB: Reacted to the event + +Subject: I'm doing something important. +Subject: My state has just changed to: 5 +Subject: Notifying observers... +ConcreteObserverB: Reacted to the event + +Subject: I'm doing something important. +Subject: My state has just changed to: 0 +Subject: Notifying observers... +ConcreteObserverB: Reacted to the event diff --git a/patterns/refactoring.guru/src/Observer/Conceptual/main.py b/patterns/refactoring.guru/src/Observer/Conceptual/main.py new file mode 100755 index 000000000..91de18c6e --- /dev/null +++ b/patterns/refactoring.guru/src/Observer/Conceptual/main.py @@ -0,0 +1,195 @@ +""" +EN: Observer Design Pattern + +Intent: Lets you define a subscription mechanism to notify multiple objects +about any events that happen to the object they're observing. + +Note that there's a lot of different terms with similar meaning associated with +this pattern. Just remember that the Subject is also called the Publisher and +the Observer is often called the Subscriber and vice versa. Also the verbs +"observe", "listen" or "track" usually mean the same thing. + +RU: Паттерн Наблюдатель + +Назначение: Создаёт механизм подписки, позволяющий одним объектам следить и +реагировать на события, происходящие в других объектах. + +Обратите внимание, что существует множество различных терминов с похожими +значениями, связанных с этим паттерном. Просто помните, что Субъекта также +называют Издателем, а Наблюдателя часто называют Подписчиком и наоборот. Также +глаголы «наблюдать», «слушать» или «отслеживать» обычно означают одно и то же. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod +from random import randrange +from typing import List + + +class Subject(ABC): + """ + EN: The Subject interface declares a set of methods for managing + subscribers. + + RU: Интерфейс издателя объявляет набор методов для управлениями + подписчиками. + """ + + @abstractmethod + def attach(self, observer: Observer) -> None: + """ + EN: Attach an observer to the subject. + + RU: Присоединяет наблюдателя к издателю. + """ + pass + + @abstractmethod + def detach(self, observer: Observer) -> None: + """ + EN: Detach an observer from the subject. + + RU: Отсоединяет наблюдателя от издателя. + """ + pass + + @abstractmethod + def notify(self) -> None: + """ + EN: Notify all observers about an event. + + RU: Уведомляет всех наблюдателей о событии. + """ + pass + + +class ConcreteSubject(Subject): + """ + EN: The Subject owns some important state and notifies observers when the + state changes. + + RU: Издатель владеет некоторым важным состоянием и оповещает наблюдателей о + его изменениях. + """ + + _state: int = None + """ + EN: For the sake of simplicity, the Subject's state, essential to all + subscribers, is stored in this variable. + + RU: Для удобства в этой переменной хранится состояние Издателя, необходимое + всем подписчикам. + """ + + _observers: List[Observer] = [] + """ + EN: List of subscribers. In real life, the list of subscribers can be stored + more comprehensively (categorized by event type, etc.). + + RU: Список подписчиков. В реальной жизни список подписчиков может храниться + в более подробном виде (классифицируется по типу события и т.д.) + """ + + def attach(self, observer: Observer) -> None: + print("Subject: Attached an observer.") + self._observers.append(observer) + + def detach(self, observer: Observer) -> None: + self._observers.remove(observer) + + """ + EN: The subscription management methods. + + RU: Методы управления подпиской. + """ + + def notify(self) -> None: + """ + EN: Trigger an update in each subscriber. + + RU: Запуск обновления в каждом подписчике. + """ + + print("Subject: Notifying observers...") + for observer in self._observers: + observer.update(self) + + def some_business_logic(self) -> None: + """ + EN: Usually, the subscription logic is only a fraction of what a Subject + can really do. Subjects commonly hold some important business logic, + that triggers a notification method whenever something important is + about to happen (or after it). + + RU: Обычно логика подписки – только часть того, что делает Издатель. + Издатели часто содержат некоторую важную бизнес-логику, которая + запускает метод уведомления всякий раз, когда должно произойти что-то + важное (или после этого). + """ + + print("\nSubject: I'm doing something important.") + self._state = randrange(0, 10) + + print(f"Subject: My state has just changed to: {self._state}") + self.notify() + + +class Observer(ABC): + """ + EN: The Observer interface declares the update method, used by subjects. + + RU: Интерфейс Наблюдателя объявляет метод уведомления, который издатели + используют для оповещения своих подписчиков. + """ + + @abstractmethod + def update(self, subject: Subject) -> None: + """ + EN: Receive update from subject. + + RU: Получить обновление от субъекта. + """ + pass + + +""" +EN: Concrete Observers react to the updates issued by the Subject they had been +attached to. + +RU: Конкретные Наблюдатели реагируют на обновления, выпущенные Издателем, к +которому они прикреплены. +""" + + +class ConcreteObserverA(Observer): + def update(self, subject: Subject) -> None: + if subject._state < 3: + print("ConcreteObserverA: Reacted to the event") + + +class ConcreteObserverB(Observer): + def update(self, subject: Subject) -> None: + if subject._state == 0 or subject._state >= 2: + print("ConcreteObserverB: Reacted to the event") + + +if __name__ == "__main__": + # EN: The client code. + # + # RU: Клиентский код. + + subject = ConcreteSubject() + + observer_a = ConcreteObserverA() + subject.attach(observer_a) + + observer_b = ConcreteObserverB() + subject.attach(observer_b) + + subject.some_business_logic() + subject.some_business_logic() + + subject.detach(observer_a) + + subject.some_business_logic() diff --git a/patterns/refactoring.guru/src/Prototype/Conceptual/Output.txt b/patterns/refactoring.guru/src/Prototype/Conceptual/Output.txt new file mode 100755 index 000000000..1dddade40 --- /dev/null +++ b/patterns/refactoring.guru/src/Prototype/Conceptual/Output.txt @@ -0,0 +1,7 @@ +Adding elements to `shallow_copied_component`'s some_list_of_objects adds it to `component`'s some_list_of_objects. +Changing objects in the `component`'s some_list_of_objects changes that object in `shallow_copied_component`'s some_list_of_objects. +Adding elements to `deep_copied_component`'s some_list_of_objects doesn't add it to `component`'s some_list_of_objects. +Changing objects in the `component`'s some_list_of_objects doesn't change that object in `deep_copied_component`'s some_list_of_objects. +id(deep_copied_component.some_circular_ref.parent): 4429472784 +id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent): 4429472784 +^^ This shows that deepcopied objects contain same reference, they are not cloned repeatedly. \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Prototype/Conceptual/main.py b/patterns/refactoring.guru/src/Prototype/Conceptual/main.py new file mode 100755 index 000000000..8e0db8212 --- /dev/null +++ b/patterns/refactoring.guru/src/Prototype/Conceptual/main.py @@ -0,0 +1,158 @@ +import copy + + +class SelfReferencingEntity: + def __init__(self): + self.parent = None + + def set_parent(self, parent): + self.parent = parent + + +class SomeComponent: + """ + Python provides its own interface of Prototype via `copy.copy` and + `copy.deepcopy` functions. And any class that wants to implement custom + implementations have to override `__copy__` and `__deepcopy__` member + functions. + """ + + def __init__(self, some_int, some_list_of_objects, some_circular_ref): + self.some_int = some_int + self.some_list_of_objects = some_list_of_objects + self.some_circular_ref = some_circular_ref + + def __copy__(self): + """ + Create a shallow copy. This method will be called whenever someone calls + `copy.copy` with this object and the returned value is returned as the + new shallow copy. + """ + + # First, let's create copies of the nested objects. + some_list_of_objects = copy.copy(self.some_list_of_objects) + some_circular_ref = copy.copy(self.some_circular_ref) + + # Then, let's clone the object itself, using the prepared clones of the + # nested objects. + new = self.__class__( + self.some_int, some_list_of_objects, some_circular_ref + ) + new.__dict__.update(self.__dict__) + + return new + + def __deepcopy__(self, memo=None): + """ + Create a deep copy. This method will be called whenever someone calls + `copy.deepcopy` with this object and the returned value is returned as + the new deep copy. + + What is the use of the argument `memo`? + Memo is the dictionary that is used by the `deepcopy` library to prevent + infinite recursive copies in instances of circular references. Pass it + to all the `deepcopy` calls you make in the `__deepcopy__` implementation + to prevent infinite recursions. + """ + if memo is None: + memo = {} + + # First, let's create copies of the nested objects. + some_list_of_objects = copy.deepcopy(self.some_list_of_objects, memo) + some_circular_ref = copy.deepcopy(self.some_circular_ref, memo) + + # Then, let's clone the object itself, using the prepared clones of the + # nested objects. + new = self.__class__( + self.some_int, some_list_of_objects, some_circular_ref + ) + new.__dict__ = copy.deepcopy(self.__dict__, memo) + + return new + + +if __name__ == "__main__": + + list_of_objects = [1, {1, 2, 3}, [1, 2, 3]] + circular_ref = SelfReferencingEntity() + component = SomeComponent(23, list_of_objects, circular_ref) + circular_ref.set_parent(component) + + shallow_copied_component = copy.copy(component) + + # Let's change the list in shallow_copied_component and see if it changes in + # component. + shallow_copied_component.some_list_of_objects.append("another object") + if component.some_list_of_objects[-1] == "another object": + print( + "Adding elements to `shallow_copied_component`'s " + "some_list_of_objects adds it to `component`'s " + "some_list_of_objects." + ) + else: + print( + "Adding elements to `shallow_copied_component`'s " + "some_list_of_objects doesn't add it to `component`'s " + "some_list_of_objects." + ) + + # Let's change the set in the list of objects. + component.some_list_of_objects[1].add(4) + if 4 in shallow_copied_component.some_list_of_objects[1]: + print( + "Changing objects in the `component`'s some_list_of_objects " + "changes that object in `shallow_copied_component`'s " + "some_list_of_objects." + ) + else: + print( + "Changing objects in the `component`'s some_list_of_objects " + "doesn't change that object in `shallow_copied_component`'s " + "some_list_of_objects." + ) + + deep_copied_component = copy.deepcopy(component) + + # Let's change the list in deep_copied_component and see if it changes in + # component. + deep_copied_component.some_list_of_objects.append("one more object") + if component.some_list_of_objects[-1] == "one more object": + print( + "Adding elements to `deep_copied_component`'s " + "some_list_of_objects adds it to `component`'s " + "some_list_of_objects." + ) + else: + print( + "Adding elements to `deep_copied_component`'s " + "some_list_of_objects doesn't add it to `component`'s " + "some_list_of_objects." + ) + + # Let's change the set in the list of objects. + component.some_list_of_objects[1].add(10) + if 10 in deep_copied_component.some_list_of_objects[1]: + print( + "Changing objects in the `component`'s some_list_of_objects " + "changes that object in `deep_copied_component`'s " + "some_list_of_objects." + ) + else: + print( + "Changing objects in the `component`'s some_list_of_objects " + "doesn't change that object in `deep_copied_component`'s " + "some_list_of_objects." + ) + + print( + f"id(deep_copied_component.some_circular_ref.parent): " + f"{id(deep_copied_component.some_circular_ref.parent)}" + ) + print( + f"id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent): " + f"{id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent)}" + ) + print( + "^^ This shows that deepcopied objects contain same reference, they " + "are not cloned repeatedly." + ) diff --git a/patterns/refactoring.guru/src/Proxy/Conceptual/Output.txt b/patterns/refactoring.guru/src/Proxy/Conceptual/Output.txt new file mode 100755 index 000000000..057b829f8 --- /dev/null +++ b/patterns/refactoring.guru/src/Proxy/Conceptual/Output.txt @@ -0,0 +1,7 @@ +Client: Executing the client code with a real subject: +RealSubject: Handling request. + +Client: Executing the same client code with a proxy: +Proxy: Checking access prior to firing a real request. +RealSubject: Handling request. +Proxy: Logging the time of request. \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Proxy/Conceptual/main.py b/patterns/refactoring.guru/src/Proxy/Conceptual/main.py new file mode 100755 index 000000000..c34fe6414 --- /dev/null +++ b/patterns/refactoring.guru/src/Proxy/Conceptual/main.py @@ -0,0 +1,121 @@ +""" +EN: Proxy Design Pattern + +Intent: Provide a surrogate or placeholder for another object to control access +to the original object or to add other responsibilities. + +RU: Паттерн Заместитель + +Назначение: Позволяет подставлять вместо реальных объектов специальные +объекты-заменители. Эти объекты перехватывают вызовы к оригинальному объекту, +позволяя сделать что-то до или после передачи вызова оригиналу. +""" + + +from abc import ABC, abstractmethod + + +class Subject(ABC): + """ + EN: The Subject interface declares common operations for both RealSubject + and the Proxy. As long as the client works with RealSubject using this + interface, you'll be able to pass it a proxy instead of a real subject. + + RU: Интерфейс Субъекта объявляет общие операции как для Реального Субъекта, + так и для Заместителя. Пока клиент работает с Реальным Субъектом, используя + этот интерфейс, вы сможете передать ему заместителя вместо реального + субъекта. + """ + + @abstractmethod + def request(self) -> None: + pass + + +class RealSubject(Subject): + """ + EN: The RealSubject contains some core business logic. Usually, RealSubjects + are capable of doing some useful work which may also be very slow or + sensitive - e.g. correcting input data. A Proxy can solve these issues + without any changes to the RealSubject's code. + + RU: Реальный Субъект содержит некоторую базовую бизнес-логику. Как правило, + Реальные Субъекты способны выполнять некоторую полезную работу, которая к + тому же может быть очень медленной или точной – например, коррекция входных + данных. Заместитель может решить эти задачи без каких-либо изменений в коде + Реального Субъекта. + """ + + def request(self) -> None: + print("RealSubject: Handling request.") + + +class Proxy(Subject): + """ + EN: The Proxy has an interface identical to the RealSubject. + + RU: Интерфейс Заместителя идентичен интерфейсу Реального Субъекта. + """ + + def __init__(self, real_subject: RealSubject) -> None: + self._real_subject = real_subject + + def request(self) -> None: + """ + EN: The most common applications of the Proxy pattern are lazy loading, + caching, controlling the access, logging, etc. A Proxy can perform one + of these things and then, depending on the result, pass the execution to + the same method in a linked RealSubject object. + + RU: Наиболее распространёнными областями применения паттерна Заместитель + являются ленивая загрузка, кэширование, контроль доступа, ведение + журнала и т.д. Заместитель может выполнить одну из этих задач, а затем, + в зависимости от результата, передать выполнение одноимённому методу в + связанном объекте класса Реального Субъекта. + """ + + if self.check_access(): + self._real_subject.request() + self.log_access() + + def check_access(self) -> bool: + print("Proxy: Checking access prior to firing a real request.") + return True + + def log_access(self) -> None: + print("Proxy: Logging the time of request.", end="") + + +def client_code(subject: Subject) -> None: + """ + EN: The client code is supposed to work with all objects (both subjects and + proxies) via the Subject interface in order to support both real subjects + and proxies. In real life, however, clients mostly work with their real + subjects directly. In this case, to implement the pattern more easily, you + can extend your proxy from the real subject's class. + + RU: Клиентский код должен работать со всеми объектами (как с реальными, так + и заместителями) через интерфейс Субъекта, чтобы поддерживать как реальные + субъекты, так и заместителей. В реальной жизни, однако, клиенты в основном + работают с реальными субъектами напрямую. В этом случае, для более простой + реализации паттерна, можно расширить заместителя из класса реального + субъекта. + """ + + # ... + + subject.request() + + # ... + + +if __name__ == "__main__": + print("Client: Executing the client code with a real subject:") + real_subject = RealSubject() + client_code(real_subject) + + print("") + + print("Client: Executing the same client code with a proxy:") + proxy = Proxy(real_subject) + client_code(proxy) diff --git a/patterns/refactoring.guru/src/Singleton/Conceptual/NonThreadSafe/Output.txt b/patterns/refactoring.guru/src/Singleton/Conceptual/NonThreadSafe/Output.txt new file mode 100755 index 000000000..6bcc57015 --- /dev/null +++ b/patterns/refactoring.guru/src/Singleton/Conceptual/NonThreadSafe/Output.txt @@ -0,0 +1 @@ +Singleton works, both variables contain the same instance. \ No newline at end of file diff --git a/patterns/refactoring.guru/src/Singleton/Conceptual/NonThreadSafe/main.py b/patterns/refactoring.guru/src/Singleton/Conceptual/NonThreadSafe/main.py new file mode 100755 index 000000000..f1fd8b383 --- /dev/null +++ b/patterns/refactoring.guru/src/Singleton/Conceptual/NonThreadSafe/main.py @@ -0,0 +1,66 @@ +""" +EN: Singleton Design Pattern + +Intent: Lets you ensure that a class has only one instance, while providing a +global access point to this instance. One instance per each subclass (if any). + +RU: Паттерн Одиночка + +Назначение: Гарантирует, что у класса есть только один экземпляр, и +предоставляет к нему глобальную точку доступа. У каждого наследника класса тоже +будет по одному экземпляру. +""" + + +class SingletonMeta(type): + """ + EN: The Singleton class can be implemented in different ways in Python. Some + possible methods include: base class, decorator, metaclass. We will use the + metaclass because it is best suited for this purpose. + + RU: В Python класс Одиночка можно реализовать по-разному. Возможные + способы включают себя базовый класс, декоратор, метакласс. Мы воспользуемся + метаклассом, поскольку он лучше всего подходит для этой цели. + """ + + _instances = {} + + def __call__(cls, *args, **kwargs): + """ + EN: Possible changes to the value of the `__init__` argument do not + affect the returned instance. + + RU: Данная реализация не учитывает возможное изменение передаваемых + аргументов в `__init__`. + """ + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] + + +class Singleton(metaclass=SingletonMeta): + def some_business_logic(self): + """ + EN: Finally, any singleton should define some business logic, which can + be executed on its instance. + + RU: Наконец, любой одиночка должен содержать некоторую бизнес-логику, + которая может быть выполнена на его экземпляре. + """ + + # ... + + +if __name__ == "__main__": + # EN: The client code. + # + # RU: Клиентский код. + + s1 = Singleton() + s2 = Singleton() + + if id(s1) == id(s2): + print("Singleton works, both variables contain the same instance.") + else: + print("Singleton failed, variables contain different instances.") diff --git a/patterns/refactoring.guru/src/Singleton/Conceptual/ThreadSafe/Output.txt b/patterns/refactoring.guru/src/Singleton/Conceptual/ThreadSafe/Output.txt new file mode 100755 index 000000000..8071f6062 --- /dev/null +++ b/patterns/refactoring.guru/src/Singleton/Conceptual/ThreadSafe/Output.txt @@ -0,0 +1,7 @@ +If you see the same value, then singleton was reused (yay!) +If you see different values, then 2 singletons were created (booo!!) + +RESULT: + +FOO +FOO diff --git a/patterns/refactoring.guru/src/Singleton/Conceptual/ThreadSafe/main.py b/patterns/refactoring.guru/src/Singleton/Conceptual/ThreadSafe/main.py new file mode 100755 index 000000000..9dfbd1cc3 --- /dev/null +++ b/patterns/refactoring.guru/src/Singleton/Conceptual/ThreadSafe/main.py @@ -0,0 +1,117 @@ +""" +EN: Singleton Design Pattern + +Intent: Lets you ensure that a class has only one instance, while providing a +global access point to this instance. One instance per each subclass (if any). + +RU: Паттерн Одиночка + +Назначение: Гарантирует, что у класса есть только один экземпляр, и +предоставляет к нему глобальную точку доступа. У каждого наследника класса тоже +будет по одному экземпляру. +""" + +from threading import Lock, Thread + + +class SingletonMeta(type): + """ + EN: This is a thread-safe implementation of Singleton. + + RU: Это потокобезопасная реализация класса Singleton. + """ + + _instances = {} + + _lock: Lock = Lock() + """ + We now have a lock object that will be used to synchronize + threads during first access to the Singleton. + + RU: У нас теперь есть объект-блокировка для синхронизации потоков во + время первого доступа к Одиночке. + """ + + def __call__(cls, *args, **kwargs): + """ + EN: Possible changes to the value of the `__init__` argument do not + affect the returned instance. + + RU: Данная реализация не учитывает возможное изменение передаваемых + аргументов в `__init__`. + """ + # EN: Now, imagine that the program has just been launched. + # Since there's no Singleton instance yet, multiple threads can + # simultaneously pass the previous conditional and reach this + # point almost at the same time. The first of them will acquire + # lock and will proceed further, while the rest will wait here. + # + # RU: Теперь представьте, что программа была только-только + # запущена. Объекта-одиночки ещё никто не создавал, поэтому + # несколько потоков вполне могли одновременно пройти через + # предыдущее условие и достигнуть блокировки. Самый быстрый + # поток поставит блокировку и двинется внутрь секции, пока + # другие будут здесь его ожидать. + with cls._lock: + # EN: The first thread to acquire the lock, reaches this + # conditional, goes inside and creates the Singleton + # instance. Once it leaves the lock block, a thread that + # might have been waiting for the lock release may then + # enter this section. But since the Singleton field is + # already initialized, the thread won't create a new + # object. + # + # RU: Первый поток достигает этого условия и проходит внутрь, + # создавая объект-одиночку. Как только этот поток покинет + # секцию и освободит блокировку, следующий поток может + # снова установить блокировку и зайти внутрь. Однако теперь + # экземпляр одиночки уже будет создан и поток не сможет + # пройти через это условие, а значит новый объект не будет + # создан. + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] + + +class Singleton(metaclass=SingletonMeta): + value: str = None + """ + EN: We'll use this property to prove that our Singleton really works. + + RU: Мы используем это поле, чтобы доказать, что наш Одиночка + действительно работает. + """ + + def __init__(self, value: str) -> None: + self.value = value + + def some_business_logic(self): + """ + EN: Finally, any singleton should define some business logic, which can + be executed on its instance. + + RU: Наконец, любой одиночка должен содержать некоторую бизнес-логику, + которая может быть выполнена на его экземпляре. + """ + + +def test_singleton(value: str) -> None: + singleton = Singleton(value) + print(singleton.value) + + +if __name__ == "__main__": + # EN: The client code. + # + # RU: Клиентский код. + + print("If you see the same value, then singleton was reused (yay!)\n" + "If you see different values, " + "then 2 singletons were created (booo!!)\n\n" + "RESULT:\n") + + process1 = Thread(target=test_singleton, args=("FOO",)) + process2 = Thread(target=test_singleton, args=("BAR",)) + process1.start() + process2.start() diff --git a/patterns/refactoring.guru/src/State/Conceptual/Output.txt b/patterns/refactoring.guru/src/State/Conceptual/Output.txt new file mode 100755 index 000000000..0ab114dab --- /dev/null +++ b/patterns/refactoring.guru/src/State/Conceptual/Output.txt @@ -0,0 +1,7 @@ +Context: Transition to ConcreteStateA +ConcreteStateA handles request1. +ConcreteStateA wants to change the state of the context. +Context: Transition to ConcreteStateB +ConcreteStateB handles request2. +ConcreteStateB wants to change the state of the context. +Context: Transition to ConcreteStateA diff --git a/patterns/refactoring.guru/src/State/Conceptual/main.py b/patterns/refactoring.guru/src/State/Conceptual/main.py new file mode 100755 index 000000000..bb253a645 --- /dev/null +++ b/patterns/refactoring.guru/src/State/Conceptual/main.py @@ -0,0 +1,129 @@ +""" +EN: State Design Pattern + +Intent: Lets an object alter its behavior when its internal state changes. It +appears as if the object changed its class. + +RU: Паттерн Состояние + +Назначение: Позволяет объектам менять поведение в зависимости от своего +состояния. Извне создаётся впечатление, что изменился класс объекта. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod + + +class Context: + """ + EN: The Context defines the interface of interest to clients. It also + maintains a reference to an instance of a State subclass, which represents + the current state of the Context. + + RU: Контекст определяет интерфейс, представляющий интерес для клиентов. Он + также хранит ссылку на экземпляр подкласса Состояния, который отображает + текущее состояние Контекста. + """ + + _state = None + """ + EN: A reference to the current state of the Context. + + RU: Ссылка на текущее состояние Контекста. + """ + + def __init__(self, state: State) -> None: + self.transition_to(state) + + def transition_to(self, state: State): + """ + EN: The Context allows changing the State object at runtime. + + RU: Контекст позволяет изменять объект Состояния во время выполнения. + """ + + print(f"Context: Transition to {type(state).__name__}") + self._state = state + self._state.context = self + + """ + EN: The Context delegates part of its behavior to the current State object. + + RU: Контекст делегирует часть своего поведения текущему объекту Состояния. + """ + + def request1(self): + self._state.handle1() + + def request2(self): + self._state.handle2() + + +class State(ABC): + """ + EN: The base State class declares methods that all Concrete State should + implement and also provides a backreference to the Context object, + associated with the State. This backreference can be used by States to + transition the Context to another State. + + RU: Базовый класс Состояния объявляет методы, которые должны реализовать все + Конкретные Состояния, а также предоставляет обратную ссылку на объект + Контекст, связанный с Состоянием. Эта обратная ссылка может использоваться + Состояниями для передачи Контекста другому Состоянию. + """ + + @property + def context(self) -> Context: + return self._context + + @context.setter + def context(self, context: Context) -> None: + self._context = context + + @abstractmethod + def handle1(self) -> None: + pass + + @abstractmethod + def handle2(self) -> None: + pass + + +""" +EN: Concrete States implement various behaviors, associated with a state of the +Context. + +RU: Конкретные Состояния реализуют различные модели поведения, связанные с +состоянием Контекста. +""" + + +class ConcreteStateA(State): + def handle1(self) -> None: + print("ConcreteStateA handles request1.") + print("ConcreteStateA wants to change the state of the context.") + self.context.transition_to(ConcreteStateB()) + + def handle2(self) -> None: + print("ConcreteStateA handles request2.") + + +class ConcreteStateB(State): + def handle1(self) -> None: + print("ConcreteStateB handles request1.") + + def handle2(self) -> None: + print("ConcreteStateB handles request2.") + print("ConcreteStateB wants to change the state of the context.") + self.context.transition_to(ConcreteStateA()) + + +if __name__ == "__main__": + # EN: The client code. + # + # RU: Клиентский код. + + context = Context(ConcreteStateA()) + context.request1() + context.request2() diff --git a/patterns/refactoring.guru/src/Strategy/Conceptual/Output.txt b/patterns/refactoring.guru/src/Strategy/Conceptual/Output.txt new file mode 100755 index 000000000..5bccbb77b --- /dev/null +++ b/patterns/refactoring.guru/src/Strategy/Conceptual/Output.txt @@ -0,0 +1,7 @@ +Client: Strategy is set to normal sorting. +Context: Sorting data using the strategy (not sure how it'll do it) +a,b,c,d,e + +Client: Strategy is set to reverse sorting. +Context: Sorting data using the strategy (not sure how it'll do it) +e,d,c,b,a diff --git a/patterns/refactoring.guru/src/Strategy/Conceptual/main.py b/patterns/refactoring.guru/src/Strategy/Conceptual/main.py new file mode 100755 index 000000000..5081582ef --- /dev/null +++ b/patterns/refactoring.guru/src/Strategy/Conceptual/main.py @@ -0,0 +1,136 @@ +""" +EN: Strategy Design Pattern + +Intent: Lets you define a family of algorithms, put each of them into a separate +class, and make their objects interchangeable. + +RU: Паттерн Стратегия + +Назначение: Определяет семейство схожих алгоритмов и помещает каждый из них в +собственный класс, после чего алгоритмы можно взаимозаменять прямо во время +исполнения программы. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import List + + +class Context(): + """ + EN: The Context defines the interface of interest to clients. + + RU: Контекст определяет интерфейс, представляющий интерес для клиентов. + """ + + def __init__(self, strategy: Strategy) -> None: + """ + EN: Usually, the Context accepts a strategy through the constructor, but + also provides a setter to change it at runtime. + + RU: Обычно Контекст принимает стратегию через конструктор, а также + предоставляет сеттер для её изменения во время выполнения. + """ + + self._strategy = strategy + + @property + def strategy(self) -> Strategy: + """ + EN: The Context maintains a reference to one of the Strategy objects. + The Context does not know the concrete class of a strategy. It should + work with all strategies via the Strategy interface. + + RU: Контекст хранит ссылку на один из объектов Стратегии. Контекст не + знает конкретного класса стратегии. Он должен работать со всеми + стратегиями через интерфейс Стратегии. + """ + + return self._strategy + + @strategy.setter + def strategy(self, strategy: Strategy) -> None: + """ + EN: Usually, the Context allows replacing a Strategy object at runtime. + + RU: Обычно Контекст позволяет заменить объект Стратегии во время + выполнения. + """ + + self._strategy = strategy + + def do_some_business_logic(self) -> None: + """ + EN: The Context delegates some work to the Strategy object instead of + implementing multiple versions of the algorithm on its own. + + RU: Вместо того, чтобы самостоятельно реализовывать множественные версии + алгоритма, Контекст делегирует некоторую работу объекту Стратегии. + """ + + # ... + + print("Context: Sorting data using the strategy (not sure how it'll do it)") + result = self._strategy.do_algorithm(["a", "b", "c", "d", "e"]) + print(",".join(result)) + + # ... + + +class Strategy(ABC): + """ + EN: The Strategy interface declares operations common to all supported + versions of some algorithm. + + The Context uses this interface to call the algorithm defined by Concrete + Strategies. + + RU: Интерфейс Стратегии объявляет операции, общие для всех поддерживаемых + версий некоторого алгоритма. + + Контекст использует этот интерфейс для вызова алгоритма, определённого + Конкретными Стратегиями. + """ + + @abstractmethod + def do_algorithm(self, data: List): + pass + + +""" +EN: Concrete Strategies implement the algorithm while following the base +Strategy interface. The interface makes them interchangeable in the Context. + +RU: Конкретные Стратегии реализуют алгоритм, следуя базовому интерфейсу +Стратегии. Этот интерфейс делает их взаимозаменяемыми в Контексте. +""" + + +class ConcreteStrategyA(Strategy): + def do_algorithm(self, data: List) -> List: + return sorted(data) + + +class ConcreteStrategyB(Strategy): + def do_algorithm(self, data: List) -> List: + return reversed(sorted(data)) + + +if __name__ == "__main__": + # EN: The client code picks a concrete strategy and passes it to the + # context. The client should be aware of the differences between strategies + # in order to make the right choice. + # + # RU: Клиентский код выбирает конкретную стратегию и передаёт её в контекст. + # Клиент должен знать о различиях между стратегиями, чтобы сделать + # правильный выбор. + + context = Context(ConcreteStrategyA()) + print("Client: Strategy is set to normal sorting.") + context.do_some_business_logic() + print() + + print("Client: Strategy is set to reverse sorting.") + context.strategy = ConcreteStrategyB() + context.do_some_business_logic() diff --git a/patterns/refactoring.guru/src/TemplateMethod/Conceptual/Output.txt b/patterns/refactoring.guru/src/TemplateMethod/Conceptual/Output.txt new file mode 100755 index 000000000..861ee508f --- /dev/null +++ b/patterns/refactoring.guru/src/TemplateMethod/Conceptual/Output.txt @@ -0,0 +1,14 @@ +Same client code can work with different subclasses: +AbstractClass says: I am doing the bulk of the work +ConcreteClass1 says: Implemented Operation1 +AbstractClass says: But I let subclasses override some operations +ConcreteClass1 says: Implemented Operation2 +AbstractClass says: But I am doing the bulk of the work anyway + +Same client code can work with different subclasses: +AbstractClass says: I am doing the bulk of the work +ConcreteClass2 says: Implemented Operation1 +AbstractClass says: But I let subclasses override some operations +ConcreteClass2 says: Overridden Hook1 +ConcreteClass2 says: Implemented Operation2 +AbstractClass says: But I am doing the bulk of the work anyway diff --git a/patterns/refactoring.guru/src/TemplateMethod/Conceptual/main.py b/patterns/refactoring.guru/src/TemplateMethod/Conceptual/main.py new file mode 100755 index 000000000..f0f45cbff --- /dev/null +++ b/patterns/refactoring.guru/src/TemplateMethod/Conceptual/main.py @@ -0,0 +1,152 @@ +""" +EN: Template Method Design Pattern + +Intent: Defines the skeleton of an algorithm in the superclass but lets +subclasses override specific steps of the algorithm without changing its +structure. + +RU: Паттерн Шаблонный метод + +Назначение: Определяет общую схему алгоритма, перекладывая реализацию некоторых +шагов на подклассы. Шаблонный метод позволяет подклассам переопределять +отдельные шаги алгоритма без изменения структуры алгоритма. +""" + + +from abc import ABC, abstractmethod + + +class AbstractClass(ABC): + """ + EN: The Abstract Class defines a template method that contains a skeleton of + some algorithm, composed of calls to (usually) abstract primitive + operations. + + Concrete subclasses should implement these operations, but leave the + template method itself intact. + + RU: Абстрактный Класс определяет шаблонный метод, содержащий скелет + некоторого алгоритма, состоящего из вызовов (обычно) абстрактных примитивных + операций. + + Конкретные подклассы должны реализовать эти операции, но оставить сам + шаблонный метод без изменений. + """ + + def template_method(self) -> None: + """ + EN: The template method defines the skeleton of an algorithm. + + RU: Шаблонный метод определяет скелет алгоритма. + """ + + self.base_operation1() + self.required_operations1() + self.base_operation2() + self.hook1() + self.required_operations2() + self.base_operation3() + self.hook2() + + # EN: These operations already have implementations. + # + # RU: Эти операции уже имеют реализации. + + def base_operation1(self) -> None: + print("AbstractClass says: I am doing the bulk of the work") + + def base_operation2(self) -> None: + print("AbstractClass says: But I let subclasses override some operations") + + def base_operation3(self) -> None: + print("AbstractClass says: But I am doing the bulk of the work anyway") + + # EN: These operations have to be implemented in subclasses. + # + # RU: А эти операции должны быть реализованы в подклассах. + + @abstractmethod + def required_operations1(self) -> None: + pass + + @abstractmethod + def required_operations2(self) -> None: + pass + + # EN: These are "hooks." Subclasses may override them, but it's not + # mandatory since the hooks already have default (but empty) implementation. + # Hooks provide additional extension points in some crucial places of the + # algorithm. + # + # RU: Это «хуки». Подклассы могут переопределять их, но это не обязательно, + # поскольку у хуков уже есть стандартная (но пустая) реализация. Хуки + # предоставляют дополнительные точки расширения в некоторых критических + # местах алгоритма. + + def hook1(self) -> None: + pass + + def hook2(self) -> None: + pass + + +class ConcreteClass1(AbstractClass): + """ + EN: Concrete classes have to implement all abstract operations of the base + class. They can also override some operations with a default implementation. + + RU: Конкретные классы должны реализовать все абстрактные операции базового + класса. Они также могут переопределить некоторые операции с реализацией по + умолчанию. + """ + + def required_operations1(self) -> None: + print("ConcreteClass1 says: Implemented Operation1") + + def required_operations2(self) -> None: + print("ConcreteClass1 says: Implemented Operation2") + + +class ConcreteClass2(AbstractClass): + """ + EN: Usually, concrete classes override only a fraction of base class' + operations. + + RU: Обычно конкретные классы переопределяют только часть операций базового + класса. + """ + + def required_operations1(self) -> None: + print("ConcreteClass2 says: Implemented Operation1") + + def required_operations2(self) -> None: + print("ConcreteClass2 says: Implemented Operation2") + + def hook1(self) -> None: + print("ConcreteClass2 says: Overridden Hook1") + + +def client_code(abstract_class: AbstractClass) -> None: + """ + EN: The client code calls the template method to execute the algorithm. + Client code does not have to know the concrete class of an object it works + with, as long as it works with objects through the interface of their base + class. + + RU: Клиентский код вызывает шаблонный метод для выполнения алгоритма. + Клиентский код не должен знать конкретный класс объекта, с которым работает, + при условии, что он работает с объектами через интерфейс их базового класса. + """ + + # ... + abstract_class.template_method() + # ... + + +if __name__ == "__main__": + print("Same client code can work with different subclasses:") + client_code(ConcreteClass1()) + print("") + + print("Same client code can work with different subclasses:") + client_code(ConcreteClass2()) diff --git a/patterns/refactoring.guru/src/Visitor/Conceptual/Output.txt b/patterns/refactoring.guru/src/Visitor/Conceptual/Output.txt new file mode 100755 index 000000000..f853b060e --- /dev/null +++ b/patterns/refactoring.guru/src/Visitor/Conceptual/Output.txt @@ -0,0 +1,6 @@ +The client code works with all visitors via the base Visitor interface: +A + ConcreteVisitor1 +B + ConcreteVisitor1 +It allows the same client code to work with different types of visitors: +A + ConcreteVisitor2 +B + ConcreteVisitor2 diff --git a/patterns/refactoring.guru/src/Visitor/Conceptual/main.py b/patterns/refactoring.guru/src/Visitor/Conceptual/main.py new file mode 100755 index 000000000..49080dda2 --- /dev/null +++ b/patterns/refactoring.guru/src/Visitor/Conceptual/main.py @@ -0,0 +1,164 @@ +""" +EN: Visitor Design Pattern + +Intent: Lets you separate algorithms from the objects on which they operate. + +RU: Паттерн Посетитель + +Назначение: Позволяет создавать новые операции, не меняя классы объектов, над +которыми эти операции могут выполняться. +""" + + +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import List + + +class Component(ABC): + """ + EN: The Component interface declares an `accept` method that should take the + base visitor interface as an argument. + + RU: Интерфейс Компонента объявляет метод accept, который в качестве + аргумента может получать любой объект, реализующий интерфейс посетителя. + """ + + @abstractmethod + def accept(self, visitor: Visitor) -> None: + pass + + +class ConcreteComponentA(Component): + """ + EN: Each Concrete Component must implement the `accept` method in such a way + that it calls the visitor's method corresponding to the component's class. + + RU: Каждый Конкретный Компонент должен реализовать метод accept таким + образом, чтобы он вызывал метод посетителя, соответствующий классу + компонента. + """ + + def accept(self, visitor: Visitor) -> None: + """ + EN: Note that we're calling `visitConcreteComponentA`, which matches the + current class name. This way we let the visitor know the class of the + component it works with. + + RU: Обратите внимание, мы вызываем visitConcreteComponentA, что + соответствует названию текущего класса. Таким образом мы позволяем + посетителю узнать, с каким классом компонента он работает. + """ + + visitor.visit_concrete_component_a(self) + + def exclusive_method_of_concrete_component_a(self) -> str: + """ + EN: Concrete Components may have special methods that don't exist in + their base class or interface. The Visitor is still able to use these + methods since it's aware of the component's concrete class. + + RU: Конкретные Компоненты могут иметь особые методы, не объявленные в их + базовом классе или интерфейсе. Посетитель всё же может использовать эти + методы, поскольку он знает о конкретном классе компонента. + """ + + return "A" + + +class ConcreteComponentB(Component): + """ + EN: Same here: visitConcreteComponentB => ConcreteComponentB + + RU: То же самое здесь: visitConcreteComponentB => ConcreteComponentB + """ + + def accept(self, visitor: Visitor): + visitor.visit_concrete_component_b(self) + + def special_method_of_concrete_component_b(self) -> str: + return "B" + + +class Visitor(ABC): + """ + EN: The Visitor Interface declares a set of visiting methods that correspond + to component classes. The signature of a visiting method allows the visitor + to identify the exact class of the component that it's dealing with. + + RU: Интерфейс Посетителя объявляет набор методов посещения, соответствующих + классам компонентов. Сигнатура метода посещения позволяет посетителю + определить конкретный класс компонента, с которым он имеет дело. + """ + + @abstractmethod + def visit_concrete_component_a(self, element: ConcreteComponentA) -> None: + pass + + @abstractmethod + def visit_concrete_component_b(self, element: ConcreteComponentB) -> None: + pass + + +""" +EN: Concrete Visitors implement several versions of the same algorithm, which +can work with all concrete component classes. + +You can experience the biggest benefit of the Visitor pattern when using it with +a complex object structure, such as a Composite tree. In this case, it might be +helpful to store some intermediate state of the algorithm while executing +visitor's methods over various objects of the structure. + +RU: Конкретные Посетители реализуют несколько версий одного и того же алгоритма, +которые могут работать со всеми классами конкретных компонентов. + +Максимальную выгоду от паттерна Посетитель вы почувствуете, используя его со +сложной структурой объектов, такой как дерево Компоновщика. В этом случае было +бы полезно хранить некоторое промежуточное состояние алгоритма при выполнении +методов посетителя над различными объектами структуры. +""" + + +class ConcreteVisitor1(Visitor): + def visit_concrete_component_a(self, element) -> None: + print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor1") + + def visit_concrete_component_b(self, element) -> None: + print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor1") + + +class ConcreteVisitor2(Visitor): + def visit_concrete_component_a(self, element) -> None: + print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor2") + + def visit_concrete_component_b(self, element) -> None: + print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor2") + + +def client_code(components: List[Component], visitor: Visitor) -> None: + """ + EN: The client code can run visitor operations over any set of elements + without figuring out their concrete classes. The accept operation directs a + call to the appropriate operation in the visitor object. + + RU: Клиентский код может выполнять операции посетителя над любым набором + элементов, не выясняя их конкретных классов. Операция принятия направляет + вызов к соответствующей операции в объекте посетителя. + """ + + # ... + for component in components: + component.accept(visitor) + # ... + + +if __name__ == "__main__": + components = [ConcreteComponentA(), ConcreteComponentB()] + + print("The client code works with all visitors via the base Visitor interface:") + visitor1 = ConcreteVisitor1() + client_code(components, visitor1) + + print("It allows the same client code to work with different types of visitors:") + visitor2 = ConcreteVisitor2() + client_code(components, visitor2) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0de4748b0..8ba7212cf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,6 +3,6 @@ pytest~=6.2.0 pytest-cov~=2.11.0 pytest-randomly~=3.1.0 -black>=20.8b1 +black>=24.4.2 isort~=5.7.0 flake8~=3.8.0 \ No newline at end of file