លំហសិក្សាកម្ពុជា លំហសិក្សាកម្ពុជា V1.0
ចូល ចុះឈ្មោះ

Homework OOP Lesson Summaries 1-8



Student Name Pheap Pheasothearum
Professor/Lecturer Sek Socheat
Submission Date 26 September 2026

LESSON 1
Advanced OOP Foundations and Project Setup
Overview
Lesson 1 opens the course by moving from procedural thinking (loose functions and dictionaries) to object-oriented thinking (classes and objects that bundle data with behavior). It introduces the vocabulary needed for every later lesson and builds a first working mini-project: A Role-Based Access Control (RBAC) domain model with User, Role, and Permission classes.
Core Vocabulary
⦁ Class is a blueprint for creating objects.
⦁ Object is a real instance created from a class.
⦁ Attribute is data stored inside an object or class.
⦁ Method is a function that belongs to a class.
⦁ self is referring to the current object using the method.
⦁ Responsibility is the clear job a class should do.
Instance vs. Class Attributes
Instance attributes (set with self.x = x inside __init__) belong to one object only; each object can hold a different value. Class attributes are defined directly on the class body and are shared by all instances unless explicitly overridden useful for things like a shared system_name or a running user_count.
Three Method Types
⦁ Instance method  uses self; works with one specific object (e.g. user. Display()).
⦁ Class method (@classmethod)  uses cls; works with the class itself, often as a factory (e.g. User.from_username()).
⦁ Static method (@staticmethod)  uses neither self nor cls; a utility function placed inside the class for organizational reasons (e.g. User.is_valid_username()).
The RBAC Mini Domain
The running example for Lessons 1–3: a User can have one or more Roles; a Role holds a list of Permissions; a user can perform an action if any of their roles has the matching permission. This is composition ("has-a") rather than inheritance Role has permissions, User has roles.




Project Structure and Modules
Good OOP projects separate files by responsibility instead of putting everything in one script:
⦁ main.py - entry point that creates objects and demonstrates behavior.
⦁ models/ - a package folder containing one file per domain class (user.py, role.py, permission.py) plus an __init__.py that marks it as a package.
⦁ A module is one Python file; a package is a folder of related modules; import brings code from one file into another (from models.user import User).
Clean Code Habits
⦁ Type hints (username: str) document expected data types.
⦁ Docstrings explain the purpose of a class or method in plain sentences.
⦁ Naming conventions: PascalCase for classes, snake_case for methods and variables.
Key Takeaway
A class becomes trustworthy when its constructor, attributes, and methods each have one clear job. The lesson closes by previewing Lesson 2's shift toward dataclasses and property-based validation.

LESSON 2
Smart Class Design with Dataclass and Property
Overview
Lesson 2 strengthens the same RBAC model from Lesson 1 by removing boilerplate with @dataclass and adding controlled state with @property. The central design question: is a class mainly a bundle of data, or mainly an object that performs and controls behavior?
Data-Centric vs. Behavior-Centric Classes
⦁ Data-centric class — stores a clear group of related values with light validation/formatting (e.g. Permission, configuration objects, DTOs). Often a good fit for @dataclass.
⦁ Behavior-centric class — coordinates state changes and business actions (e.g. UserAccount, services). Usually better as a regular class with properties and methods.
The @dataclass Decorator
Applied to a class with annotated fields, @dataclass automatically generates __init__, __repr__, and __eq__ (and optional ordering with order=True), removing repetitive constructor/representation/equality code. Required fields must be listed before fields with defaults, or Python raises a TypeError.


The Mutable Default Trap
Using [], {}, or set() directly as a field default is forbidden — modern Python raises a ValueError because all instances would share the same mutable object. The fix is field(default_factory=list) (or dict/set), which builds a fresh, independent collection for every new instance.
Useful field() Options
⦁ default_factory — build a fresh mutable default per instance.
⦁ init=False — exclude a field from the generated constructor.
⦁ repr=False — hide sensitive or noisy values from the generated representation.
⦁ compare=False — exclude a field from equality/ordering comparisons.
⦁ metadata={...} — attach read-only descriptive information.
__post_init__, ClassVar, and InitVar
__post_init__ runs immediately after the generated constructor assigns fields — the right place to normalize text, validate ranges, and fail fast with a clear ValueError before invalid data spreads. ClassVar marks a name as shared by the class (ignored as a dataclass field). InitVar marks a constructor-only input that is passed to __post_init__ but never stored as a regular field (e.g. a raw_password used only to compute a derived length).
frozen, slots, and kw_only
⦁ frozen=True - blocks normal field reassignment after construction; good for stable value objects like Permission.
⦁ slots=True -prevents accidental new attributes and can reduce per-instance memory use.
⦁ kw_only=True - forces named constructor arguments, improving readability and reducing ordering mistakes.
@property: Controlled Access
A property keeps attribute-style syntax (user.email = ...) while adding validation and normalization behind the scenes. Step 1 creates a getter reading from an internal _email attribute; Step 2 adds a setter that normalizes (strip/lowercase), validates (e.g. exactly one '@'), and only then assigns to the internal attribute. A getter without a setter creates a read-only, computed property (e.g. display_name derived from username, or role_count derived from len(self._roles)).
Choosing Field, Property, or Method
⦁ Public field — simple, direct access with no rules to enforce (e.g. permission.description).
⦁ Property — access needs validation, normalization, or a computed value (e.g. user.email, user.role_count).
⦁ Method — the operation is an action, takes parameters, or changes several parts of state (e.g. user.assign_role(role)).
The Practical Model
Permission becomes a frozen, slotted, kw_only, orderable dataclass with a derived resource property. Role is a dataclass with a safe default_factory permission list plus add_permission()/has_permission() methods. User remains a full regular class with validated username/email properties, a tuple-returning roles property (read-only from outside), and behavior methods like assign_role() and has_permission().
Key Takeaway
Smart classes make the correct state easy to create, easy to read, and hard to misuse.

LESSON 3

Python Object Model and Special Methods
Overview
Lesson 3 explains how Python syntax (print(), len(), in, [], |, with) quietly calls special "dunder" methods behind the scenes, and shows how to implement those methods so a custom class feels natural to use while staying predictable and domain-correct.
Object Model: Identity, Type, Value
⦁ Identity -which exact object this is; stable for its lifetime; observed with id(), compared with is.
⦁ Type - what operations the object supports; observed with type(), checked with isinstance().
⦁ Value -the information the object currently represents; may be mutable or immutable.
⦁ is checks object identity (same object in memory); == checks value equality and is controlled by __eq__(). Aliasing means two names point to the same object, so a change through one name is visible through the other.

Attribute Lookup
Accessing user.can searches, in order: the instance namespace, the class namespace, then base classes via the Method Resolution Order (MRO), raising AttributeError only if nothing is found.






Syntax-to-Protocol Map
Syntax Special Method Meaning
str(obj) __str__() User-friendly text
repr(obj) __repr__() Developer-focused representation
a == b __eq__() Value equality
hash(obj) __hash__() Dict/set hash value
len(obj) __len__() Number of contained items
for x in obj __iter__() Iteration
x in obj __contains__() Membership
obj[key] __getitem__() Indexing / lookup
a | b __or__() Domain-specific combination
with obj __enter__ / __exit__ Managed runtime context

__str__ vs __repr__
__str__() targets the application user — readable and concise (called by str(), print(), often f-strings). __repr__() targets the developer/debugger — unambiguous and informative, ideally reconstructive (e.g. User(user_id=1, username="dara")), using !r for nested values. Neither should have side effects, touch a database, or leak secrets.
Equality and the Hashing Contract
Without a custom __eq__(), two separate instances compare by identity only. A safe __eq__() checks isinstance(other, ClassName), returns NotImplemented (never False or None) for unsupported types, and compares a stable domain key such as user_id rather than every mutable field. The hashing contract requires: if a == b then hash(a) == hash(b). Hash only stable, immutable identity fields — hashing a mutable field that later changes can make an object "disappear" from a set or dict.
Container Protocols
A custom collection (e.g. UserDirectory) can feel like a built-in list while protecting domain rules:
⦁ __len__()  returns item count as a non-negative integer.
⦁ __iter__()  best implemented by delegation, e.g. return iter(self._users), not by exposing internal storage.
⦁ __contains__()  can support several membership forms (a User object, an int ID, a username string) when each meaning is documented.
⦁ __getitem__()  one protocol for integer index, slice, and a deliberate string lookup; raises KeyError for missing keys and TypeError for unsupported key types.
Operator Overloading
Overload an operator (e.g. __or__ for manager | security to combine two Roles) only when the meaning is short, familiar, and non-mutating — the test is whether a new developer would guess the meaning correctly. Return NotImplemented for unsupported operand types and prefer returning a new object over silently mutating either operand.
Context Managers
The with statement guarantees a resource lifecycle: __enter__() acquires the resource, sets up state, and returns the object bound after as; __exit__(exc_type, exc_value, traceback) always runs on the way out, records success or failure, releases the resource, and returns False to let any exception propagate (returning True suppresses it, and must be used deliberately). Example: an AuditSession that opens a log file, writes BEGIN, and writes END: SUCCESS or END: ERROR before closing.
Mini Project
Extends the RBAC model: Permission becomes hashable/equatable by normalized code; UserDirectory implements __len__, __iter__, __contains__, __getitem__; Role gains __or__ for combining permission sets; and an AuditSession context manager records actions to a log file with guaranteed cleanup.
Key Takeaway
Special methods should make a class easier to use without making its behavior surprising.

LESSON 4
Abstraction, Composition, Mixins, and Protocols
Overview
Lesson 4 asks: how can objects cooperate without knowing too much about one another? It works through a multi-channel notification system to compare four relationship tools — inheritance/ABC, composition, mixins, and structural Protocols — and teaches when each is the right fit.
Naming the Relationship First
⦁ is-a — EmailChannel is a NotificationChannel → inheritance / ABC.
⦁ has-a — NotificationService has a sender → composition.
⦁ uses-a — NotificationService uses an audit sink → dependency / composition.
⦁ behaves-like — LegacyWebhookSender behaves like a Sender → Protocol / structural typing.
⦁ A sentence that sounds unnatural often predicts an unnatural inheritance hierarchy.


Abstract Base Classes (ABC)
An ABC (from abc import ABC, abstractmethod) declares a nominal contract: a class only becomes part of the family by inheriting from it, and @abstractmethod-marked members must be implemented before a subclass can be instantiated  attempting to instantiate an incomplete subclass raises TypeError immediately, catching missing implementations earlier than a runtime AttributeError. An ABC can also provide concrete shared behavior (e.g. a validate() method) alongside its abstract requirements. Caution: ABC uses ABCMeta, which can conflict with another framework's metaclass (e.g. PyQt)  prefer composition or a Protocol in that case.
Inheritance: Benefits and Costs
Benefits: communicates a clear is a relationship, enables polymorphic substitution, centralizes required behavior. Costs: subclasses depend on parent design, base-class changes ripple outward, multiple inheritance adds MRO complexity. Rule of thumb: do not inherit only to avoid copying a few lines  reuse alone does not prove an is a relationship.
Composition
A coordinating object (e.g. NotificationService) holds focused collaborators (Sender, AuditSink, Metrics, Clock) and delegates work to them rather than inheriting from one of them. Constructor injection makes dependencies visible and replaceable the caller passes in compatible collaborators instead of the class secretly constructing its own. This dramatically simplifies testing  a test can inject a FakeSender that records calls in memory instead of contacting a real email/SMS gateway.
Inheritance or Composition? Decision Table
⦁ Relationship sentence: "X is a Y" → inheritance; "X has/uses a Y" → composition.
⦁ Change: stable base contract → inheritance; parts that change independently → composition.
⦁ Testing: subclass setup required → inheritance; inject a fake/memory object → composition.
⦁ Default question to ask: "Can this be a collaborator instead of a parent?"
Mixins
A mixin (e.g. RetryMixin) contributes one narrow, optional capability  retrying, timestamps, serialization  with a small surface and minimal state; it does not represent the class's main domain identity ("EmailChannel is a RetryMixin" is not the business meaning, it only gains retry behavior). It is combined via multiple inheritance, e.g. class EmailChannel(RetryMixin, NotificationChannel). Python computes a Method Resolution Order (MRO) for consistent attribute lookup  inspect it with Class.mro(). If a mixin defines __init__, every participating class must use cooperative super() and forward *args/**kwargs, or the initialization chain silently breaks; stateless mixins are safer, and composition is often clearer for anything stateful
Structural Protocols
A typing.Protocol (optionally @runtime_checkable) describes required members (e.g. name and send()) without requiring inheritance — any class with compatible members satisfies it, including legacy classes, third-party classes, and small test fakes. isinstance() against a @runtime_checkable Protocol only checks that the members are present, not that their signatures or semantics are correct — it complements, but does not replace, static type checking and tests.
ABC vs Protocol
⦁ ABC: nominal subtyping (declared inheritance); blocks incomplete subclasses; can share concrete implementation; best for an owned, stable class family.
⦁ Protocol: structural subtyping (compatible members); not used to instantiate; matches legacy/third-party classes without modification; best for a consumer-facing behavioral boundary.
⦁ They combine well: concrete channels inherit an ABC, while the service accepts the broader Sender Protocol.
Mini Project: Multi-Channel Notification System
NotificationChannel is an owned ABC with shared validate(); EmailChannel adds RetryMixin for optional retry behavior; Sender and AuditSink are Protocols so the NotificationService can accept ABC-based channels and a structurally compatible LegacyWebhookSender interchangeably; the service itself uses composition and constructor injection, with replace_sender() allowing a live collaborator swap.
Key Takeaway
Depend on the smallest meaningful behavior, and keep changing details behind that boundary.

LESSON 5

Factory Method and Abstract Factory
Overview
Lesson 5 tackles the object-creation problem: when client code directly constructs many concrete classes (e.g. via long if/elif chains), it becomes tightly coupled, hard to extend, and hard to test. Factories centralize creation decisions behind a stable contract.
The Problem
⦁ High coupling — client code knows every concrete class.
⦁ Repeated modification — adding a new type means editing the same selection logic everywhere it appears.
⦁ Harder testing — business logic and creation logic are mixed together
⦁
Simple Factory
A plain function or a class with a static create() method centralizes an if/elif selection in one place (e.g. create_notifier(channel)). This is not a formal GoF pattern but a useful first step; large if/elif blocks inside it can still become hard to maintain.

Factory Method Pattern
An abstract base creator (e.g. ReportService(ABC)) owns a stable workflow method (export()) that calls an abstract create_report() step; concrete subclasses (PDFReportService, CSVReportService) override only that one step to decide which product is created. The client can work through the base creator without knowing the concrete product classes. @classmethod alternative constructors (User.from_username(), User.from_dict()) are a lightweight, everyday form of factory method.

Registry-Based Factory
Replaces a long if/elif chain with a dictionary mapping names to classes (a _registry). New products are added by calling NotifierFactory.register("push", PushNotifier) instead of editing the factory's create() logic — open for extension, closed for modification (a preview of the Open/Closed Principle in Lesson 7).

Abstract Factory
Creates a family of related products that must match each other consistently — e.g. a UIFactory ABC with create_button() and create_dialog(); LightUIFactory produces LightButton + LightDialog while DarkUIFactory produces DarkButton + DarkDialog. The Application class depends only on the UIFactory abstraction, so choosing one concrete factory guarantees every product it creates belongs to the same consistent family.

Practical RBAC Factory
A UserFactory.create_admin() static method can hide default role/permission setup (creating a User, an Administrator Role, and assigning standard permissions) so the main program does not repeat this boilerplate. Caution: a factory should create defaults — it should not grow into a full user-management service.

Factory + Dependency Injection
These are complementary: the factory's job is to select and create a concrete implementation (e.g. NotifierFactory.create("sms") → SMSNotifier); dependency injection's job is to hand that created object to a service from the outside (NotificationService(notifier)), so the service only depends on the notifier's contract.
Pattern Comparison
⦁ Simple Factory — centralize selection; best for a small set of products chosen by input.
⦁ Factory Method — subclass chooses the product; best when a shared creator workflow needs a variable product.
⦁ Registry Factory — extend via registration; avoids long if/elif and supports the Open/Closed Principle.
⦁ Abstract Factory — creates product families; best when related products must stay consistent with each other.
Key Takeaway
A good factory hides object-creation decisions so the rest of the application can depend on clear abstractions instead of concrete classes — but a factory added where there is no real creation variability is overengineering.

LESSON 6

Decorators and Cross-Cutting Concerns
Overview
Lesson 6 shows how Python decorators wrap core business functions with reusable, cross-cutting behavior — logging, timing, validation, authentication, and RBAC authorization — so the wrapped function itself stays focused on one job.
Foundations: First-Class Functions and Closures
⦁ Functions are first-class objects: they can be stored in variables, passed as arguments, and returned from other functions (higher-order functions).
⦁ A closure lets an inner function remember values from its enclosing outer function even after the outer function has returned (e.g. multiplier(factor) returning an inner multiply() that remembers factor).
⦁ A decorator is built on both ideas: it receives a function, builds a wrapper around it, and returns that wrapper  @my_decorator on say_hello() means say_hello = my_decorator(say_hello).
The Professional Decorator Template
A safe, general-purpose decorator always:
⦁ Accepts *args, **kwargs in the wrapper and forwards them to the original function, so it works with any signature.
⦁ Captures and returns the original function's result (return result) so no return value is silently lost.
⦁ Uses @functools.wraps(function) on the wrapper to preserve the original function's name, docstring, and metadata for debugging and testing.
Reusable Cross-Cutting Concerns
⦁ @log_action — prints/logs start and success (or failure) around a function call, without repeating log statements in every function body.
⦁ Timing decorators — measure execution duration for slow operations.
⦁ Caution: never log passwords, secrets, or tokens.
Parameterized Decorators
A decorator that itself takes an argument (e.g. @require_permission("user.delete")) needs three nested layers: an outer decorator factory that accepts the parameter, a middle decorator function that receives the target function, and an inner wrapper that performs the actual check before calling the original function.
Authentication vs. Authorization
⦁ Authentication — "Who are you?" (login, session, @require_login); failure is 401 Unauthorized.
⦁ Authorization — "What are you allowed to do?" (role/permission checks, @require_permission(code)); failure is 403 Forbidden.
⦁ Authentication is checked before authorization.
Decorator Order
Stacking decorators builds a pipeline: @A above @B on a function means function = A(B(function)) — the topmost decorator's wrapper runs outermost. For example, stacking @log_action, @require_login, and @require_permission("user.delete") above delete_user() creates a defined call order that should be sketched out before debugging unexpected behavior.
Class Decorators
A decorator can also target a class rather than a function — for example, @register_notifier("email") on class EmailNotifier stores the class in a NOTIFIER_REGISTRY dictionary the moment the class is defined. This is a direct bridge to Lesson 5's Registry Factory: new implementations register themselves with minimal change to client code.
Practical Project Structure
Decorators, models, and services are kept in separate folders: decorators/ (logging_decorators.py, security_decorators.py, timing_decorators.py, validation_decorators.py), models/ (User, Role, Permission), and services/ (a UserService whose methods are decorated with the security and logging rules).



Guided Lab Decorators
⦁ log_action - print start, success, failure.
⦁ measure_time -measure execution duration.
⦁ require_login -reject anonymous users.
⦁ require_permission(code) -check an RBAC permission.
⦁ validate_positive_id - reject invalid IDs.
⦁ audit_action(name) -record the action name.
Key Takeaway
Decorators allow reusable cross-cutting behavior to surround core business logic without mixing responsibilities inside the business function itself.


LESSON 7

SOLID Principles and Refactoring
Overview
Lesson 7 introduces the five SOLID principles as a practical thinking tool, teaches common code smells that signal a design needs attention, and walks through refactoring a large, tangled UserManager class into small, focused, testable services.
Code Smells
Warning signs that a design may become difficult to change: Giant Class, Long Method, Duplicate Code, too many if/elif branches, Tight Coupling, Hidden Dependencies, Large Interface, and Unexpected Subclass Behavior. SOLID does not make code automatically perfect — it gives a set of questions to ask before and during refactoring.
S — Single Responsibility Principle
A class should have one clear reason to change. A UserManager that validates input, hashes passwords, saves to a database, sends emails, and writes audit logs has five separate reasons to change bundled into one class. The fix: split into UserValidator, PasswordHasher, UserRepository, Notifier, and AuditLogger, each with one purpose, coordinated by a slim UserService that composes them via constructor injection. Refactor by responsibility, not by random file splitting.
O — Open/Closed Principle
Software should be open for extension but closed for risky modification. An if/elif chain selecting a notifier by channel means every new channel requires editing the same function. The fix: depend on a Notifier Protocol (with a send() method); NotificationService only needs an object following that contract, so new notifier implementations (e.g. TelegramNotifier) can be added without rewriting the service — extension without chaos.
L — Liskov Substitution Principle
A subtype should safely replace its base type without breaking the caller's expectations. EmailNotifier and SMSNotifier both implementing send() normally are good substitutions. A ReadOnlyExporter that overrides export() to always raise RuntimeError("Export not supported") is a bad substitution — it violates what callers of the base FileExporter expect. Test question: if we replace the parent with the child, will the caller still work correctly?
I — Interface Segregation Principle
Clients should not be forced to depend on methods they do not use. A single large SystemService Protocol bundling send_email(), generate_report(), backup_database(), and process_payment() forces every consumer to depend on the whole thing. The fix: split into small, focused Protocols — EmailSender, ReportGenerator, BackupService — each with just the one method a given consumer actually needs.

D — Dependency Inversion Principle
High-level business logic should depend on abstractions, not concrete infrastructure. A UserService that internally constructs self.repository = SQLiteUserRepository() is tightly coupled to SQLite. Inverting the dependency — UserService.__init__(self, repository: UserRepository) — lets the service depend on a UserRepository Protocol, with SQLite or an in-memory implementation supplied from outside.
Refactoring Workflow
A safe refactor cycle: write or confirm tests → refactor a small step → run tests → review the design → repeat. Good refactoring keeps external behavior stable, improves names and boundaries, and reduces duplication. Bad refactoring silently changes requirements, adds unnecessary layers, hides errors, or makes simple code needlessly complex.
Testing Benefit: Fake Dependencies
Because dependencies are injected rather than hard-coded, tests can substitute a simple FakeUserRepository (an in-memory list) for a real database. This lets students test business rules — duplicate usernames, invalid input, permission checks, the full service workflow, and error paths — quickly and without any real infrastructure.
Recap: SOLID in One Line Each
⦁ SRP — one reason to change.
⦁ OCP — extend safely.
⦁ LSP — substitute safely.
⦁ ISP — small, focused contracts.
⦁ DIP — depend on abstractions.
Key Takeaway
Clean design is not about more classes; it is about clearer responsibilities and safer change.

LESSON 8

Strategy, Template Method, and Command Patterns
Overview
Lesson 8 covers three behavioral design patterns that organize changing behavior without making core classes fragile: Strategy (choose an algorithm at runtime), Template Method (a stable workflow with customizable steps), and Command (turn a request into an object, enabling undo, history, and queuing).

Why Behavioral Patterns?
⦁ Problems they solve: large if/elif chains, hard-coded algorithms, rigid workflow steps, and actions tightly coupled to the UI.
⦁ Goals: separate what changes from what stays stable, keep core logic clean, enable runtime choices, and improve testing.
Strategy Pattern
Encapsulates interchangeable algorithms behind one shared contract (e.g. a DiscountStrategy Protocol with a discount(total) method). A context object (CheckoutService) holds a reference to a strategy object and delegates to it, instead of choosing behavior with large conditional blocks. StudentDiscount, VIPDiscount, and a NoDiscount strategy can all be swapped into the same CheckoutService at runtime — service = CheckoutService(VIPDiscount()).
⦁ Mechanism: composition  the context delegates behavior to another object.
⦁ Can switch algorithms at runtime.
⦁ Best for interchangeable algorithms (discounts, payment methods).
⦁ Key question: "Which algorithm?"
Template Method Pattern
Defines a stable workflow once in a base class (e.g. ReportExporter.export() calling load() → validate() → format() → save() → finish()) while letting subclasses override only selected steps. CSVExporter and JSONExporter both inherit the same export() sequence but each supplies its own format() step — same workflow, different formatting.
⦁ Mechanism: inheritance — the base class owns the workflow; subclasses override selected steps.
⦁ Runtime switch: usually no (chosen by subclassing, not swapped live).
⦁ Best for a stable process with variation (e.g. export/import workflows).
⦁ Key question: "Which steps vary?"

Strategy vs. Template Method
Both vary behavior but through different mechanisms: Strategy uses composition and can switch at runtime by asking "which behavior?"; Template Method uses inheritance with a fixed base-class workflow and asks "which steps vary?".
Command Pattern
Turns a request or action into an object with its own execute() (and often undo()) method, separating what to do from when or how it gets invoked. A Command object stores everything needed for the request (e.g. RenameTaskCommand holding a service, task_id, and new_title); the Receiver (a service) contains the real business operation; the Invoker can run, queue, log, or undo commands. Undo works by having execute() remember prior state (e.g. old_title) so undo() can restore it.

⦁ Mechanism: encapsulation of a request as an object.
⦁ Supports history, queuing, logging, and undo.
⦁ A Command Manager can maintain an execution history and expose undo_last (), which calls the most recent command's undo ().
⦁ Best for representing actions as objects (e.g. task operations in a to-do app).
⦁ Key question: "How do we represent a request?"
Integrated Project Architecture
The three patterns are combined in one architecture rather than used in isolation: the UI/Client issues a Command, which calls a Service, which may use a Strategy for algorithmic choices; a separate Template governs report/export flows. In short: Command organizes actions, Strategy organizes algorithms, and Template Method organizes workflows.
Key Takeaway
Three patterns answer three different design questions: use Strategy when algorithms must be interchangeable, Template Method when the overall process is stable but selected steps vary, and Command when actions need history, queuing, logging, or undo. Next lesson: Observer Pattern and Event-Driven OOP.
ចែករំលែក
រក្សាទុក
ការផ្សព្វផ្សាយ
ការផ្សព្វផ្សាយ
មតិយោបល់ 0
ចូលគណនី ដើម្បីបញ្ចេញមតិ